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 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
109 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
110 : std::optional<AVPixelFormat>{};
111
112 qCDebug(qLcFFmpegEncoder) << "adding video source" << source->metaObject()->className() << ":"
113 << "pixelFormat=" << frameFormat.pixelFormat()
114 << "frameSize=" << frameFormat.frameSize()
115 << "frameRate=" << frameFormat.streamFrameRate()
116 << "ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
117
118 auto videoEncoder = new VideoEncoder(*this, m_settings, frameFormat, hwPixelFormat);
119 m_videoEncoders.emplace_back(videoEncoder);
120 if (m_autoStop)
121 videoEncoder->setAutoStop(true);
122
123 connect(videoEncoder, &EncoderThread::endOfSourceStream, this,
124 &RecordingEngine::handleSourceEndOfStream);
125
126 connect(videoEncoder, &EncoderThread::initialized, this,
127 &RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
128
129 // set the frame before connecting to avoid potential races
130 if (firstFrame.isValid())
131 videoEncoder->addFrame(firstFrame);
132
133 connectEncoderToSource(videoEncoder, source);
134}
135
136bool RecordingEngine::startEncoders()
137{
138 Q_ASSERT(m_state == State::FormatsInitializing);
139 Q_ASSERT(m_formatsInitializer);
140 m_formatsInitializer.reset();
141
142 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
143 emit sessionError(QMediaRecorder::ResourceError,
144 QLatin1StringView("No valid stream found for encoding"));
145 return false;
146 }
147
148 m_state = State::EncodersInitializing;
149
150 forEachEncoder([](EncoderThread *encoder) { //
151 encoder->start();
152 });
153
154 return true;
155}
156
157bool RecordingEngine::initialize(const std::vector<QAudioBufferSource *> &audioSources,
158 const std::vector<QPlatformVideoSource *> &videoSources)
159{
160 Q_ASSERT(m_state == State::None);
161
162 m_state = State::FormatsInitializing;
163 m_formatsInitializer = std::make_unique<EncodingInitializer>(*this);
164 return m_formatsInitializer->start(audioSources, videoSources);
165}
166
167RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine,
168 bool writeTrailer)
169 : m_recordingEngine(recordingEngine), m_writeTrailer(writeTrailer)
170{
171 setObjectName(QStringLiteral("EncodingFinalizer"));
172 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
173 connect(this, &QThread::finished, this, &QObject::deleteLater);
174}
175
176void RecordingEngine::EncodingFinalizer::run()
177{
178 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
179
180 m_recordingEngine.stopAndDeleteThreads();
181
182 if (m_writeTrailer) {
183 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
184 if (res < 0) {
185 qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << AVError(res);
186 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
187 QLatin1String("Cannot write trailer: ")
188 + err2str(res));
189 }
190 }
191 // else ffmpeg might crash
192
193 // close AVIO before emitting finalizationDone.
194 m_recordingEngine.m_formatContext->closeAVIO();
195
196 qCDebug(qLcFFmpegEncoder) << "Media recording finalized";
197 emit m_recordingEngine.finalizationDone();
198 auto recordingEnginePtr = &m_recordingEngine;
199 delete recordingEnginePtr;
200}
201
203{
204 qCDebug(qLcFFmpegEncoder) << "Media recording finalizing";
205
206 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
207 || m_state == State::Encoding);
208
209 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
210
211 m_formatsInitializer.reset();
212
213 forEachEncoder(&disconnectEncoderFromSource);
214 if (m_state != State::Encoding)
215 forEachEncoder(&EncoderThread::startEncoding, false);
216
217 const bool shouldWriteTrailer = m_state == State::Encoding;
218 m_state = State::Finalizing;
219
220 EncodingFinalizer *finalizer = new EncodingFinalizer(*this, shouldWriteTrailer);
221 finalizer->start();
222}
223
224void RecordingEngine::setPaused(bool paused)
225{
226 forEachEncoder(&EncoderThread::setPaused, paused);
227}
228
229void RecordingEngine::setAutoStop(bool autoStop)
230{
231 m_autoStop = autoStop;
232 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
233 handleSourceEndOfStream();
234}
235
236void RecordingEngine::setMetaData(const QMediaMetaData &metaData)
237{
238 m_metaData = metaData;
239}
240
241void RecordingEngine::newTimeStamp(qint64 time)
242{
243 QMutexLocker locker(&m_timeMutex);
244 if (time > m_timeRecorded) {
245 m_timeRecorded = time;
246 emit durationChanged(time);
247 }
248}
249
251{
252 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
253}
254
255void RecordingEngine::handleSourceEndOfStream()
256{
257 if (m_autoStop && isEndOfSourceStreams())
258 emit autoStopped();
259}
260
261void RecordingEngine::handleEncoderInitialization()
262{
263 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
264
265 if (m_state == State::Finalizing)
266 return; // outdated event, drop it
267
268 ++m_initializedEncodersCount;
269
270 Q_ASSERT(m_initializedEncodersCount <= encodersCount());
271
272 if (m_initializedEncodersCount < encodersCount())
273 return;
274
275 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
276
277 qCDebug(qLcFFmpegEncoder) << "Encoders initialized; writing header";
278
279 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
280
281 const int res = avformat_write_header(avFormatContext(), nullptr);
282 if (res < 0) {
283 qWarning() << "could not write header, error:" << res << AVError(res);
284 emit sessionError(QMediaRecorder::ResourceError,
285 QLatin1StringView("Cannot start writing the stream"));
286 return;
287 }
288
289 qCDebug(qLcFFmpegEncoder) << "Stream header is successfully written";
290
291 m_state = State::Encoding;
292 m_muxer->start();
293 forEachEncoder(&EncoderThread::startEncoding, true);
294}
295
296void RecordingEngine::stopAndDeleteThreads()
297{
298 m_audioEncoders.clear();
299 m_videoEncoders.clear();
300 m_muxer.reset();
301}
302
303template <typename F, typename... Args>
304void RecordingEngine::forEachEncoder(F &&f, Args &&...args)
305{
306 for (auto &audioEncoder : m_audioEncoders)
307 std::invoke(f, audioEncoder.get(), args...);
308 for (auto &videoEncoder : m_videoEncoders)
309 std::invoke(f, videoEncoder.get(), args...);
310}
311
312template <typename F>
313bool RecordingEngine::allOfEncoders(F &&f) const
314{
315 auto predicate = [&f](const auto &encoder) { return std::invoke(f, encoder.get()); };
316
317 return ranges::all_of(m_audioEncoders, predicate)
318 && ranges::all_of(m_videoEncoders, predicate);
319}
320} // namespace QFFmpeg
321
322QT_END_NAMESPACE
323
324#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,...)