12#include <QtCore/qloggingcategory.h>
22void setupStreamParameters(AVStream *stream,
const Codec &codec,
23 const AVAudioFormat &requestedAudioFormat)
25 const auto channelLayouts = codec.channelLayouts();
26#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
27 stream->codecpar->ch_layout =
28 adjustChannelLayout(channelLayouts, requestedAudioFormat.channelLayout);
30 stream->codecpar->channel_layout =
31 adjustChannelLayout(channelLayouts, requestedAudioFormat.channelLayoutMask);
32 stream->codecpar->channels = qPopulationCount(stream->codecpar->channel_layout);
34 const auto sampleRates = codec.sampleRates();
35 const auto sampleRate = adjustSampleRate(sampleRates, requestedAudioFormat.sampleRate);
37 stream->codecpar->sample_rate = sampleRate;
42 if (!(codec.capabilities() & AV_CODEC_CAP_VARIABLE_FRAME_SIZE))
43 stream->codecpar->frame_size = 1024;
44 const auto sampleFormats = codec.sampleFormats();
45 stream->codecpar->format = adjustSampleFormat(sampleFormats, requestedAudioFormat.sampleFormat);
47 stream->time_base = AVRational{ 1, sampleRate };
49 qCDebug(qLcFFmpegAudioEncoder)
50 <<
"set stream time_base" << stream->time_base.num <<
"/" << stream->time_base.den;
53bool openCodecContext(AVCodecContext *codecContext, AVStream *stream,
54 const QMediaEncoderSettings &settings)
56 Q_ASSERT(codecContext);
57 codecContext->time_base = stream->time_base;
59 int status = avcodec_parameters_to_context(codecContext, stream->codecpar);
61 qCWarning(qLcFFmpegAudioEncoder)
62 <<
"Cannot set codec parameters; result:" << AVError(status);
67 Codec codec{ codecContext->codec };
69 AVDictionaryHolder opts;
70 applyAudioEncoderOptions(settings, codec.name(), codecContext, opts);
71 applyExperimentalCodecOptions(codec, opts);
73 status = avcodec_open2(codecContext, codec.get(), opts);
76 qCWarning(qLcFFmpegAudioEncoder)
77 <<
"Cannot open audio codec" << codec.name() <<
"; result:" << AVError(status);
81 qCDebug(qLcFFmpegAudioEncoder) <<
"audio codec params: fmt=" << codecContext->sample_fmt
82 <<
"rate=" << codecContext->sample_rate;
84 status = avcodec_parameters_from_context(stream->codecpar, codecContext);
86 qCWarning(qLcFFmpegAudioEncoder) <<
"Cannot get parameters from audio codec context"
87 << codec.name() <<
"; result:" << AVError(status);
97 const QMediaEncoderSettings &settings)
98 : EncoderThread(recordingEngine), m_sourceFormat(sourceFormat), m_settings(settings)
100 setObjectName(QLatin1String(
"AudioEncoder"));
101 qCDebug(qLcFFmpegAudioEncoder) <<
"AudioEncoder" << settings.audioCodec();
103 const AVCodecID codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
104 Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
105 FF_COMPLIANCE_NORMAL));
107 Q_ASSERT(QFFmpeg::findAVEncoder(codecID));
109 m_stream = avformat_new_stream(recordingEngine.avFormatContext(),
nullptr);
110 m_stream->id = recordingEngine.avFormatContext()->nb_streams - 1;
111 m_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
112 m_stream->codecpar->codec_id = codecID;
117 if (!buffer.isValid()) {
118 setEndOfSourceStream();
123 const std::chrono::microseconds bufferDuration(buffer.duration());
124 auto guard = lockLoopData();
126 resetEndOfSourceStream();
133 m_audioBufferQueue.push(buffer);
134 m_queueDuration += bufferDuration;
142 auto locker = lockLoopData();
143 QAudioBuffer result = dequeueIfPossible(m_audioBufferQueue);
144 m_queueDuration -= std::chrono::microseconds(result.duration());
150 const AVAudioFormat requestedAudioFormat(m_sourceFormat);
151 const AVCodecID codecId = m_stream->codecpar->codec_id;
153 namespace ranges = QtMultimediaPrivate::ranges;
155 const auto encoders = findAndScoreEncoders(codecId, [&](
const Codec &codec) {
156 AVScore result = DefaultAVScore;
159 if (
auto fmts = codec.sampleFormats(); !fmts.empty())
160 result += ranges::contains(fmts, requestedAudioFormat.sampleFormat) ? 1 : -1;
162 if (
auto rates = codec.sampleRates(); !rates.empty())
163 result += ranges::contains(rates, requestedAudioFormat.sampleRate) ? 1 : -1;
165#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
166 if (
auto layouts = codec.channelLayouts(); !layouts.empty())
167 result += ranges::contains(layouts, requestedAudioFormat.channelLayout) ? 1 : -1;
169 if (
auto layouts = codec.channelLayouts(); !layouts.empty())
171 ranges::contains(layouts, requestedAudioFormat.channelLayoutMask) ? 1 : -1;
177 for (
const auto &[codec, score] : encoders) {
178 AVCodecContextUPtr codecContext(avcodec_alloc_context3(codec.get()));
182 setupStreamParameters(m_stream, Codec{ codecContext->codec }, requestedAudioFormat);
183 if (!openCodecContext(codecContext.get(), m_stream, m_settings))
186 m_codecContext = std::move(codecContext);
190 if (!m_codecContext) {
191 qCWarning(qLcFFmpegAudioEncoder) <<
"Unable to open any audio codec";
193 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
194 QStringLiteral(
"Cannot open any audio codec"));
198 qCDebug(qLcFFmpegAudioEncoder) <<
"found audio codec" << m_codecContext->codec->name;
200 updateResampler(m_sourceFormat);
203 if (
auto input = qobject_cast<QFFmpegAudioInput *>(source()))
204 input->setBufferSize(m_codecContext->frame_size);
206 return EncoderThread::init();
211 while (!m_audioBufferQueue.empty())
214 if (m_avFrameSamplesOffset) {
218 sendPendingFrameToAVCodec();
221 while (avcodec_send_frame(m_codecContext.get(),
nullptr) == AVERROR(EAGAIN))
228 return !m_audioBufferQueue.empty();
234 AVPacketUPtr packet(av_packet_alloc());
235 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
239 case AVERROR(EAGAIN):
243 qCDebug(qLcFFmpegAudioEncoder) <<
"receive packet" << ret << QFFmpeg::AVError{ ret };
248 qCDebug(qLcFFmpegAudioEncoder)
249 <<
"writing audio packet" << packet->size << packet->pts << packet->dts;
250 packet->stream_index = m_stream->id;
251 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
257 QAudioBuffer buffer = takeBuffer();
258 Q_ASSERT(buffer.isValid());
260 if constexpr (audioEncoderExtendedTracing)
261 qCDebug(qLcFFmpegAudioEncoder)
262 <<
"new audio buffer" << buffer.byteCount() << buffer.format()
263 << buffer.frameCount() << m_codecContext->frame_size;
265 if (buffer.format() != m_sourceFormat && !updateResampler(buffer.format()))
268 int samplesOffset = 0;
269 const int bufferSamplesCount =
static_cast<
int>(buffer.frameCount());
271 while (samplesOffset < bufferSamplesCount)
272 handleAudioData(buffer.constData<uint8_t>(), samplesOffset, bufferSamplesCount);
274 Q_ASSERT(samplesOffset == bufferSamplesCount);
279 if (m_encodingStarted)
280 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
282 return m_audioBufferQueue.empty();
291 const AVAudioFormat requestedAudioFormat(sourceFormat);
292 const AVAudioFormat codecAudioFormat(m_codecContext.get());
294 if (requestedAudioFormat != codecAudioFormat) {
295 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
296 if (!swr_is_initialized(m_resampler.get())) {
298 qCWarning(qLcFFmpegAudioEncoder) <<
"Cannot initialize resampler for audio encoder";
299 emit m_recordingEngine.sessionError(
300 QMediaRecorder::FormatError,
301 QStringLiteral(
"Cannot initialize resampler for audio encoder"));
304 qCDebug(qLcFFmpegAudioEncoder) <<
"Created resampler with audio formats conversion\n"
305 << requestedAudioFormat <<
"->" << codecAudioFormat;
307 qCDebug(qLcFFmpegAudioEncoder) <<
"Resampler is not needed due to no-conversion format\n"
308 << requestedAudioFormat;
311 m_sourceFormat = sourceFormat;
316void AudioEncoder::ensurePendingFrame(
int availableSamplesCount)
318 Q_ASSERT(availableSamplesCount >= 0);
323 m_avFrame = makeAVFrame();
325 m_avFrame->format = m_codecContext->sample_fmt;
326#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
327 m_avFrame->ch_layout = m_codecContext->ch_layout;
329 m_avFrame->channel_layout = m_codecContext->channel_layout;
330 m_avFrame->channels = m_codecContext->channels;
332 m_avFrame->sample_rate = m_codecContext->sample_rate;
334 const bool isFixedFrameSize =
335 !(m_codecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
336 && m_codecContext->frame_size;
337 m_avFrame->nb_samples = isFixedFrameSize ? m_codecContext->frame_size : availableSamplesCount;
338 if (m_avFrame->nb_samples) {
339 const int status = av_frame_get_buffer(m_avFrame.get(), 0);
341 qCWarning(qLcFFmpegAudioEncoder)
342 <<
"Failed to allocate audio frame buffer:" << AVError(status);
345 const auto &timeBase = m_stream->time_base;
346 const auto pts = timeBase.den && timeBase.num
347 ? timeBase.den * m_samplesWritten / (m_codecContext->sample_rate * timeBase.num)
349 setAVFrameTime(*m_avFrame, pts, timeBase);
352void AudioEncoder::writeDataToPendingFrame(
const uchar *data,
int &samplesOffset,
int samplesCount)
355 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
357 const int bytesPerSample = av_get_bytes_per_sample(m_codecContext->sample_fmt);
358 const bool isPlanar = av_sample_fmt_is_planar(m_codecContext->sample_fmt);
360#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
361 const int channelsCount = m_codecContext->ch_layout.nb_channels;
363 const int channelsCount = m_codecContext->channels;
366 const int audioDataOffset = isPlanar ? bytesPerSample * m_avFrameSamplesOffset
367 : bytesPerSample * m_avFrameSamplesOffset * channelsCount;
369 const int planesCount = isPlanar ? channelsCount : 1;
370 m_avFramePlanesData.resize(planesCount);
371 for (
int plane = 0; plane < planesCount; ++plane)
372 m_avFramePlanesData[plane] = m_avFrame->extended_data[plane] + audioDataOffset;
374 const int samplesToWrite = m_avFrame->nb_samples - m_avFrameSamplesOffset;
376 (samplesToWrite * m_sourceFormat.sampleRate() + m_codecContext->sample_rate / 2)
377 / m_codecContext->sample_rate;
379 samplesToRead = qBound(1, samplesToRead, samplesCount - samplesOffset);
381 data += m_sourceFormat.bytesForFrames(samplesOffset);
384 m_avFrameSamplesOffset += swr_convert(m_resampler.get(), m_avFramePlanesData.data(),
385 samplesToWrite, &data, samplesToRead);
387 Q_ASSERT(planesCount == 1);
388 m_avFrameSamplesOffset += samplesToRead;
389 memcpy(m_avFramePlanesData[0], data, m_sourceFormat.bytesForFrames(samplesToRead));
392 samplesOffset += samplesToRead;
398 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
400 m_avFrame->nb_samples = m_avFrameSamplesOffset;
402 m_samplesWritten += m_avFrameSamplesOffset;
404 const qint64 time = m_sourceFormat.durationForFrames(
405 m_samplesWritten * m_sourceFormat.sampleRate() / m_codecContext->sample_rate);
406 m_recordingEngine.newTimeStamp(time / 1000);
408 if constexpr (audioEncoderExtendedTracing)
409 qCDebug(qLcFFmpegAudioEncoder) <<
"sendPendingFrameToAVCodec" << m_avFrame->nb_samples
410 << m_codecContext->frame_size << m_avFrame->pts;
412 int ret = avcodec_send_frame(m_codecContext.get(), m_avFrame.get());
414 qCDebug(qLcFFmpegAudioEncoder) <<
"error sending frame" << ret << QFFmpeg::AVError(ret);
417 m_avFrameSamplesOffset = 0;
418 std::fill(m_avFramePlanesData.begin(), m_avFramePlanesData.end(),
nullptr);
421void AudioEncoder::handleAudioData(
const uchar *data,
int &samplesOffset,
int samplesCount)
423 ensurePendingFrame(samplesCount - samplesOffset);
425 writeDataToPendingFrame(data, samplesOffset, samplesCount);
428 if (m_avFrameSamplesOffset < m_avFrame->nb_samples)
433 sendPendingFrameToAVCodec();
bool init() override
Called on this thread when thread starts.
void cleanup() override
Called on this thread before thread exits.
bool hasData() const override
Must return true when data is available for processing.
void processOne() override
Process one work item.
bool checkIfCanPushFrame() const override
Q_STATIC_LOGGING_CATEGORY(qLCAndroidVideoDevices, "qt.multimedia.ffmpeg.android.videoDevices")
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
static constexpr bool audioEncoderExtendedTracing
Combined button and popup list for selecting options.