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";
192 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
193 QStringLiteral(
"Cannot open any audio codec"));
197 qCDebug(qLcFFmpegAudioEncoder) <<
"found audio codec" << m_codecContext->codec->name;
199 updateResampler(m_sourceFormat);
202 if (
auto input = qobject_cast<QFFmpegAudioInput *>(source()))
203 input->setBufferSize(m_codecContext->frame_size);
205 return EncoderThread::init();
210 while (!m_audioBufferQueue.empty())
213 if (m_avFrameSamplesOffset) {
217 sendPendingFrameToAVCodec();
220 while (avcodec_send_frame(m_codecContext.get(),
nullptr) == AVERROR(EAGAIN))
227 return !m_audioBufferQueue.empty();
233 AVPacketUPtr packet(av_packet_alloc());
234 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
238 case AVERROR(EAGAIN):
242 qCDebug(qLcFFmpegAudioEncoder) <<
"receive packet" << ret << QFFmpeg::AVError{ ret };
247 qCDebug(qLcFFmpegAudioEncoder)
248 <<
"writing audio packet" << packet->size << packet->pts << packet->dts;
249 packet->stream_index = m_stream->id;
250 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
256 QAudioBuffer buffer = takeBuffer();
257 Q_ASSERT(buffer.isValid());
259 if constexpr (audioEncoderExtendedTracing)
260 qCDebug(qLcFFmpegAudioEncoder)
261 <<
"new audio buffer" << buffer.byteCount() << buffer.format()
262 << buffer.frameCount() << m_codecContext->frame_size;
264 if (buffer.format() != m_sourceFormat && !updateResampler(buffer.format()))
267 int samplesOffset = 0;
268 const int bufferSamplesCount =
static_cast<
int>(buffer.frameCount());
270 while (samplesOffset < bufferSamplesCount)
271 handleAudioData(buffer.constData<uint8_t>(), samplesOffset, bufferSamplesCount);
273 Q_ASSERT(samplesOffset == bufferSamplesCount);
278 if (m_encodingStarted)
279 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
281 return m_audioBufferQueue.empty();
290 const AVAudioFormat requestedAudioFormat(sourceFormat);
291 const AVAudioFormat codecAudioFormat(m_codecContext.get());
293 if (requestedAudioFormat != codecAudioFormat) {
294 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
295 if (!swr_is_initialized(m_resampler.get())) {
297 qCWarning(qLcFFmpegAudioEncoder) <<
"Cannot initialize resampler for audio encoder";
298 emit m_recordingEngine.sessionError(
299 QMediaRecorder::FormatError,
300 QStringLiteral(
"Cannot initialize resampler for audio encoder"));
303 qCDebug(qLcFFmpegAudioEncoder) <<
"Created resampler with audio formats conversion\n"
304 << requestedAudioFormat <<
"->" << codecAudioFormat;
306 qCDebug(qLcFFmpegAudioEncoder) <<
"Resampler is not needed due to no-conversion format\n"
307 << requestedAudioFormat;
310 m_sourceFormat = sourceFormat;
315void AudioEncoder::ensurePendingFrame(
int availableSamplesCount)
317 Q_ASSERT(availableSamplesCount >= 0);
322 m_avFrame = makeAVFrame();
324 m_avFrame->format = m_codecContext->sample_fmt;
325#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
326 m_avFrame->ch_layout = m_codecContext->ch_layout;
328 m_avFrame->channel_layout = m_codecContext->channel_layout;
329 m_avFrame->channels = m_codecContext->channels;
331 m_avFrame->sample_rate = m_codecContext->sample_rate;
333 const bool isFixedFrameSize =
334 !(m_codecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
335 && m_codecContext->frame_size;
336 m_avFrame->nb_samples = isFixedFrameSize ? m_codecContext->frame_size : availableSamplesCount;
337 if (m_avFrame->nb_samples) {
338 const int status = av_frame_get_buffer(m_avFrame.get(), 0);
340 qCWarning(qLcFFmpegAudioEncoder)
341 <<
"Failed to allocate audio frame buffer:" << AVError(status);
344 const auto &timeBase = m_stream->time_base;
345 const auto pts = timeBase.den && timeBase.num
346 ? timeBase.den * m_samplesWritten / (m_codecContext->sample_rate * timeBase.num)
348 setAVFrameTime(*m_avFrame, pts, timeBase);
351void AudioEncoder::writeDataToPendingFrame(
const uchar *data,
int &samplesOffset,
int samplesCount)
354 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
356 const int bytesPerSample = av_get_bytes_per_sample(m_codecContext->sample_fmt);
357 const bool isPlanar = av_sample_fmt_is_planar(m_codecContext->sample_fmt);
359#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
360 const int channelsCount = m_codecContext->ch_layout.nb_channels;
362 const int channelsCount = m_codecContext->channels;
365 const int audioDataOffset = isPlanar ? bytesPerSample * m_avFrameSamplesOffset
366 : bytesPerSample * m_avFrameSamplesOffset * channelsCount;
368 const int planesCount = isPlanar ? channelsCount : 1;
369 m_avFramePlanesData.resize(planesCount);
370 for (
int plane = 0; plane < planesCount; ++plane)
371 m_avFramePlanesData[plane] = m_avFrame->extended_data[plane] + audioDataOffset;
373 const int samplesToWrite = m_avFrame->nb_samples - m_avFrameSamplesOffset;
375 (samplesToWrite * m_sourceFormat.sampleRate() + m_codecContext->sample_rate / 2)
376 / m_codecContext->sample_rate;
378 samplesToRead = qBound(1, samplesToRead, samplesCount - samplesOffset);
380 data += m_sourceFormat.bytesForFrames(samplesOffset);
383 m_avFrameSamplesOffset += swr_convert(m_resampler.get(), m_avFramePlanesData.data(),
384 samplesToWrite, &data, samplesToRead);
386 Q_ASSERT(planesCount == 1);
387 m_avFrameSamplesOffset += samplesToRead;
388 memcpy(m_avFramePlanesData[0], data, m_sourceFormat.bytesForFrames(samplesToRead));
391 samplesOffset += samplesToRead;
397 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
399 m_avFrame->nb_samples = m_avFrameSamplesOffset;
401 m_samplesWritten += m_avFrameSamplesOffset;
403 const qint64 time = m_sourceFormat.durationForFrames(
404 m_samplesWritten * m_sourceFormat.sampleRate() / m_codecContext->sample_rate);
405 m_recordingEngine.newTimeStamp(time / 1000);
407 if constexpr (audioEncoderExtendedTracing)
408 qCDebug(qLcFFmpegAudioEncoder) <<
"sendPendingFrameToAVCodec" << m_avFrame->nb_samples
409 << m_codecContext->frame_size << m_avFrame->pts;
411 int ret = avcodec_send_frame(m_codecContext.get(), m_avFrame.get());
413 qCDebug(qLcFFmpegAudioEncoder) <<
"error sending frame" << ret << QFFmpeg::AVError(ret);
416 m_avFrameSamplesOffset = 0;
417 std::fill(m_avFramePlanesData.begin(), m_avFramePlanesData.end(),
nullptr);
420void AudioEncoder::handleAudioData(
const uchar *data,
int &samplesOffset,
int samplesCount)
422 ensurePendingFrame(samplesCount - samplesOffset);
424 writeDataToPendingFrame(data, samplesOffset, samplesCount);
427 if (m_avFrameSamplesOffset < m_avFrame->nb_samples)
432 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.