9#include "private/qmultimediautils_p.h"
10#include "private/qplatformaudiobufferinput_p.h"
11#include "private/qplatformvideosource_p.h"
12#include "private/qplatformvideoframeinput_p.h"
21#include "libavutil/opt.h"
25namespace ranges = QtMultimediaPrivate::ranges;
34bool supportsNegativeCtsOffsets(
const AVOutputFormat *format)
36 if (!format || !format->priv_class)
41 const AVClass *avClass = format->priv_class;
42 return av_opt_find(
static_cast<
void *>(&avClass),
"negative_cts_offsets",
"movflags", 0, 0)
46AVDictionaryHolder muxerOptions(
const AVFormatContext *formatContext)
48 AVDictionaryHolder options;
50 if (supportsNegativeCtsOffsets(formatContext->oformat)) {
53 av_dict_set(options,
"movflags",
"+negative_cts_offsets", 0);
55 av_dict_set(options,
"avoid_negative_ts",
"disabled", 0);
64 std::unique_ptr<EncodingFormatContext> context)
65 : m_settings(settings), m_formatContext(std::move(context)), m_muxer(
new Muxer(
this))
67 Q_ASSERT(m_formatContext);
68 Q_ASSERT(m_formatContext->isAVIOOpen());
69 m_startEncodingPromise.start();
74 Q_ASSERT(m_state == State::Finalizing);
80 Q_ASSERT(m_state == State::FormatsInitializing);
82 if (input->device.isNull()) {
83 emit streamInitializationError(QMediaRecorder::ResourceError,
84 QLatin1StringView(
"Audio device is null"));
88 const QAudioFormat format = input->device.preferredFormat();
90 if (!format.isValid()) {
91 emit streamInitializationError(
92 QMediaRecorder::FormatError,
93 QLatin1StringView(
"Audio device has invalid preferred format"));
98 connectEncoderToSource(audioEncoder, input);
101void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
102 const QAudioBuffer &firstBuffer)
105 Q_ASSERT(m_state == State::FormatsInitializing);
107 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
109 AudioEncoder *audioEncoder = createAudioEncoder(format);
112 if (firstBuffer.isValid())
113 audioEncoder->addBuffer(firstBuffer);
115 connectEncoderToSource(audioEncoder, input);
120 Q_ASSERT(format.isValid());
122 auto audioEncoder =
new AudioEncoder(*
this, format, m_settings);
124 m_audioEncoders.emplace_back(audioEncoder);
125 connect(audioEncoder, &EncoderThread::endOfSourceStream,
this,
128 audioEncoder->setAutoStop(
true);
133void RecordingEngine::addVideoSource(QPlatformVideoSource *source,
const QVideoFrame &firstFrame)
135 Q_ASSERT(m_state == State::FormatsInitializing);
137 QVideoFrameFormat frameFormat =
138 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
140 Q_ASSERT(frameFormat.isValid());
142 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
143 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
144 : std::optional<AVPixelFormat>{};
146 qCDebug(qLcFFmpegEncoder) <<
"adding video source" << source->metaObject()->className() <<
":"
147 <<
"pixelFormat=" << frameFormat.pixelFormat()
148 <<
"frameSize=" << frameFormat.frameSize()
149 <<
"frameRate=" << frameFormat.streamFrameRate()
150 <<
"ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
152 auto videoEncoder =
new VideoEncoder(*
this, m_settings, frameFormat, hwPixelFormat);
153 m_videoEncoders.emplace_back(videoEncoder);
155 videoEncoder->setAutoStop(
true);
157 connect(videoEncoder, &EncoderThread::endOfSourceStream,
this,
161 if (firstFrame.isValid())
162 videoEncoder->addFrame(firstFrame);
164 connectEncoderToSource(videoEncoder, source);
169 Q_ASSERT(m_state == State::FormatsInitializing);
170 Q_ASSERT(m_formatsInitializer);
171 m_formatsInitializer.reset();
173 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
174 emit sessionError(QMediaRecorder::ResourceError,
175 QLatin1StringView(
"No valid stream found for encoding"));
179 m_state = State::EncodersInitializing;
181 std::vector<QFuture<
bool>> resolutions;
184 resolutions.push_back(encoder->resolvedFuture());
187 QtFuture::whenAll(resolutions.begin(), resolutions.end())
188 .then(
this, [
this](
const QList<QFuture<
bool>> &resolutions) {
189 handleEncodersResolved(resolutions);
196 const std::vector<QPlatformVideoSource *> &videoSources)
198 Q_ASSERT(m_state == State::None);
200 m_state = State::FormatsInitializing;
201 m_formatsInitializer = std::make_unique<EncodingInitializer>(*
this);
202 return m_formatsInitializer->start(audioSources, videoSources);
206 : m_recordingEngine(recordingEngine)
208 setObjectName(QStringLiteral(
"EncodingFinalizer"));
209 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
210 connect(
this, &QThread::finished,
this, &QObject::deleteLater);
215 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
221 m_recordingEngine.stopAndDeleteThreads();
224 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
226 qCWarning(qLcFFmpegEncoder) <<
"could not write trailer" << res << AVError(res);
227 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
228 QLatin1String(
"Cannot write trailer: ")
235 m_recordingEngine.m_formatContext->closeAVIO();
237 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalized";
238 emit m_recordingEngine.finalizationDone();
239 m_recordingEngine.deleteLater();
244 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalizing";
246 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
247 || m_state == State::Encoding);
249 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
251 if (m_state == State::FormatsInitializing)
252 markEncodingStart(
false);
254 m_formatsInitializer.reset();
258 m_state = State::Finalizing;
260 m_finalizer =
new EncodingFinalizer(*
this);
261 m_finalizer->start();
266 forEachEncoder(&EncoderThread::setPaused, paused);
271 m_autoStop = autoStop;
272 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
273 handleSourceEndOfStream();
278 m_metaData = metaData;
283 QMutexLocker locker(&m_timeMutex);
284 if (time > m_timeRecorded) {
285 m_timeRecorded = time;
286 emit durationChanged(time);
292 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
301void RecordingEngine::handleEncodersResolved(
const QList<QFuture<
bool>> &resolutions)
303 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
305 bool allEncodersSucceeded = ranges::all_of(resolutions,
306 [](
const QFuture<
bool> &future) {
307 return future.result();
310 if (allEncodersSucceeded) {
311 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
313 qCDebug(qLcFFmpegEncoder) <<
"Encoders initialized; writing header";
315 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
317 AVDictionaryHolder options = muxerOptions(avFormatContext());
318 const int res = avformat_write_header(avFormatContext(), options);
320 qWarning() <<
"could not write header, error:" << res << AVError(res);
321 allEncodersSucceeded =
false;
322 emit sessionError(QMediaRecorder::ResourceError,
323 QStringLiteral(
"Cannot start writing the stream"));
325 qCDebug(qLcFFmpegEncoder) <<
"Stream header is successfully written";
327 if (m_state == State::EncodersInitializing)
328 m_state = State::Encoding;
334 markEncodingStart(allEncodersSucceeded);
339 m_audioEncoders.clear();
340 m_videoEncoders.clear();
346 m_startEncodingPromise.addResult(startOk);
347 m_startEncodingPromise.finish();
350template <
typename F,
typename... Args>
353 for (
auto &audioEncoder : m_audioEncoders)
354 std::invoke(f, audioEncoder.get(), args...);
355 for (
auto &videoEncoder : m_videoEncoders)
356 std::invoke(f, videoEncoder.get(), args...);
362 auto predicate = [&f](
const auto &encoder) {
return std::invoke(f, encoder.get()); };
364 return ranges::all_of(m_audioEncoders, predicate)
365 && ranges::all_of(m_videoEncoders, predicate);
372#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)
void disconnectEncoderFromSource(EncoderThread *encoder)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)