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
18AudioEncoder::AudioEncoder(RecordingEngine &recordingEngine, const QAudioFormat &sourceFormat,
20 : EncoderThread(recordingEngine), m_format(sourceFormat), m_settings(settings)
21{
22 setObjectName(QLatin1String("AudioEncoder"));
23 qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder" << settings.audioCodec();
24
25 auto codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(settings.audioCodec());
26 Q_ASSERT(avformat_query_codec(recordingEngine.avFormatContext()->oformat, codecID,
27 FF_COMPLIANCE_NORMAL));
28
29 const AVAudioFormat requestedAudioFormat(m_format);
30
31 m_avCodec = QFFmpeg::findAVEncoder(codecID, {}, requestedAudioFormat.sampleFormat);
32
33 if (!m_avCodec)
34 m_avCodec = QFFmpeg::findAVEncoder(codecID);
35
36 qCDebug(qLcFFmpegAudioEncoder) << "found audio codec" << m_avCodec->name;
37
38 Q_ASSERT(m_avCodec);
39
40 m_stream = avformat_new_stream(recordingEngine.avFormatContext(), nullptr);
41 m_stream->id = recordingEngine.avFormatContext()->nb_streams - 1;
42 m_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
43 m_stream->codecpar->codec_id = codecID;
44#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
45 m_stream->codecpar->channel_layout =
46 adjustChannelLayout(m_avCodec->channel_layouts, requestedAudioFormat.channelLayoutMask);
47 m_stream->codecpar->channels = qPopulationCount(m_stream->codecpar->channel_layout);
48#else
49 m_stream->codecpar->ch_layout =
50 adjustChannelLayout(m_avCodec->ch_layouts, requestedAudioFormat.channelLayout);
51#endif
52 const auto sampleRate =
53 adjustSampleRate(m_avCodec->supported_samplerates, requestedAudioFormat.sampleRate);
54
55 m_stream->codecpar->sample_rate = sampleRate;
56 m_stream->codecpar->frame_size = 1024;
57 m_stream->codecpar->format =
58 adjustSampleFormat(m_avCodec->sample_fmts, requestedAudioFormat.sampleFormat);
59
60 m_stream->time_base = AVRational{ 1, sampleRate };
61
62 qCDebug(qLcFFmpegAudioEncoder) << "set stream time_base" << m_stream->time_base.num << "/"
63 << m_stream->time_base.den;
64}
65
66void AudioEncoder::open()
67{
68 m_codecContext.reset(avcodec_alloc_context3(m_avCodec));
69
70 if (m_stream->time_base.num != 1 || m_stream->time_base.den != m_format.sampleRate()) {
71 qCDebug(qLcFFmpegAudioEncoder) << "Most likely, av_format_write_header changed time base from"
72 << 1 << "/" << m_format.sampleRate() << "to"
73 << m_stream->time_base;
74 }
75
76 m_codecContext->time_base = m_stream->time_base;
77
78 avcodec_parameters_to_context(m_codecContext.get(), m_stream->codecpar);
79
80 AVDictionaryHolder opts;
81 applyAudioEncoderOptions(m_settings, m_avCodec->name, m_codecContext.get(), opts);
82 applyExperimentalCodecOptions(m_avCodec, opts);
83
84 int res = avcodec_open2(m_codecContext.get(), m_avCodec, opts);
85 qCDebug(qLcFFmpegAudioEncoder) << "audio codec opened" << res;
86 qCDebug(qLcFFmpegAudioEncoder) << "audio codec params: fmt=" << m_codecContext->sample_fmt
87 << "rate=" << m_codecContext->sample_rate;
88
89 const AVAudioFormat requestedAudioFormat(m_format);
90 const AVAudioFormat codecAudioFormat(m_codecContext.get());
91
92 if (requestedAudioFormat != codecAudioFormat)
93 m_resampler = createResampleContext(requestedAudioFormat, codecAudioFormat);
94}
95
97{
98 {
99 const std::chrono::microseconds bufferDuration(buffer.duration());
100 auto guard = lockLoopData();
101
102 if (m_paused)
103 return;
104
105 // TODO: apply logic with canPushFrame
106
107 m_audioBufferQueue.push(buffer);
108 m_queueDuration += bufferDuration;
109 }
110
111 dataReady();
112}
113
114QAudioBuffer AudioEncoder::takeBuffer()
115{
116 auto locker = lockLoopData();
117 QAudioBuffer result = dequeueIfPossible(m_audioBufferQueue);
118 m_queueDuration -= std::chrono::microseconds(result.duration());
119 return result;
120}
121
123{
124 open();
125
126 // TODO: try to address this dependency here.
127 if (auto input = qobject_cast<QFFmpegAudioInput *>(source()))
128 input->setFrameSize(m_codecContext->frame_size);
129
130 qCDebug(qLcFFmpegAudioEncoder) << "AudioEncoder::init started audio device thread.";
131}
132
134{
135 while (!m_audioBufferQueue.empty())
136 processOne();
137 while (avcodec_send_frame(m_codecContext.get(), nullptr) == AVERROR(EAGAIN))
138 retrievePackets();
139 retrievePackets();
140}
141
143{
144 return !m_audioBufferQueue.empty();
145}
146
147void AudioEncoder::retrievePackets()
148{
149 while (1) {
150 AVPacketUPtr packet(av_packet_alloc());
151 int ret = avcodec_receive_packet(m_codecContext.get(), packet.get());
152 if (ret < 0) {
153 if (ret != AVERROR(EOF))
154 break;
155 if (ret != AVERROR(EAGAIN)) {
156 char errStr[1024];
157 av_strerror(ret, errStr, 1024);
158 qCDebug(qLcFFmpegAudioEncoder) << "receive packet" << ret << errStr;
159 }
160 break;
161 }
162
163 // qCDebug(qLcFFmpegEncoder) << "writing audio packet" << packet->size << packet->pts <<
164 // packet->dts;
165 packet->stream_index = m_stream->id;
166 m_recordingEngine.getMuxer()->addPacket(std::move(packet));
167 }
168}
169
171{
172 QAudioBuffer buffer = takeBuffer();
173 if (!buffer.isValid())
174 return;
175
176 if (buffer.format() != m_format) {
177 // should we recreate recreate resampler here?
178 qWarning() << "Get invalid audio format:" << buffer.format() << ", expected:" << m_format;
179 return;
180 }
181
182 // qCDebug(qLcFFmpegEncoder) << "new audio buffer" << buffer.byteCount() << buffer.format()
183 // << buffer.frameCount() << codec->frame_size;
184 retrievePackets();
185
186 auto frame = makeAVFrame();
187 frame->format = m_codecContext->sample_fmt;
188#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
189 frame->channel_layout = m_codecContext->channel_layout;
190 frame->channels = m_codecContext->channels;
191#else
192 frame->ch_layout = m_codecContext->ch_layout;
193#endif
194 frame->sample_rate = m_codecContext->sample_rate;
195 frame->nb_samples = buffer.frameCount();
196 if (frame->nb_samples)
197 av_frame_get_buffer(frame.get(), 0);
198
199 if (m_resampler) {
200 const uint8_t *data = buffer.constData<uint8_t>();
201 swr_convert(m_resampler.get(), frame->extended_data, frame->nb_samples, &data,
202 frame->nb_samples);
203 } else {
204 memcpy(frame->buf[0]->data, buffer.constData<uint8_t>(), buffer.byteCount());
205 }
206
207 const auto &timeBase = m_stream->time_base;
208 const auto pts = timeBase.den && timeBase.num
209 ? timeBase.den * m_samplesWritten / (m_codecContext->sample_rate * timeBase.num)
210 : m_samplesWritten;
211 setAVFrameTime(*frame, pts, timeBase);
212 m_samplesWritten += buffer.frameCount();
213
214 qint64 time = m_format.durationForFrames(m_samplesWritten);
216
217 // qCDebug(qLcFFmpegEncoder) << "sending audio frame" << buffer.byteCount() << frame->pts <<
218 // ((double)buffer.frameCount()/frame->sample_rate);
219
220 int ret = avcodec_send_frame(m_codecContext.get(), frame.get());
221 if (ret < 0) {
222 char errStr[1024];
223 av_strerror(ret, errStr, 1024);
224 // qCDebug(qLcFFmpegEncoder) << "error sending frame" << ret << errStr;
225 }
226}
227
229{
230 if (isRunning())
231 return m_audioBufferQueue.size() <= 1 || m_queueDuration < m_maxQueueDuration;
232 if (!isFinished())
233 return m_audioBufferQueue.empty();
234
235 return false;
236}
237
238
239
240} // namespace QFFmpeg
241
\inmodule QtMultimedia
The QAudioFormat class stores audio stream parameter information.
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.
static AVCodecID codecIdForAudioCodec(QMediaFormat::AudioCodec codec)
AudioEncoder(RecordingEngine &recordingEngine, const QAudioFormat &sourceFormat, const QMediaEncoderSettings &settings)
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.
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]