9#include "private/qmultimediautils_p.h"
10#include "private/qplatformaudiobufferinput_p.h"
11#include "private/qplatformvideosource_p.h"
12#include "private/qplatformvideoframeinput_p.h"
21namespace ranges = QtMultimediaPrivate::ranges;
29 std::unique_ptr<EncodingFormatContext> context)
30 : m_settings(settings), m_formatContext(std::move(context)), m_muxer(
new Muxer(
this))
32 Q_ASSERT(m_formatContext);
33 Q_ASSERT(m_formatContext->isAVIOOpen());
34 m_startEncodingPromise.start();
39 Q_ASSERT(m_state == State::Finalizing);
45 Q_ASSERT(m_state == State::FormatsInitializing);
47 if (input->device.isNull()) {
48 emit streamInitializationError(QMediaRecorder::ResourceError,
49 QLatin1StringView(
"Audio device is null"));
53 const QAudioFormat format = input->device.preferredFormat();
55 if (!format.isValid()) {
56 emit streamInitializationError(
57 QMediaRecorder::FormatError,
58 QLatin1StringView(
"Audio device has invalid preferred format"));
63 connectEncoderToSource(audioEncoder, input);
66void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
67 const QAudioBuffer &firstBuffer)
70 Q_ASSERT(m_state == State::FormatsInitializing);
72 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
77 if (firstBuffer.isValid())
78 audioEncoder->addBuffer(firstBuffer);
80 connectEncoderToSource(audioEncoder, input);
85 Q_ASSERT(format.isValid());
87 auto audioEncoder =
new AudioEncoder(*
this, format, m_settings);
89 m_audioEncoders.emplace_back(audioEncoder);
90 connect(audioEncoder, &EncoderThread::endOfSourceStream,
this,
93 audioEncoder->setAutoStop(
true);
98void RecordingEngine::addVideoSource(QPlatformVideoSource *source,
const QVideoFrame &firstFrame)
100 Q_ASSERT(m_state == State::FormatsInitializing);
102 QVideoFrameFormat frameFormat =
103 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
105 Q_ASSERT(frameFormat.isValid());
107 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
108 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
109 : std::optional<AVPixelFormat>{};
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);
117 auto videoEncoder =
new VideoEncoder(*
this, m_settings, frameFormat, hwPixelFormat);
118 m_videoEncoders.emplace_back(videoEncoder);
120 videoEncoder->setAutoStop(
true);
122 connect(videoEncoder, &EncoderThread::endOfSourceStream,
this,
126 if (firstFrame.isValid())
127 videoEncoder->addFrame(firstFrame);
129 connectEncoderToSource(videoEncoder, source);
134 Q_ASSERT(m_state == State::FormatsInitializing);
135 Q_ASSERT(m_formatsInitializer);
136 m_formatsInitializer.reset();
138 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
139 emit sessionError(QMediaRecorder::ResourceError,
140 QLatin1StringView(
"No valid stream found for encoding"));
144 m_state = State::EncodersInitializing;
146 std::vector<QFuture<
bool>> resolutions;
149 resolutions.push_back(encoder->resolvedFuture());
152 QtFuture::whenAll(resolutions.begin(), resolutions.end())
153 .then(
this, [
this](
const QList<QFuture<
bool>> &resolutions) {
154 handleEncodersResolved(resolutions);
161 const std::vector<QPlatformVideoSource *> &videoSources)
163 Q_ASSERT(m_state == State::None);
165 m_state = State::FormatsInitializing;
166 m_formatsInitializer = std::make_unique<EncodingInitializer>(*
this);
167 return m_formatsInitializer->start(audioSources, videoSources);
171 : m_recordingEngine(recordingEngine)
173 setObjectName(QStringLiteral(
"EncodingFinalizer"));
174 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
175 connect(
this, &QThread::finished,
this, &QObject::deleteLater);
180 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
186 m_recordingEngine.stopAndDeleteThreads();
189 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
191 qCWarning(qLcFFmpegEncoder) <<
"could not write trailer" << res << AVError(res);
192 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
193 QLatin1String(
"Cannot write trailer: ")
200 m_recordingEngine.m_formatContext->closeAVIO();
202 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalized";
203 emit m_recordingEngine.finalizationDone();
204 auto recordingEnginePtr = &m_recordingEngine;
205 delete recordingEnginePtr;
210 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalizing";
212 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
213 || m_state == State::Encoding);
215 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
217 if (m_state == State::FormatsInitializing)
218 markEncodingStart(
false);
220 m_formatsInitializer.reset();
224 m_state = State::Finalizing;
226 m_finalizer =
new EncodingFinalizer(*
this);
227 m_finalizer->start();
232 forEachEncoder(&EncoderThread::setPaused, paused);
237 m_autoStop = autoStop;
238 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
239 handleSourceEndOfStream();
244 m_metaData = metaData;
249 QMutexLocker locker(&m_timeMutex);
250 if (time > m_timeRecorded) {
251 m_timeRecorded = time;
252 emit durationChanged(time);
258 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
267void RecordingEngine::handleEncodersResolved(
const QList<QFuture<
bool>> &resolutions)
269 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
271 bool allEncodersSucceeded = ranges::all_of(resolutions,
272 [](
const QFuture<
bool> &future) {
273 return future.result();
276 if (allEncodersSucceeded) {
277 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
279 qCDebug(qLcFFmpegEncoder) <<
"Encoders initialized; writing header";
281 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
283 const int res = avformat_write_header(avFormatContext(),
nullptr);
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"));
290 qCDebug(qLcFFmpegEncoder) <<
"Stream header is successfully written";
292 if (m_state == State::EncodersInitializing)
293 m_state = State::Encoding;
299 markEncodingStart(allEncodersSucceeded);
304 m_audioEncoders.clear();
305 m_videoEncoders.clear();
311 m_startEncodingPromise.addResult(startOk);
312 m_startEncodingPromise.finish();
315template <
typename F,
typename... Args>
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...);
327 auto predicate = [&f](
const auto &encoder) {
return std::invoke(f, encoder.get()); };
329 return ranges::all_of(m_audioEncoders, predicate)
330 && ranges::all_of(m_videoEncoders, predicate);
337#include "moc_qffmpegrecordingengine_p.cpp"
bool isEndOfSourceStreams() const
bool initialize(const std::vector< QAudioBufferSource * > &audioSources, const std::vector< QPlatformVideoSource * > &videoSources)
~RecordingEngine() override
bool waitForEncodingStart() const
void setAutoStop(bool autoStop)
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,...)