Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegaudioencoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
12#include <QtCore/qloggingcategory.h>
13
15
16namespace QFFmpeg {
17
18Q_STATIC_LOGGING_CATEGORY(qLcFFmpegAudioEncoder, "qt.multimedia.ffmpeg.audioencoder");
19static constexpr bool audioEncoderExtendedTracing = false;
20
21namespace {
22void setupStreamParameters(AVStream *stream, const Codec &codec,
23 const AVAudioFormat &requestedAudioFormat)
24{
25 const auto channelLayouts = codec.channelLayouts();
26#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
27 stream->codecpar->ch_layout =
28 adjustChannelLayout(channelLayouts, requestedAudioFormat.channelLayout);
29#else
30 stream->codecpar->channel_layout =
31 adjustChannelLayout(channelLayouts, requestedAudioFormat.channelLayoutMask);
32 stream->codecpar->channels = qPopulationCount(stream->codecpar->channel_layout);
33#endif
34 const auto sampleRates = codec.sampleRates();
35 const auto sampleRate = adjustSampleRate(sampleRates, requestedAudioFormat.sampleRate);
36
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);
41
42 stream->time_base = AVRational{ 1, sampleRate };
43
44 qCDebug(qLcFFmpegAudioEncoder)
45 << "set stream time_base" << stream->time_base.num << "/" << stream->time_base.den;
46}
47
48bool openCodecContext(AVCodecContext *codecContext, AVStream *stream,
49 const QMediaEncoderSettings &settings)
50{
51 Q_ASSERT(codecContext);
52 codecContext->time_base = stream->time_base;
53
54 avcodec_parameters_to_context(codecContext, stream->codecpar);
55
56 // if avcodec_open2 fails, it may clean codecContext->codec
57 Codec codec{ codecContext->codec };
58
59 AVDictionaryHolder opts;
60 applyAudioEncoderOptions(settings, QByteArray{ codec.name() }, codecContext, opts);
61 applyExperimentalCodecOptions(codec, opts);
62
63 const int res = avcodec_open2(codecContext, codec.get(), opts);
64
65 if (res != 0) {
66 qCWarning(qLcFFmpegAudioEncoder)
67 << "Cannot open audio codec" << codec.name() << "; result:" << AVError(res);
68 return false;
69 }
70
71 qCDebug(qLcFFmpegAudioEncoder) << "audio codec params: fmt=" << codecContext->sample_fmt
72 << "rate=" << codecContext->sample_rate;
73
74 avcodec_parameters_from_context(stream->codecpar, codecContext);
75
76 return true;
77}
78
79} // namespace
80
81AudioEncoder::AudioEncoder(RecordingEngine &recordingEngine, const QAudioFormat &sourceFormat,
82 const QMediaEncoderSettings &settings)
83 : EncoderThread(recordingEngine), m_sourceFormat(sourceFormat), m_settings(settings)
84{
85 setObjectName(QLatin1String("AudioEncoder"));
86 qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder" << settings.audioCodec();
87
88 const AVCodecID codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
89 Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
90 FF_COMPLIANCE_NORMAL));
91
92 Q_ASSERT(QFFmpeg::findAVEncoder(codecID));
93
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;
98}
99
100void AudioEncoder::addBuffer(const QAudioBuffer &buffer)
101{
102 if (!buffer.isValid()) {
103 setEndOfSourceStream();
104 return;
105 }
106
107 {
108 const std::chrono::microseconds bufferDuration(buffer.duration());
109 auto guard = lockLoopData();
110
111 resetEndOfSourceStream();
112
113 if (m_paused)
114 return;
115
116 // TODO: apply logic with canPushFrame
117
118 m_audioBufferQueue.push(buffer);
119 m_queueDuration += bufferDuration;
120 }
121
122 dataReady();
123}
124
125QAudioBuffer AudioEncoder::takeBuffer()
126{
127 auto locker = lockLoopData();
128 QAudioBuffer result = dequeueIfPossible(m_audioBufferQueue);
129 m_queueDuration -= std::chrono::microseconds(result.duration());
130 return result;
131}
132
134{
135 const AVAudioFormat requestedAudioFormat(m_sourceFormat);
136
137 QFFmpeg::findAndOpenAVEncoder(
138 m_stream->codecpar->codec_id,
139 [&](const Codec &codec) {
140 AVScore result = DefaultAVScore;
141
142 // Attempt to find no-conversion format
143 if (auto fmts = codec.sampleFormats(); !fmts.empty())
144 result += hasValue(fmts, requestedAudioFormat.sampleFormat) ? 1 : -1;
145
146 if (auto rates = codec.sampleRates(); !rates.empty())
147 result += hasValue(rates, requestedAudioFormat.sampleRate) ? 1 : -1;
148
149#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
150 if (auto layouts = codec.channelLayouts(); !layouts.empty())
151 result += hasValue(layouts, requestedAudioFormat.channelLayout) ? 1 : -1;
152#else
153 if (auto layouts = codec.channelLayouts(); !layouts.empty())
154 result += hasValue(layouts, requestedAudioFormat.channelLayoutMask) ? 1 : -1;
155#endif
156
157 return result;
158 },
159 [&](const Codec &codec) {
160 AVCodecContextUPtr codecContext(avcodec_alloc_context3(codec.get()));
161 if (!codecContext)
162 return false;
163
164 setupStreamParameters(m_stream, Codec{ codecContext->codec }, requestedAudioFormat);
165 if (!openCodecContext(codecContext.get(), m_stream, m_settings))
166 return false;
167
168 m_codecContext = std::move(codecContext);
169 return true;
170 });
171
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"));
176 return false;
177 }
178
179 qCDebug(qLcFFmpegAudioEncoder) << "found audio codec" << m_codecContext->codec->name;
180
181 updateResampler(m_sourceFormat);
182
183 // TODO: try to address this dependency here.
184 if (auto input = qobject_cast<QFFmpegAudioInput *>(source()))
185 input->setBufferSize(m_codecContext->frame_size);
186
187 return EncoderThread::init();
188}
189
191{
192 while (!m_audioBufferQueue.empty())
194
195 if (m_avFrameSamplesOffset) {
196 // the size of the last frame can be less than m_codecContext->frame_size
197
198 retrievePackets();
199 sendPendingFrameToAVCodec();
200 }
201
202 while (avcodec_send_frame(m_codecContext.get(), nullptr) == AVERROR(EAGAIN))
203 retrievePackets();
204 retrievePackets();
205}
206
208{
209 return !m_audioBufferQueue.empty();
210}
211
212void AudioEncoder::retrievePackets()
213{
214 while (true) {
215 AVPacketUPtr packet(av_packet_alloc());
216 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
217 switch (ret) {
218 case 0:
219 break;
220 case AVERROR(EAGAIN):
221 case AVERROR(EOF):
222 return;
223 default:
224 qCDebug(qLcFFmpegAudioEncoder) << "receive packet" << ret << QFFmpeg::AVError{ ret };
225 return;
226 }
227
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));
233 }
234}
235
237{
238 QAudioBuffer buffer = takeBuffer();
239 Q_ASSERT(buffer.isValid());
240
241 if constexpr (audioEncoderExtendedTracing)
242 qCDebug(qLcFFmpegAudioEncoder)
243 << "new audio buffer" << buffer.byteCount() << buffer.format()
244 << buffer.frameCount() << m_codecContext->frame_size;
245
246 if (buffer.format() != m_sourceFormat && !updateResampler(buffer.format()))
247 return;
248
249 int samplesOffset = 0;
250 const int bufferSamplesCount = static_cast<int>(buffer.frameCount());
251
252 while (samplesOffset < bufferSamplesCount)
253 handleAudioData(buffer.constData<uint8_t>(), samplesOffset, bufferSamplesCount);
254
255 Q_ASSERT(samplesOffset == bufferSamplesCount);
256}
257
259{
260 if (m_encodingStarted)
261 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
262 if (!isFinished())
263 return m_audioBufferQueue.empty();
264
265 return false;
266}
267
268bool AudioEncoder::updateResampler(const QAudioFormat &sourceFormat)
269{
270 m_resampler.reset();
271
272 const AVAudioFormat requestedAudioFormat(sourceFormat);
273 const AVAudioFormat codecAudioFormat(m_codecContext.get());
274
275 if (requestedAudioFormat != codecAudioFormat) {
276 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
277 if (!swr_is_initialized(m_resampler.get())) {
278 m_sourceFormat = {};
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"));
283 return false;
284 }
285 qCDebug(qLcFFmpegAudioEncoder) << "Created resampler with audio formats conversion\n"
286 << requestedAudioFormat << "->" << codecAudioFormat;
287 } else {
288 qCDebug(qLcFFmpegAudioEncoder) << "Resampler is not needed due to no-conversion format\n"
289 << requestedAudioFormat;
290 }
291
292 m_sourceFormat = sourceFormat;
293
294 return true;
295}
296
297void AudioEncoder::ensurePendingFrame(int availableSamplesCount)
298{
299 Q_ASSERT(availableSamplesCount >= 0);
300
301 if (m_avFrame)
302 return;
303
304 m_avFrame = makeAVFrame();
305
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;
309#else
310 m_avFrame->channel_layout = m_codecContext->channel_layout;
311 m_avFrame->channels = m_codecContext->channels;
312#endif
313 m_avFrame->sample_rate = m_codecContext->sample_rate;
314
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);
321
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)
325 : m_samplesWritten;
326 setAVFrameTime(*m_avFrame, pts, timeBase);
327}
328
329void AudioEncoder::writeDataToPendingFrame(const uchar *data, int &samplesOffset, int samplesCount)
330{
331 Q_ASSERT(m_avFrame);
332 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
333
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);
336
337#if QT_FFMPEG_HAS_AV_CHANNEL_LAYOUT
338 const int channelsCount = m_codecContext->ch_layout.nb_channels;
339#else
340 const int channelsCount = m_codecContext->channels;
341#endif
342
343 const int audioDataOffset = isPlanar ? bytesPerSample * m_avFrameSamplesOffset
344 : bytesPerSample * m_avFrameSamplesOffset * channelsCount;
345
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;
350
351 const int samplesToWrite = m_avFrame->nb_samples - m_avFrameSamplesOffset;
352 int samplesToRead =
353 (samplesToWrite * m_sourceFormat.sampleRate() + m_codecContext->sample_rate / 2)
354 / m_codecContext->sample_rate;
355 // the lower bound is need to get round infinite loops in corner cases
356 samplesToRead = qBound(1, samplesToRead, samplesCount - samplesOffset);
357
358 data += m_sourceFormat.bytesForFrames(samplesOffset);
359
360 if (m_resampler) {
361 m_avFrameSamplesOffset += swr_convert(m_resampler.get(), m_avFramePlanesData.data(),
362 samplesToWrite, &data, samplesToRead);
363 } else {
364 Q_ASSERT(planesCount == 1);
365 m_avFrameSamplesOffset += samplesToRead;
366 memcpy(m_avFramePlanesData[0], data, m_sourceFormat.bytesForFrames(samplesToRead));
367 }
368
369 samplesOffset += samplesToRead;
370}
371
372void AudioEncoder::sendPendingFrameToAVCodec()
373{
374 Q_ASSERT(m_avFrame);
375 Q_ASSERT(m_avFrameSamplesOffset <= m_avFrame->nb_samples);
376
377 m_avFrame->nb_samples = m_avFrameSamplesOffset;
378
379 m_samplesWritten += m_avFrameSamplesOffset;
380
381 const qint64 time = m_sourceFormat.durationForFrames(
382 m_samplesWritten * m_sourceFormat.sampleRate() / m_codecContext->sample_rate);
383 m_recordingEngine.newTimeStamp(time / 1000);
384
385 if constexpr (audioEncoderExtendedTracing)
386 qCDebug(qLcFFmpegAudioEncoder) << "sendPendingFrameToAVCodec" << m_avFrame->nb_samples
387 << m_codecContext->frame_size << m_avFrame->pts;
388
389 int ret = avcodec_send_frame(m_codecContext.get(), m_avFrame.get());
390 if (ret < 0)
391 qCDebug(qLcFFmpegAudioEncoder) << "error sending frame" << ret << QFFmpeg::AVError(ret);
392
393 m_avFrame = nullptr;
394 m_avFrameSamplesOffset = 0;
395 std::fill(m_avFramePlanesData.begin(), m_avFramePlanesData.end(), nullptr);
396}
397
398void AudioEncoder::handleAudioData(const uchar *data, int &samplesOffset, int samplesCount)
399{
400 ensurePendingFrame(samplesCount - samplesOffset);
401
402 writeDataToPendingFrame(data, samplesOffset, samplesCount);
403
404 // The frame is not ready yet
405 if (m_avFrameSamplesOffset < m_avFrame->nb_samples)
406 return;
407
408 retrievePackets();
409
410 sendPendingFrameToAVCodec();
411}
412
413} // namespace QFFmpeg
414
415QT_END_NAMESPACE
The QAudioFormat class stores audio stream parameter information.
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