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
qffmpegrecordingengine.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
8
9#include "private/qmultimediautils_p.h"
10#include "private/qplatformaudiobufferinput_p.h"
11#include "private/qplatformvideosource_p.h"
12#include "private/qplatformvideoframeinput_p.h"
13
14#include "qdebug.h"
17#include "qffmpegmuxer_p.h"
19
21namespace ranges = QtMultimediaPrivate::ranges;
22
23Q_STATIC_LOGGING_CATEGORY(qLcFFmpegEncoder, "qt.multimedia.ffmpeg.encoder");
24
25namespace QFFmpeg
26{
27
28RecordingEngine::RecordingEngine(const QMediaEncoderSettings &settings,
29 std::unique_ptr<EncodingFormatContext> context)
30 : m_settings(settings), m_formatContext(std::move(context)), m_muxer(new Muxer(this))
31{
32 Q_ASSERT(m_formatContext);
33 Q_ASSERT(m_formatContext->isAVIOOpen());
34}
35
37{
38 Q_ASSERT(m_state == State::Finalizing);
39}
40
41void RecordingEngine::addAudioInput(QFFmpegAudioInput *input)
42{
43 Q_ASSERT(input);
44 Q_ASSERT(m_state == State::FormatsInitializing);
45
46 if (input->device.isNull()) {
47 emit streamInitializationError(QMediaRecorder::ResourceError,
48 QLatin1StringView("Audio device is null"));
49 return;
50 }
51
52 const QAudioFormat format = input->device.preferredFormat();
53
54 if (!format.isValid()) {
55 emit streamInitializationError(
56 QMediaRecorder::FormatError,
57 QLatin1StringView("Audio device has invalid preferred format"));
58 return;
59 }
60
61 AudioEncoder *audioEncoder = createAudioEncoder(format);
62 connectEncoderToSource(audioEncoder, input);
63}
64
65void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
66 const QAudioBuffer &firstBuffer)
67{
68 Q_ASSERT(input);
69 Q_ASSERT(m_state == State::FormatsInitializing);
70
71 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
72
73 AudioEncoder *audioEncoder = createAudioEncoder(format);
74
75 // set the buffer before connecting to avoid potential races
76 if (firstBuffer.isValid())
77 audioEncoder->addBuffer(firstBuffer);
78
79 connectEncoderToSource(audioEncoder, input);
80}
81
82AudioEncoder *RecordingEngine::createAudioEncoder(const QAudioFormat &format)
83{
84 Q_ASSERT(format.isValid());
85
86 auto audioEncoder = new AudioEncoder(*this, format, m_settings);
87
88 m_audioEncoders.emplace_back(audioEncoder);
89 connect(audioEncoder, &EncoderThread::endOfSourceStream, this,
90 &RecordingEngine::handleSourceEndOfStream);
91 connect(audioEncoder, &EncoderThread::initialized, this,
92 &RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
93 if (m_autoStop)
94 audioEncoder->setAutoStop(true);
95
96 return audioEncoder;
97}
98
99void RecordingEngine::addVideoSource(QPlatformVideoSource *source, const QVideoFrame &firstFrame)
100{
101 Q_ASSERT(m_state == State::FormatsInitializing);
102
103 QVideoFrameFormat frameFormat =
104 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
105
106 Q_ASSERT(frameFormat.isValid());
107
108 if (firstFrame.isValid() && frameFormat.streamFrameRate() <= 0.f) {
109 const qint64 startTime = firstFrame.startTime();
110 const qint64 endTime = firstFrame.endTime();
111 if (startTime != -1 && endTime > startTime)
112 frameFormat.setStreamFrameRate(static_cast<qreal>(VideoFrameTimeBase)
113 / (endTime - startTime));
114 }
115
116 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
117 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
118 : std::optional<AVPixelFormat>{};
119
120 qCDebug(qLcFFmpegEncoder) << "adding video source" << source->metaObject()->className() << ":"
121 << "pixelFormat=" << frameFormat.pixelFormat()
122 << "frameSize=" << frameFormat.frameSize()
123 << "frameRate=" << frameFormat.streamFrameRate()
124 << "ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
125
126 auto videoEncoder = new VideoEncoder(*this, m_settings, frameFormat, hwPixelFormat);
127 m_videoEncoders.emplace_back(videoEncoder);
128 if (m_autoStop)
129 videoEncoder->setAutoStop(true);
130
131 connect(videoEncoder, &EncoderThread::endOfSourceStream, this,
132 &RecordingEngine::handleSourceEndOfStream);
133
134 connect(videoEncoder, &EncoderThread::initialized, this,
135 &RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
136
137 // set the frame before connecting to avoid potential races
138 if (firstFrame.isValid())
139 videoEncoder->addFrame(firstFrame);
140
141 connectEncoderToSource(videoEncoder, source);
142}
143
144bool RecordingEngine::startEncoders()
145{
146 Q_ASSERT(m_state == State::FormatsInitializing);
147 Q_ASSERT(m_formatsInitializer);
148 m_formatsInitializer.reset();
149
150 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
151 emit sessionError(QMediaRecorder::ResourceError,
152 QLatin1StringView("No valid stream found for encoding"));
153 return false;
154 }
155
156 m_state = State::EncodersInitializing;
157
158 forEachEncoder([](EncoderThread *encoder) { //
159 encoder->start();
160 });
161
162 return true;
163}
164
165bool RecordingEngine::initialize(const std::vector<QAudioBufferSource *> &audioSources,
166 const std::vector<QPlatformVideoSource *> &videoSources)
167{
168 Q_ASSERT(m_state == State::None);
169
170 m_state = State::FormatsInitializing;
171 m_formatsInitializer = std::make_unique<EncodingInitializer>(*this);
172 return m_formatsInitializer->start(audioSources, videoSources);
173}
174
175RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine,
176 bool writeTrailer)
177 : m_recordingEngine(recordingEngine), m_writeTrailer(writeTrailer)
178{
179 setObjectName(QStringLiteral("EncodingFinalizer"));
180 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
181 connect(this, &QThread::finished, this, &QObject::deleteLater);
182}
183
184void RecordingEngine::EncodingFinalizer::run()
185{
186 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
187
188 m_recordingEngine.stopAndDeleteThreads();
189
190 if (m_writeTrailer) {
191 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
192 if (res < 0) {
193 qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << AVError(res);
194 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
195 QLatin1String("Cannot write trailer: ")
196 + err2str(res));
197 }
198 }
199 // else ffmpeg might crash
200
201 // close AVIO before emitting finalizationDone.
202 m_recordingEngine.m_formatContext->closeAVIO();
203
204 qCDebug(qLcFFmpegEncoder) << "Media recording finalized";
205 emit m_recordingEngine.finalizationDone();
206 auto recordingEnginePtr = &m_recordingEngine;
207 delete recordingEnginePtr;
208}
209
211{
212 qCDebug(qLcFFmpegEncoder) << "Media recording finalizing";
213
214 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
215 || m_state == State::Encoding);
216
217 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
218
219 m_formatsInitializer.reset();
220
221 forEachEncoder(&disconnectEncoderFromSource);
222 if (m_state != State::Encoding)
223 forEachEncoder(&EncoderThread::startEncoding, false);
224
225 const bool shouldWriteTrailer = m_state == State::Encoding;
226 m_state = State::Finalizing;
227
228 EncodingFinalizer *finalizer = new EncodingFinalizer(*this, shouldWriteTrailer);
229 finalizer->start();
230}
231
232void RecordingEngine::setPaused(bool paused)
233{
234 forEachEncoder(&EncoderThread::setPaused, paused);
235}
236
237void RecordingEngine::setAutoStop(bool autoStop)
238{
239 m_autoStop = autoStop;
240 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
241 handleSourceEndOfStream();
242}
243
244void RecordingEngine::setMetaData(const QMediaMetaData &metaData)
245{
246 m_metaData = metaData;
247}
248
249void RecordingEngine::newTimeStamp(qint64 time)
250{
251 QMutexLocker locker(&m_timeMutex);
252 if (time > m_timeRecorded) {
253 m_timeRecorded = time;
254 emit durationChanged(time);
255 }
256}
257
259{
260 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
261}
262
263void RecordingEngine::handleSourceEndOfStream()
264{
265 if (m_autoStop && isEndOfSourceStreams())
266 emit autoStopped();
267}
268
269void RecordingEngine::handleEncoderInitialization()
270{
271 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
272
273 if (m_state == State::Finalizing)
274 return; // outdated event, drop it
275
276 ++m_initializedEncodersCount;
277
278 Q_ASSERT(m_initializedEncodersCount <= encodersCount());
279
280 if (m_initializedEncodersCount < encodersCount())
281 return;
282
283 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
284
285 qCDebug(qLcFFmpegEncoder) << "Encoders initialized; writing header";
286
287 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
288
289 const int res = avformat_write_header(avFormatContext(), nullptr);
290 if (res < 0) {
291 qWarning() << "could not write header, error:" << res << AVError(res);
292 emit sessionError(QMediaRecorder::ResourceError,
293 QLatin1StringView("Cannot start writing the stream"));
294 return;
295 }
296
297 qCDebug(qLcFFmpegEncoder) << "Stream header is successfully written";
298
299 m_state = State::Encoding;
300 m_muxer->start();
301 forEachEncoder(&EncoderThread::startEncoding, true);
302}
303
304void RecordingEngine::stopAndDeleteThreads()
305{
306 m_audioEncoders.clear();
307 m_videoEncoders.clear();
308 m_muxer.reset();
309}
310
311template <typename F, typename... Args>
312void RecordingEngine::forEachEncoder(F &&f, Args &&...args)
313{
314 for (auto &audioEncoder : m_audioEncoders)
315 std::invoke(f, audioEncoder.get(), args...);
316 for (auto &videoEncoder : m_videoEncoders)
317 std::invoke(f, videoEncoder.get(), args...);
318}
319
320template <typename F>
321bool RecordingEngine::allOfEncoders(F &&f) const
322{
323 auto predicate = [&f](const auto &encoder) { return std::invoke(f, encoder.get()); };
324
325 return ranges::all_of(m_audioEncoders, predicate)
326 && ranges::all_of(m_videoEncoders, predicate);
327}
328} // namespace QFFmpeg
329
330QT_END_NAMESPACE
331
332#include "moc_qffmpegrecordingengine_p.cpp"
The QAudioFormat class stores audio stream parameter information.
bool initialize(const std::vector< QAudioBufferSource * > &audioSources, const std::vector< QPlatformVideoSource * > &videoSources)
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
void disconnectEncoderFromSource(EncoderThread *encoder)
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)