11#include "private/qmultimediautils_p.h"
12#include "private/qplatformaudiobufferinput_p.h"
13#include "private/qplatformvideosource_p.h"
14#include "private/qplatformvideoframeinput_p.h"
23#include "libavutil/opt.h"
27namespace ranges = QtMultimediaPrivate::ranges;
36bool supportsNegativeCtsOffsets(
const AVOutputFormat *format)
38 if (!format || !format->priv_class)
43 const AVClass *avClass = format->priv_class;
44 return av_opt_find(
static_cast<
void *>(&avClass),
"negative_cts_offsets",
"movflags", 0, 0)
48AVDictionaryHolder muxerOptions(
const AVFormatContext *formatContext)
50 AVDictionaryHolder options;
52 if (supportsNegativeCtsOffsets(formatContext->oformat)) {
55 av_dict_set(options,
"movflags",
"+negative_cts_offsets", 0);
57 av_dict_set(options,
"avoid_negative_ts",
"disabled", 0);
68 std::unique_ptr<EncodingFormatContext> context)
69 : m_settings(settings), m_formatContext(std::move(context)), m_muxer(
new Muxer(
this))
71 Q_ASSERT(m_formatContext);
72 Q_ASSERT(m_formatContext->isAVIOOpen());
73 m_startEncodingPromise.start();
78 Q_ASSERT(m_state == State::Finalizing);
84 Q_ASSERT(m_state == State::FormatsInitializing);
86 if (input->device.isNull()) {
87 Q_EMIT streamInitializationError(QMediaRecorder::ResourceError,
88 QLatin1StringView(
"Audio device is null"));
92 const QAudioFormat format = input->device.preferredFormat();
95 Q_EMIT streamInitializationError(
96 QMediaRecorder::FormatError,
97 QLatin1StringView(
"Audio device has invalid preferred format"));
101 AudioEncoder *audioEncoder = createAudioEncoder(format);
105 connectEncoderToSource(audioEncoder, input);
108void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
109 const QAudioBuffer &firstBuffer)
112 Q_ASSERT(m_state == State::FormatsInitializing);
114 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
116 AudioEncoder *audioEncoder = createAudioEncoder(format);
121 if (firstBuffer.isValid())
122 audioEncoder->addBuffer(firstBuffer);
124 connectEncoderToSource(audioEncoder, input);
131 const AVCodecID codecID = QFFmpegMediaFormatInfo::codecIdForAudioCodec(m_settings.audioCodec());
132 if (codecID == AV_CODEC_ID_NONE) {
133 Q_EMIT streamInitializationError(QMediaRecorder::FormatError,
134 u"No audio encoder available for the selected format"_s);
138 auto audioEncoder =
new AudioEncoder(*
this, format, m_settings);
140 m_audioEncoders.emplace_back(audioEncoder);
141 connect(audioEncoder, &EncoderThread::endOfSourceStream,
this,
144 audioEncoder->setAutoStop(
true);
149void RecordingEngine::addVideoSource(QPlatformVideoSource *source,
const QVideoFrame &firstFrame)
151 Q_ASSERT(m_state == State::FormatsInitializing);
153 QVideoFrameFormat frameFormat =
154 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
156 Q_ASSERT(frameFormat.isValid());
158 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
159 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
160 : std::optional<AVPixelFormat>{};
162 qCDebug(qLcFFmpegEncoder) <<
"adding video source" << source->metaObject()->className() <<
":"
163 <<
"pixelFormat=" << frameFormat.pixelFormat()
164 <<
"frameSize=" << frameFormat.frameSize()
165 <<
"frameRate=" << frameFormat.streamFrameRate()
166 <<
"ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
168 auto videoEncoder =
new VideoEncoder(*
this, m_settings, frameFormat, hwPixelFormat);
169 m_videoEncoders.emplace_back(videoEncoder);
171 videoEncoder->setAutoStop(
true);
173 connect(videoEncoder, &EncoderThread::endOfSourceStream,
this,
177 if (firstFrame.isValid())
178 videoEncoder->addFrame(firstFrame);
180 connectEncoderToSource(videoEncoder, source);
185 Q_ASSERT(m_state == State::FormatsInitializing);
186 Q_ASSERT(m_formatsInitializer);
188 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
189 Q_EMIT sessionError(QMediaRecorder::ResourceError,
190 QLatin1StringView(
"No valid stream found for encoding"));
194 m_formatsInitializer.reset();
195 m_state = State::EncodersInitializing;
197 std::vector<QFuture<
bool>> resolutions;
200 resolutions.push_back(encoder->resolvedFuture());
203 QtFuture::whenAll(resolutions.begin(), resolutions.end())
204 .then(
this, [
this](
const QList<QFuture<
bool>> &resolutions) {
205 handleEncodersResolved(resolutions);
212 const std::vector<QPlatformVideoSource *> &videoSources)
214 Q_ASSERT(m_state == State::None);
216 m_state = State::FormatsInitializing;
217 m_formatsInitializer = std::make_unique<EncodingInitializer>(*
this);
218 return m_formatsInitializer->start(audioSources, videoSources);
222 : m_recordingEngine(recordingEngine)
224 setObjectName(QStringLiteral(
"EncodingFinalizer"));
225 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
226 connect(
this, &QThread::finished,
this, &QObject::deleteLater);
231 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
237 m_recordingEngine.stopAndDeleteThreads();
240 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
242 qCWarning(qLcFFmpegEncoder) <<
"could not write trailer" << res << AVError(res);
243 Q_EMIT m_recordingEngine.sessionError(QMediaRecorder::FormatError,
244 QLatin1String(
"Cannot write trailer: ")
251 m_recordingEngine.m_formatContext->closeAVIO();
253 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalized";
254 Q_EMIT m_recordingEngine.finalizationDone();
255 m_recordingEngine.deleteLater();
260 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalizing";
262 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
263 || m_state == State::Encoding);
265 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
267 if (m_state == State::FormatsInitializing)
268 markEncodingStart(
false);
270 m_formatsInitializer.reset();
274 m_state = State::Finalizing;
276 m_finalizer =
new EncodingFinalizer(*
this);
277 m_finalizer->start();
282 forEachEncoder(&EncoderThread::setPaused, paused);
287 m_autoStop = autoStop;
288 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
289 handleSourceEndOfStream();
294 m_metaData = metaData;
299 QMutexLocker locker(&m_timeMutex);
300 if (time > m_timeRecorded) {
301 m_timeRecorded = time;
302 Q_EMIT durationChanged(time);
308 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
316 if (m_state == State::Finalizing)
320 Q_EMIT autoStopped();
323void RecordingEngine::handleEncodersResolved(
const QList<QFuture<
bool>> &resolutions)
325 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
327 bool allEncodersSucceeded = ranges::all_of(resolutions,
328 [](
const QFuture<
bool> &future) {
329 return future.result();
332 if (allEncodersSucceeded) {
333 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
335 qCDebug(qLcFFmpegEncoder) <<
"Encoders initialized; writing header";
337 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
339 AVDictionaryHolder options = muxerOptions(avFormatContext());
340 const int res = avformat_write_header(avFormatContext(), options);
342 qWarning() <<
"could not write header, error:" << res << AVError(res);
343 allEncodersSucceeded =
false;
344 Q_EMIT sessionError(QMediaRecorder::ResourceError,
345 QStringLiteral(
"Cannot start writing the stream"));
347 qCDebug(qLcFFmpegEncoder) <<
"Stream header is successfully written";
349 if (m_state == State::EncodersInitializing)
350 m_state = State::Encoding;
356 markEncodingStart(allEncodersSucceeded);
361 m_audioEncoders.clear();
362 m_videoEncoders.clear();
368 m_startEncodingPromise.addResult(startOk);
369 m_startEncodingPromise.finish();
372template <
typename F,
typename... Args>
375 for (
auto &audioEncoder : m_audioEncoders)
376 std::invoke(f, audioEncoder.get(), args...);
377 for (
auto &videoEncoder : m_videoEncoders)
378 std::invoke(f, videoEncoder.get(), args...);
384 auto predicate = [&f](
const auto &encoder) {
return std::invoke(f, encoder.get()); };
386 return ranges::all_of(m_audioEncoders, predicate)
387 && ranges::all_of(m_videoEncoders, predicate);
394#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,...)