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;
38 stream->codecpar->frame_size = 1024;
39 const auto sampleFormats = codec.sampleFormats();
40 stream->codecpar->format = adjustSampleFormat(sampleFormats, requestedAudioFormat.sampleFormat);
42 stream->time_base = AVRational{ 1, sampleRate };
44 qCDebug(qLcFFmpegAudioEncoder)
45 <<
"set stream time_base" << stream->time_base.num <<
"/" << stream->time_base.den;
48bool openCodecContext(AVCodecContext *codecContext, AVStream *stream,
49 const QMediaEncoderSettings &settings)
51 Q_ASSERT(codecContext);
52 codecContext->time_base = stream->time_base;
54 avcodec_parameters_to_context(codecContext, stream->codecpar);
57 Codec codec{ codecContext->codec };
59 AVDictionaryHolder opts;
60 applyAudioEncoderOptions(settings, QByteArray{ codec.name() }, codecContext, opts);
61 applyExperimentalCodecOptions(codec, opts);
63 const int res = avcodec_open2(codecContext, codec.get(), opts);
66 qCWarning(qLcFFmpegAudioEncoder)
67 <<
"Cannot open audio codec" << codec.name() <<
"; result:" << AVError(res);
71 qCDebug(qLcFFmpegAudioEncoder) <<
"audio codec params: fmt=" << codecContext->sample_fmt
72 <<
"rate=" << codecContext->sample_rate;
74 avcodec_parameters_from_context(stream->codecpar, codecContext);
82 const QMediaEncoderSettings &settings)
83 : EncoderThread(recordingEngine), m_sourceFormat(sourceFormat), m_settings(settings)
85 setObjectName(QLatin1String(
"AudioEncoder"));
86 qCDebug(qLcFFmpegAudioEncoder) <<
"AudioEncoder" << settings.audioCodec();
88 const AVCodecID codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
89 Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
90 FF_COMPLIANCE_NORMAL));
92 Q_ASSERT(QFFmpeg::findAVEncoder(codecID));
94 m_stream = avformat_new_stream(recordingEngine.avFormatContext(),
nullptr);
95 m_stream->id = recordingEngine.avFormatContext()->nb_streams - 1;
96 m_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
97 m_stream->codecpar->codec_id = codecID;
102 if (!buffer.isValid()) {
103 setEndOfSourceStream();
108 const std::chrono::microseconds bufferDuration(buffer.duration());
109 auto guard = lockLoopData();
111 resetEndOfSourceStream();
118 m_audioBufferQueue.push(buffer);
119 m_queueDuration += bufferDuration;
127 auto locker = lockLoopData();
128 QAudioBuffer result = dequeueIfPossible(m_audioBufferQueue);
129 m_queueDuration -= std::chrono::microseconds(result.duration());
135 const AVAudioFormat requestedAudioFormat(m_sourceFormat);
137 QFFmpeg::findAndOpenAVEncoder(
138 m_stream->codecpar->codec_id,
139 [&](
const Codec &codec) {
140 AVScore result = DefaultAVScore;
143 if (
auto fmts = codec.sampleFormats(); !fmts.empty())
144 result += hasValue(fmts, requestedAudioFormat.sampleFormat) ? 1 : -1;
146 if (
auto rates = codec.sampleRates(); !rates.empty())
147 result += hasValue(rates, requestedAudioFormat.sampleRate) ? 1 : -1;
149#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
150 if (
auto layouts = codec.channelLayouts(); !layouts.empty())
151 result += hasValue(layouts, requestedAudioFormat.channelLayout) ? 1 : -1;
153 if (
auto layouts = codec.channelLayouts(); !layouts.empty())
154 result += hasValue(layouts, requestedAudioFormat.channelLayoutMask) ? 1 : -1;
159 [&](
const Codec &codec) {
160 AVCodecContextUPtr codecContext(avcodec_alloc_context3(codec.get()));
164 setupStreamParameters(m_stream, Codec{ codecContext->codec }, requestedAudioFormat);
165 if (!openCodecContext(codecContext.get(), m_stream, m_settings))
168 m_codecContext = std::move(codecContext);
172 if (!m_codecContext) {
173 qCWarning(qLcFFmpegAudioEncoder) <<
"Unable to open any audio codec";
174 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
175 QStringLiteral(
"Cannot open any audio codec"));
179 qCDebug(qLcFFmpegAudioEncoder) <<
"found audio codec" << m_codecContext->codec->name;
181 updateResampler(m_sourceFormat);
184 if (
auto input = qobject_cast<QFFmpegAudioInput *>(source()))
185 input->setBufferSize(m_codecContext->frame_size);
187 return EncoderThread::init();
192 while (!m_audioBufferQueue.empty())
195 if (m_avFrameSamplesOffset) {
199 sendPendingFrameToAVCodec();
202 while (avcodec_send_frame(m_codecContext.get(),
nullptr) == AVERROR(EAGAIN))
209 return !m_audioBufferQueue.empty();
215 AVPacketUPtr packet(av_packet_alloc());
216 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
220 case AVERROR(EAGAIN):
224 qCDebug(qLcFFmpegAudioEncoder) <<
"receive packet" << ret << QFFmpeg::AVError{ ret };
228 if constexpr (audioEncoderExtendedTracing)
229 qCDebug(qLcFFmpegAudioEncoder)
230 <<
"writing audio packet" << packet->size << packet->pts << packet->dts;
231 packet->stream_index = m_stream->id;
232 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
238 QAudioBuffer buffer = takeBuffer();
239 Q_ASSERT(buffer.isValid());
241 if constexpr (audioEncoderExtendedTracing)
242 qCDebug(qLcFFmpegAudioEncoder)
243 <<
"new audio buffer" << buffer.byteCount() << buffer.format()
244 << buffer.frameCount() << m_codecContext->frame_size;
246 if (buffer.format() != m_sourceFormat && !updateResampler(buffer.format()))
249 int samplesOffset = 0;
250 const int bufferSamplesCount =
static_cast<
int>(buffer.frameCount());
252 while (samplesOffset < bufferSamplesCount)
253 handleAudioData(buffer.constData<uint8_t>(), samplesOffset, bufferSamplesCount);
255 Q_ASSERT(samplesOffset == bufferSamplesCount);
260 if (m_encodingStarted)
261 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
263 return m_audioBufferQueue.empty();
272 const AVAudioFormat requestedAudioFormat(sourceFormat);
273 const AVAudioFormat codecAudioFormat(m_codecContext.get());
275 if (requestedAudioFormat != codecAudioFormat) {
276 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
277 if (!swr_is_initialized(m_resampler.get())) {
279 qCWarning(qLcFFmpegAudioEncoder) <<
"Cannot initialize resampler for audio encoder";
280 emit m_recordingEngine.sessionError(
281 QMediaRecorder::FormatError,
282 QStringLiteral(
"Cannot initialize resampler for audio encoder"));
285 qCDebug(qLcFFmpegAudioEncoder) <<
"Created resampler with audio formats conversion\n"
286 << requestedAudioFormat <<
"->" << codecAudioFormat;
288 qCDebug(qLcFFmpegAudioEncoder) <<
"Resampler is not needed due to no-conversion format\n"
289 << requestedAudioFormat;
292 m_sourceFormat = sourceFormat;
297void AudioEncoder::ensurePendingFrame(
int availableSamplesCount)
299 Q_ASSERT(availableSamplesCount >= 0);
304 m_avFrame = makeAVFrame();
306 m_avFrame->format = m_codecContext->sample_fmt;
307#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
308 m_avFrame->ch_layout = m_codecContext->ch_layout;
310 m_avFrame->channel_layout = m_codecContext->channel_layout;
311 m_avFrame->channels = m_codecContext->channels;
313 m_avFrame->sample_rate = m_codecContext->sample_rate;
315 const bool isFixedFrameSize =
316 !(m_codecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
317 && m_codecContext->frame_size;
318 m_avFrame->nb_samples = isFixedFrameSize ? m_codecContext->frame_size : availableSamplesCount;
319 if (m_avFrame->nb_samples)
320 av_frame_get_buffer(m_avFrame.get(), 0);
322 const auto &timeBase = m_stream->time_base;
323 const auto pts = timeBase.den && timeBase.num
324 ? timeBase.den * m_samplesWritten / (m_codecContext->sample_rate * timeBase.num)
326 setAVFrameTime(*m_avFrame, pts, timeBase);
329void AudioEncoder::writeDataToPendingFrame(
const uchar *data,
int &samplesOffset,
int samplesCount)
332 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
334 const int bytesPerSample = av_get_bytes_per_sample(m_codecContext->sample_fmt);
335 const bool isPlanar = av_sample_fmt_is_planar(m_codecContext->sample_fmt);
337#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
338 const int channelsCount = m_codecContext->ch_layout.nb_channels;
340 const int channelsCount = m_codecContext->channels;
343 const int audioDataOffset = isPlanar ? bytesPerSample * m_avFrameSamplesOffset
344 : bytesPerSample * m_avFrameSamplesOffset * channelsCount;
346 const int planesCount = isPlanar ? channelsCount : 1;
347 m_avFramePlanesData.resize(planesCount);
348 for (
int plane = 0; plane < planesCount; ++plane)
349 m_avFramePlanesData[plane] = m_avFrame->extended_data[plane] + audioDataOffset;
351 const int samplesToWrite = m_avFrame->nb_samples - m_avFrameSamplesOffset;
353 (samplesToWrite * m_sourceFormat.sampleRate() + m_codecContext->sample_rate / 2)
354 / m_codecContext->sample_rate;
356 samplesToRead = qBound(1, samplesToRead, samplesCount - samplesOffset);
358 data += m_sourceFormat.bytesForFrames(samplesOffset);
361 m_avFrameSamplesOffset += swr_convert(m_resampler.get(), m_avFramePlanesData.data(),
362 samplesToWrite, &data, samplesToRead);
364 Q_ASSERT(planesCount == 1);
365 m_avFrameSamplesOffset += samplesToRead;
366 memcpy(m_avFramePlanesData[0], data, m_sourceFormat.bytesForFrames(samplesToRead));
369 samplesOffset += samplesToRead;
375 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
377 m_avFrame->nb_samples = m_avFrameSamplesOffset;
379 m_samplesWritten += m_avFrameSamplesOffset;
381 const qint64 time = m_sourceFormat.durationForFrames(
382 m_samplesWritten * m_sourceFormat.sampleRate() / m_codecContext->sample_rate);
383 m_recordingEngine.newTimeStamp(time / 1000);
385 if constexpr (audioEncoderExtendedTracing)
386 qCDebug(qLcFFmpegAudioEncoder) <<
"sendPendingFrameToAVCodec" << m_avFrame->nb_samples
387 << m_codecContext->frame_size << m_avFrame->pts;
389 int ret = avcodec_send_frame(m_codecContext.get(), m_avFrame.get());
391 qCDebug(qLcFFmpegAudioEncoder) <<
"error sending frame" << ret << QFFmpeg::AVError(ret);
394 m_avFrameSamplesOffset = 0;
395 std::fill(m_avFramePlanesData.begin(), m_avFramePlanesData.end(),
nullptr);
398void AudioEncoder::handleAudioData(
const uchar *data,
int &samplesOffset,
int samplesCount)
400 ensurePendingFrame(samplesCount - samplesOffset);
402 writeDataToPendingFrame(data, samplesOffset, samplesCount);
405 if (m_avFrameSamplesOffset < m_avFrame->nb_samples)
410 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(qLcEncodingFormatContext, "qt.multimedia.ffmpeg.encodingformatcontext")
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
static constexpr bool audioEncoderExtendedTracing