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 m_startEncodingPromise.start();
35}
36
38{
39 Q_ASSERT(m_state == State::Finalizing);
40}
41
42void RecordingEngine::addAudioInput(QFFmpegAudioInput *input)
43{
44 Q_ASSERT(input);
45 Q_ASSERT(m_state == State::FormatsInitializing);
46
47 if (input->device.isNull()) {
48 emit streamInitializationError(QMediaRecorder::ResourceError,
49 QLatin1StringView("Audio device is null"));
50 return;
51 }
52
53 const QAudioFormat format = input->device.preferredFormat();
54
55 if (!format.isValid()) {
56 emit streamInitializationError(
57 QMediaRecorder::FormatError,
58 QLatin1StringView("Audio device has invalid preferred format"));
59 return;
60 }
61
62 AudioEncoder *audioEncoder = createAudioEncoder(format);
63 connectEncoderToSource(audioEncoder, input);
64}
65
66void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
67 const QAudioBuffer &firstBuffer)
68{
69 Q_ASSERT(input);
70 Q_ASSERT(m_state == State::FormatsInitializing);
71
72 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
73
74 AudioEncoder *audioEncoder = createAudioEncoder(format);
75
76 // set the buffer before connecting to avoid potential races
77 if (firstBuffer.isValid())
78 audioEncoder->addBuffer(firstBuffer);
79
80 connectEncoderToSource(audioEncoder, input);
81}
82
83AudioEncoder *RecordingEngine::createAudioEncoder(const QAudioFormat &format)
84{
85 Q_ASSERT(format.isValid());
86
87 auto audioEncoder = new AudioEncoder(*this, format, m_settings);
88
89 m_audioEncoders.emplace_back(audioEncoder);
90 connect(audioEncoder, &EncoderThread::endOfSourceStream, this,
91 &RecordingEngine::handleSourceEndOfStream);
92 if (m_autoStop)
93 audioEncoder->setAutoStop(true);
94
95 return audioEncoder;
96}
97
98void RecordingEngine::addVideoSource(QPlatformVideoSource *source, const QVideoFrame &firstFrame)
99{
100 Q_ASSERT(m_state == State::FormatsInitializing);
101
102 QVideoFrameFormat frameFormat =
103 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
104
105 Q_ASSERT(frameFormat.isValid());
106
107 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
108 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
109 : std::optional<AVPixelFormat>{};
110
111 qCDebug(qLcFFmpegEncoder) << "adding video source" << source->metaObject()->className() << ":"
112 << "pixelFormat=" << frameFormat.pixelFormat()
113 << "frameSize=" << frameFormat.frameSize()
114 << "frameRate=" << frameFormat.streamFrameRate()
115 << "ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
116
117 auto videoEncoder = new VideoEncoder(*this, m_settings, frameFormat, hwPixelFormat);
118 m_videoEncoders.emplace_back(videoEncoder);
119 if (m_autoStop)
120 videoEncoder->setAutoStop(true);
121
122 connect(videoEncoder, &EncoderThread::endOfSourceStream, this,
123 &RecordingEngine::handleSourceEndOfStream);
124
125 // set the frame before connecting to avoid potential races
126 if (firstFrame.isValid())
127 videoEncoder->addFrame(firstFrame);
128
129 connectEncoderToSource(videoEncoder, source);
130}
131
132bool RecordingEngine::startEncoders()
133{
134 Q_ASSERT(m_state == State::FormatsInitializing);
135 Q_ASSERT(m_formatsInitializer);
136 m_formatsInitializer.reset();
137
138 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
139 emit sessionError(QMediaRecorder::ResourceError,
140 QLatin1StringView("No valid stream found for encoding"));
141 return false;
142 }
143
144 m_state = State::EncodersInitializing;
145
146 std::vector<QFuture<bool>> resolutions;
147 forEachEncoder([&resolutions](EncoderThread *encoder) { //
148 encoder->start();
149 resolutions.push_back(encoder->resolvedFuture());
150 });
151
152 QtFuture::whenAll(resolutions.begin(), resolutions.end())
153 .then(this, [this](const QList<QFuture<bool>> &resolutions) {
154 handleEncodersResolved(resolutions);
155 });
156
157 return true;
158}
159
160bool RecordingEngine::initialize(const std::vector<QAudioBufferSource *> &audioSources,
161 const std::vector<QPlatformVideoSource *> &videoSources)
162{
163 Q_ASSERT(m_state == State::None);
164
165 m_state = State::FormatsInitializing;
166 m_formatsInitializer = std::make_unique<EncodingInitializer>(*this);
167 return m_formatsInitializer->start(audioSources, videoSources);
168}
169
170RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine)
171 : m_recordingEngine(recordingEngine)
172{
173 setObjectName(QStringLiteral("EncodingFinalizer"));
174 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
175 connect(this, &QThread::finished, this, &QObject::deleteLater);
176}
177
178void RecordingEngine::EncodingFinalizer::run()
179{
180 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
181
182 // Block until handleEncodersResolved() (or finalize(), if the encoders had
183 // already all resolved) has finished with the encoders
184 const bool headerWritten = m_recordingEngine.waitForEncodingStart();
185
186 m_recordingEngine.stopAndDeleteThreads();
187
188 if (headerWritten) {
189 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
190 if (res < 0) {
191 qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << AVError(res);
192 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
193 QLatin1String("Cannot write trailer: ")
194 + err2str(res));
195 }
196 }
197 // else ffmpeg might crash
198
199 // close AVIO before emitting finalizationDone.
200 m_recordingEngine.m_formatContext->closeAVIO();
201
202 qCDebug(qLcFFmpegEncoder) << "Media recording finalized";
203 emit m_recordingEngine.finalizationDone();
204 auto recordingEnginePtr = &m_recordingEngine;
205 delete recordingEnginePtr;
206}
207
209{
210 qCDebug(qLcFFmpegEncoder) << "Media recording finalizing";
211
212 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
213 || m_state == State::Encoding);
214
215 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
216
217 if (m_state == State::FormatsInitializing)
218 markEncodingStart(false);
219
220 m_formatsInitializer.reset();
221
222 forEachEncoder(&disconnectEncoderFromSource);
223
224 m_state = State::Finalizing;
225
226 m_finalizer = new EncodingFinalizer(*this);
227 m_finalizer->start();
228}
229
230void RecordingEngine::setPaused(bool paused)
231{
232 forEachEncoder(&EncoderThread::setPaused, paused);
233}
234
235void RecordingEngine::setAutoStop(bool autoStop)
236{
237 m_autoStop = autoStop;
238 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
239 handleSourceEndOfStream();
240}
241
242void RecordingEngine::setMetaData(const QMediaMetaData &metaData)
243{
244 m_metaData = metaData;
245}
246
247void RecordingEngine::newTimeStamp(qint64 time)
248{
249 QMutexLocker locker(&m_timeMutex);
250 if (time > m_timeRecorded) {
251 m_timeRecorded = time;
252 emit durationChanged(time);
253 }
254}
255
257{
258 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
259}
260
261void RecordingEngine::handleSourceEndOfStream()
262{
263 if (m_autoStop && isEndOfSourceStreams())
264 emit autoStopped();
265}
266
267void RecordingEngine::handleEncodersResolved(const QList<QFuture<bool>> &resolutions)
268{
269 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
270
271 bool allEncodersSucceeded = ranges::all_of(resolutions,
272 [](const QFuture<bool> &future) {
273 return future.result();
274 });
275
276 if (allEncodersSucceeded) {
277 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
278
279 qCDebug(qLcFFmpegEncoder) << "Encoders initialized; writing header";
280
281 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
282
283 const int res = avformat_write_header(avFormatContext(), nullptr);
284 if (res < 0) {
285 qWarning() << "could not write header, error:" << res << AVError(res);
286 allEncodersSucceeded = false;
287 emit sessionError(QMediaRecorder::ResourceError,
288 QStringLiteral("Cannot start writing the stream"));
289 } else {
290 qCDebug(qLcFFmpegEncoder) << "Stream header is successfully written";
291 // finalize() may already have moved m_state to Finalizing, don't go backwards
292 if (m_state == State::EncodersInitializing)
293 m_state = State::Encoding;
294 m_muxer->start();
295 }
296 }
297
298 // Unblock encoders in init()
299 markEncodingStart(allEncodersSucceeded);
300}
301
302void RecordingEngine::stopAndDeleteThreads()
303{
304 m_audioEncoders.clear();
305 m_videoEncoders.clear();
306 m_muxer.reset();
307}
308
309void RecordingEngine::markEncodingStart(bool startOk)
310{
311 m_startEncodingPromise.addResult(startOk);
312 m_startEncodingPromise.finish();
313}
314
315template <typename F, typename... Args>
316void RecordingEngine::forEachEncoder(F &&f, Args &&...args)
317{
318 for (auto &audioEncoder : m_audioEncoders)
319 std::invoke(f, audioEncoder.get(), args...);
320 for (auto &videoEncoder : m_videoEncoders)
321 std::invoke(f, videoEncoder.get(), args...);
322}
323
324template <typename F>
325bool RecordingEngine::allOfEncoders(F &&f) const
326{
327 auto predicate = [&f](const auto &encoder) { return std::invoke(f, encoder.get()); };
328
329 return ranges::all_of(m_audioEncoders, predicate)
330 && ranges::all_of(m_videoEncoders, predicate);
331}
332
333} // namespace QFFmpeg
334
335QT_END_NAMESPACE
336
337#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)
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
void disconnectEncoderFromSource(EncoderThread *encoder)
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)