Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
7#include "qffmpegmuxer_p.h"
10#include <QtCore/qloggingcategory.h>
11
13
14namespace QFFmpeg {
15
16static Q_LOGGING_CATEGORY(qLcFFmpegAudioEncoder, "qt.multimedia.ffmpeg.audioencoder");
17
20 : EncoderThread(recordingEngine), m_input(input), m_settings(settings)
21{
22 setObjectName(QLatin1String("AudioEncoder"));
23 qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder" << settings.audioCodec();
24
25 m_format = input->device.preferredFormat();
26 auto codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
27 Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
28 FF_COMPLIANCE_NORMAL));
29
30 const AVAudioFormat requestedAudioFormat(m_format);
31
32 m_avCodec = QFFmpeg::findAVEncoder(codecID, {}, requestedAudioFormat.sampleFormat);
33
34 if (!m_avCodec)
35 m_avCodec = QFFmpeg::findAVEncoder(codecID);
36
37 qCDebug(qLcFFmpegAudioEncoder) << "found audio codec" << m_avCodec->name;
38
39 Q_ASSERT(m_avCodec);
40
41 m_stream = avformat_new_stream(recordingEngine.avFormatContext(), nullptr);
42 m_stream->id = recordingEngine.avFormatContext()->nb_streams - 1;
43 m_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
44 m_stream->codecpar->codec_id = codecID;
45#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
46 m_stream->codecpar->channel_layout =
47 adjustChannelLayout(m_avCodec->channel_layouts, requestedAudioFormat.channelLayoutMask);
48 m_stream->codecpar->channels = qPopulationCount(m_stream->codecpar->channel_layout);
49#else
50 m_stream->codecpar->ch_layout =
51 adjustChannelLayout(m_avCodec->ch_layouts, requestedAudioFormat.channelLayout);
52#endif
53 const auto sampleRate =
54 adjustSampleRate(m_avCodec->supported_samplerates, requestedAudioFormat.sampleRate);
55
56 m_stream->codecpar->sample_rate = sampleRate;
57 m_stream->codecpar->frame_size = 1024;
58 m_stream->codecpar->format =
59 adjustSampleFormat(m_avCodec->sample_fmts, requestedAudioFormat.sampleFormat);
60
61 m_stream->time_base = AVRational{ 1, sampleRate };
62
63 qCDebug(qLcFFmpegAudioEncoder) << "set stream time_base" << m_stream->time_base.num << "/"
64 << m_stream->time_base.den;
65}
66
68{
69 m_codecContext.reset(avcodec_alloc_context3(m_avCodec));
70
71 if (m_stream->time_base.num != 1 || m_stream->time_base.den != m_format.sampleRate()) {
72 qCDebug(qLcFFmpegAudioEncoder) << "Most likely, av_format_write_header changed time base from"
73 << 1 << "/" << m_format.sampleRate() << "to"
74 << m_stream->time_base;
75 }
76
77 m_codecContext->time_base = m_stream->time_base;
78
79 avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
80
82 applyAudioEncoderOptions(m_settings, m_avCodec->name, m_codecContext.get(), opts);
83 applyExperimentalCodecOptions(m_avCodec, opts);
84
85 int res = avcodec_open2(m_codecContext.get(), m_avCodec, opts);
86 qCDebug(qLcFFmpegAudioEncoder) << "audio codec opened" << res;
87 qCDebug(qLcFFmpegAudioEncoder) << "audio codec params: fmt=" << m_codecContext->sample_fmt
88 << "rate=" << m_codecContext->sample_rate;
89
90 const AVAudioFormat requestedAudioFormat(m_format);
91 const AVAudioFormat codecAudioFormat(m_codecContext.get());
92
93 if (requestedAudioFormat != codecAudioFormat)
94 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
95}
96
98{
99 {
100 const std::chrono::microseconds bufferDuration(buffer.duration());
101 auto guard = lockLoopData();
102
103 if (m_paused)
104 return;
105
106 // TODO: apply logic with canPushFrame
107
108 m_audioBufferQueue.push(buffer);
109 m_queueDuration += bufferDuration;
110 }
111
112 dataReady();
113}
114
115QAudioBuffer AudioEncoder::takeBuffer()
116{
117 auto locker = lockLoopData();
118 QAudioBuffer result = dequeueIfPossible(m_audioBufferQueue);
119 m_queueDuration -= std::chrono::microseconds(result.duration());
120 return result;
121}
122
124{
125 open();
126 if (m_input) {
127 m_input->setFrameSize(m_codecContext->frame_size);
128 }
129 qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder::init started audio device thread.";
130}
131
133{
134 while (!m_audioBufferQueue.empty())
135 processOne();
136 while (avcodec_send_frame(m_codecContext.get(), nullptr) == AVERROR(EAGAIN))
137 retrievePackets();
138 retrievePackets();
139}
140
142{
143 return !m_audioBufferQueue.empty();
144}
145
146void AudioEncoder::retrievePackets()
147{
148 while (1) {
149 AVPacketUPtr packet(av_packet_alloc());
150 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
151 if (ret < 0) {
152 if (ret != AVERROR(EOF))
153 break;
154 if (ret != AVERROR(EAGAIN)) {
155 char errStr[1024];
156 av_strerror(ret, errStr, 1024);
157 qCDebug(qLcFFmpegAudioEncoder) << "receive packet" << ret << errStr;
158 }
159 break;
160 }
161
162 // qCDebug(qLcFFmpegEncoder) << "writing audio packet" << packet->size << packet->pts <<
163 // packet->dts;
164 packet->stream_index = m_stream->id;
165 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
166 }
167}
168
170{
171 QAudioBuffer buffer = takeBuffer();
172 if (!buffer.isValid())
173 return;
174
175 if (buffer.format() != m_format) {
176 // should we recreate recreate resampler here?
177 qWarning() << "Get invalid audio format:" << buffer.format() << ", expected:" << m_format;
178 return;
179 }
180
181 // qCDebug(qLcFFmpegEncoder) << "new audio buffer" << buffer.byteCount() << buffer.format()
182 // << buffer.frameCount() << codec->frame_size;
183 retrievePackets();
184
185 auto frame = makeAVFrame();
186 frame->format = m_codecContext->sample_fmt;
187#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
188 frame->channel_layout = m_codecContext->channel_layout;
189 frame->channels = m_codecContext->channels;
190#else
191 frame->ch_layout = m_codecContext->ch_layout;
192#endif
193 frame->sample_rate = m_codecContext->sample_rate;
194 frame->nb_samples = buffer.frameCount();
195 if (frame->nb_samples)
196 av_frame_get_buffer(frame.get(), 0);
197
198 if (m_resampler) {
199 const uint8_t *data = buffer.constData<uint8_t>();
200 swr_convert(m_resampler.get(), frame->extended_data, frame->nb_samples, &data,
201 frame->nb_samples);
202 } else {
203 memcpy(frame->buf[0]->data, buffer.constData<uint8_t>(), buffer.byteCount());
204 }
205
206 const auto &timeBase = m_stream->time_base;
207 const auto pts = timeBase.den && timeBase.num
208 ? timeBase.den * m_samplesWritten / (m_codecContext->sample_rate * timeBase.num)
209 : m_samplesWritten;
210 setAVFrameTime(*frame, pts, timeBase);
211 m_samplesWritten += buffer.frameCount();
212
213 qint64 time = m_format.durationForFrames(m_samplesWritten);
215
216 // qCDebug(qLcFFmpegEncoder) << "sending audio frame" << buffer.byteCount() << frame->pts <<
217 // ((double)buffer.frameCount()/frame->sample_rate);
218
219 int ret = avcodec_send_frame(m_codecContext.get(), frame.get());
220 if (ret < 0) {
221 char errStr[1024];
222 av_strerror(ret, errStr, 1024);
223 // qCDebug(qLcFFmpegEncoder) << "error sending frame" << ret << errStr;
224 }
225}
226
228{
229 if (isRunning())
230 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
231 if (!isFinished())
232 return m_audioBufferQueue.empty();
233
234 return false;
235}
236
237
238
239} // namespace QFFmpeg
240
\inmodule QtMultimedia
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
Q_MULTIMEDIA_EXPORT qint64 durationForFrames(qint32 frameCount) const
Return the number of microseconds represented by frameCount frames in this format.
void setFrameSize(int frameSize)
static AVCodecID codecIdForAudioCodec(QMediaFormat::AudioCodec codec)
void addBuffer(const QAudioBuffer &buffer)
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.
AudioEncoder(RecordingEngine &recordingEngine, QFFmpegAudioInput *input, const QMediaEncoderSettings &settings)
bool checkIfCanPushFrame() const override
void init() override
Called on this thread when thread starts.
void dataReady()
Wake thread from sleep and process data until hasData() returns false.
RecordingEngine & m_recordingEngine
void addPacket(AVPacketUPtr packet)
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
bool isRunning() const
Definition qthread.cpp:1064
bool isFinished() const
Definition qthread.cpp:1059
AVFrameUPtr makeAVFrame()
Definition qffmpeg_p.h:136
const AVCodec * findAVEncoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
Definition qffmpeg.cpp:433
AVSampleFormat adjustSampleFormat(const AVSampleFormat *supportedFormats, AVSampleFormat requested)
void setAVFrameTime(AVFrame &frame, int64_t pts, const AVRational &timeBase)
Definition qffmpeg_p.h:71
T dequeueIfPossible(std::queue< T > &queue)
AVChannelLayout adjustChannelLayout(const AVChannelLayout *supportedLayouts, const AVChannelLayout &requested)
int adjustSampleRate(const int *supportedRates, int requested)
void applyExperimentalCodecOptions(const AVCodec *codec, AVDictionary **opts)
Definition qffmpeg.cpp:467
void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
SwrContextUPtr createResampleContext(const AVAudioFormat &inputFormat, const AVAudioFormat &outputFormat)
Definition qffmpeg.cpp:553
std::unique_ptr< AVPacket, AVDeleter< decltype(&av_packet_free), &av_packet_free > > AVPacketUPtr
Definition qffmpeg_p.h:141
Combined button and popup list for selecting options.
Q_DECL_CONST_FUNCTION QT_POPCOUNT_CONSTEXPR uint qPopulationCount(quint32 v) noexcept
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLuint res
GLuint64EXT * result
[6]
GLenum GLenum GLenum input
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
long long qint64
Definition qtypes.h:60
QSettings settings("MySoft", "Star Runner")
[0]
QFrame frame
[0]