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());
38 Q_ASSERT(m_state == State::Finalizing);
44 Q_ASSERT(m_state == State::FormatsInitializing);
46 if (input->device.isNull()) {
47 emit streamInitializationError(QMediaRecorder::ResourceError,
48 QLatin1StringView(
"Audio device is null"));
52 const QAudioFormat format = input->device.preferredFormat();
54 if (!format.isValid()) {
55 emit streamInitializationError(
56 QMediaRecorder::FormatError,
57 QLatin1StringView(
"Audio device has invalid preferred format"));
62 connectEncoderToSource(audioEncoder, input);
65void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
66 const QAudioBuffer &firstBuffer)
69 Q_ASSERT(m_state == State::FormatsInitializing);
71 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
76 if (firstBuffer.isValid())
77 audioEncoder->addBuffer(firstBuffer);
79 connectEncoderToSource(audioEncoder, input);
84 Q_ASSERT(format.isValid());
86 auto audioEncoder =
new AudioEncoder(*
this, format, m_settings);
88 m_audioEncoders.emplace_back(audioEncoder);
89 connect(audioEncoder, &EncoderThread::endOfSourceStream,
this,
91 connect(audioEncoder, &EncoderThread::initialized,
this,
92 &
RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
94 audioEncoder->setAutoStop(
true);
99void RecordingEngine::addVideoSource(QPlatformVideoSource *source,
const QVideoFrame &firstFrame)
101 Q_ASSERT(m_state == State::FormatsInitializing);
103 QVideoFrameFormat frameFormat =
104 firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat();
106 Q_ASSERT(frameFormat.isValid());
108 if (firstFrame.isValid() && frameFormat.streamFrameRate() <= 0.f) {
109 const qint64 startTime = firstFrame.startTime();
110 const qint64 endTime = firstFrame.endTime();
111 if (startTime != -1 && endTime > startTime)
112 frameFormat.setStreamFrameRate(
static_cast<qreal>(VideoFrameTimeBase)
113 / (endTime - startTime));
116 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
117 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
118 : std::optional<AVPixelFormat>{};
120 qCDebug(qLcFFmpegEncoder) <<
"adding video source" << source->metaObject()->className() <<
":"
121 <<
"pixelFormat=" << frameFormat.pixelFormat()
122 <<
"frameSize=" << frameFormat.frameSize()
123 <<
"frameRate=" << frameFormat.streamFrameRate()
124 <<
"ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
126 auto videoEncoder =
new VideoEncoder(*
this, m_settings, frameFormat, hwPixelFormat);
127 m_videoEncoders.emplace_back(videoEncoder);
129 videoEncoder->setAutoStop(
true);
131 connect(videoEncoder, &EncoderThread::endOfSourceStream,
this,
134 connect(videoEncoder, &EncoderThread::initialized,
this,
135 &
RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
138 if (firstFrame.isValid())
139 videoEncoder->addFrame(firstFrame);
141 connectEncoderToSource(videoEncoder, source);
146 Q_ASSERT(m_state == State::FormatsInitializing);
147 Q_ASSERT(m_formatsInitializer);
148 m_formatsInitializer.reset();
150 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
151 emit sessionError(QMediaRecorder::ResourceError,
152 QLatin1StringView(
"No valid stream found for encoding"));
156 m_state = State::EncodersInitializing;
166 const std::vector<QPlatformVideoSource *> &videoSources)
168 Q_ASSERT(m_state == State::None);
170 m_state = State::FormatsInitializing;
171 m_formatsInitializer = std::make_unique<EncodingInitializer>(*
this);
172 return m_formatsInitializer->start(audioSources, videoSources);
177 : m_recordingEngine(recordingEngine), m_writeTrailer(writeTrailer)
179 setObjectName(QStringLiteral(
"EncodingFinalizer"));
180 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
181 connect(
this, &QThread::finished,
this, &QObject::deleteLater);
186 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
188 m_recordingEngine.stopAndDeleteThreads();
190 if (m_writeTrailer) {
191 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
193 qCWarning(qLcFFmpegEncoder) <<
"could not write trailer" << res << AVError(res);
194 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
195 QLatin1String(
"Cannot write trailer: ")
202 m_recordingEngine.m_formatContext->closeAVIO();
204 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalized";
205 emit m_recordingEngine.finalizationDone();
206 auto recordingEnginePtr = &m_recordingEngine;
207 delete recordingEnginePtr;
212 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalizing";
214 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
215 || m_state == State::Encoding);
217 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
219 m_formatsInitializer.reset();
222 if (m_state != State::Encoding)
223 forEachEncoder(&EncoderThread::startEncoding,
false);
225 const bool shouldWriteTrailer = m_state == State::Encoding;
226 m_state = State::Finalizing;
228 EncodingFinalizer *finalizer =
new EncodingFinalizer(*
this, shouldWriteTrailer);
234 forEachEncoder(&EncoderThread::setPaused, paused);
239 m_autoStop = autoStop;
240 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
241 handleSourceEndOfStream();
246 m_metaData = metaData;
251 QMutexLocker locker(&m_timeMutex);
252 if (time > m_timeRecorded) {
253 m_timeRecorded = time;
254 emit durationChanged(time);
260 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
271 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
273 if (m_state == State::Finalizing)
276 ++m_initializedEncodersCount;
278 Q_ASSERT(m_initializedEncodersCount <= encodersCount());
280 if (m_initializedEncodersCount < encodersCount())
283 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
285 qCDebug(qLcFFmpegEncoder) <<
"Encoders initialized; writing header";
287 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
289 const int res = avformat_write_header(avFormatContext(),
nullptr);
291 qWarning() <<
"could not write header, error:" << res << AVError(res);
292 emit sessionError(QMediaRecorder::ResourceError,
293 QLatin1StringView(
"Cannot start writing the stream"));
297 qCDebug(qLcFFmpegEncoder) <<
"Stream header is successfully written";
299 m_state = State::Encoding;
301 forEachEncoder(&EncoderThread::startEncoding,
true);
306 m_audioEncoders.clear();
307 m_videoEncoders.clear();
311template <
typename F,
typename... Args>
314 for (
auto &audioEncoder : m_audioEncoders)
315 std::invoke(f, audioEncoder.get(), args...);
316 for (
auto &videoEncoder : m_videoEncoders)
317 std::invoke(f, videoEncoder.get(), args...);
323 auto predicate = [&f](
const auto &encoder) {
return std::invoke(f, encoder.get()); };
325 return ranges::all_of(m_audioEncoders, predicate)
326 && ranges::all_of(m_videoEncoders, predicate);
332#include "moc_qffmpegrecordingengine_p.cpp"
bool isEndOfSourceStreams() const
bool initialize(const std::vector< QAudioBufferSource * > &audioSources, const std::vector< QPlatformVideoSource * > &videoSources)
~RecordingEngine() override
void setAutoStop(bool autoStop)
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
void disconnectEncoderFromSource(EncoderThread *encoder)
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)