9#include "private/qmultimediautils_p.h"
10#include "private/qplatformaudiobufferinput_p.h"
11#include "private/qplatformvideosource_p.h"
12#include "private/qplatformvideoframeinput_p.h"
28 std::unique_ptr<EncodingFormatContext> context)
29 : m_settings(settings), m_formatContext(std::move(context)), m_muxer(
new Muxer(
this))
31 Q_ASSERT(m_formatContext);
32 Q_ASSERT(m_formatContext->isAVIOOpen());
37 Q_ASSERT(m_state == State::Finalizing);
43 Q_ASSERT(m_state == State::FormatsInitializing);
45 if (input->device.isNull()) {
46 emit streamInitializationError(QMediaRecorder::ResourceError,
47 QLatin1StringView(
"Audio device is null"));
51 const QAudioFormat format = input->device.preferredFormat();
53 if (!format.isValid()) {
54 emit streamInitializationError(
55 QMediaRecorder::FormatError,
56 QLatin1StringView(
"Audio device has invalid preferred format"));
61 connectEncoderToSource(audioEncoder, input);
64void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input,
65 const QAudioBuffer &firstBuffer)
68 Q_ASSERT(m_state == State::FormatsInitializing);
70 const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat();
75 if (firstBuffer.isValid())
76 audioEncoder->addBuffer(firstBuffer);
78 connectEncoderToSource(audioEncoder, input);
83 Q_ASSERT(format.isValid());
85 auto audioEncoder =
new AudioEncoder(*
this, format, m_settings);
87 m_audioEncoders.emplace_back(audioEncoder);
88 connect(audioEncoder, &EncoderThread::endOfSourceStream,
this,
89 &RecordingEngine::handleSourceEndOfStream);
90 connect(audioEncoder, &EncoderThread::initialized,
this,
91 &RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
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 if (firstFrame.isValid() && frameFormat.streamFrameRate() <= 0.f) {
108 const qint64 startTime = firstFrame.startTime();
109 const qint64 endTime = firstFrame.endTime();
110 if (startTime != -1 && endTime > startTime)
111 frameFormat.setStreamFrameRate(
static_cast<qreal>(VideoFrameTimeBase)
112 / (endTime - startTime));
115 std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat()
116 ? AVPixelFormat(*source->ffmpegHWPixelFormat())
117 : std::optional<AVPixelFormat>{};
119 qCDebug(qLcFFmpegEncoder) <<
"adding video source" << source->metaObject()->className() <<
":"
120 <<
"pixelFormat=" << frameFormat.pixelFormat()
121 <<
"frameSize=" << frameFormat.frameSize()
122 <<
"frameRate=" << frameFormat.streamFrameRate()
123 <<
"ffmpegHWPixelFormat=" << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
125 auto videoEncoder =
new VideoEncoder(*
this, m_settings, frameFormat, hwPixelFormat);
126 m_videoEncoders.emplace_back(videoEncoder);
128 videoEncoder->setAutoStop(
true);
130 connect(videoEncoder, &EncoderThread::endOfSourceStream,
this,
131 &RecordingEngine::handleSourceEndOfStream);
133 connect(videoEncoder, &EncoderThread::initialized,
this,
134 &RecordingEngine::handleEncoderInitialization, Qt::SingleShotConnection);
137 if (firstFrame.isValid())
138 videoEncoder->addFrame(firstFrame);
140 connectEncoderToSource(videoEncoder, source);
145 Q_ASSERT(m_state == State::FormatsInitializing);
146 Q_ASSERT(m_formatsInitializer);
147 m_formatsInitializer.reset();
149 if (m_audioEncoders.empty() && m_videoEncoders.empty()) {
150 emit sessionError(QMediaRecorder::ResourceError,
151 QLatin1StringView(
"No valid stream found for encoding"));
155 m_state = State::EncodersInitializing;
165 const std::vector<QPlatformVideoSource *> &videoSources)
167 Q_ASSERT(m_state == State::None);
169 m_state = State::FormatsInitializing;
170 m_formatsInitializer = std::make_unique<EncodingInitializer>(*
this);
171 return m_formatsInitializer->start(audioSources, videoSources);
176 : m_recordingEngine(recordingEngine), m_writeTrailer(writeTrailer)
178 setObjectName(QStringLiteral(
"EncodingFinalizer"));
179 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
180 connect(
this, &QThread::finished,
this, &QObject::deleteLater);
185 Q_ASSERT(m_recordingEngine.m_state == State::Finalizing);
187 m_recordingEngine.stopAndDeleteThreads();
189 if (m_writeTrailer) {
190 const int res = av_write_trailer(m_recordingEngine.avFormatContext());
192 qCWarning(qLcFFmpegEncoder) <<
"could not write trailer" << res << AVError(res);
193 emit m_recordingEngine.sessionError(QMediaRecorder::FormatError,
194 QLatin1String(
"Cannot write trailer: ")
201 m_recordingEngine.m_formatContext->closeAVIO();
203 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalized";
204 emit m_recordingEngine.finalizationDone();
205 auto recordingEnginePtr = &m_recordingEngine;
206 delete recordingEnginePtr;
211 qCDebug(qLcFFmpegEncoder) <<
"Media recording finalizing";
213 Q_ASSERT(m_state == State::FormatsInitializing || m_state == State::EncodersInitializing
214 || m_state == State::Encoding);
216 Q_ASSERT((m_state == State::FormatsInitializing) == !!m_formatsInitializer);
218 m_formatsInitializer.reset();
221 if (m_state != State::Encoding)
222 forEachEncoder(&EncoderThread::startEncoding,
false);
224 const bool shouldWriteTrailer = m_state == State::Encoding;
225 m_state = State::Finalizing;
227 EncodingFinalizer *finalizer =
new EncodingFinalizer(*
this, shouldWriteTrailer);
233 forEachEncoder(&EncoderThread::setPaused, paused);
238 m_autoStop = autoStop;
239 forEachEncoder(&EncoderThread::setAutoStop, autoStop);
240 handleSourceEndOfStream();
245 m_metaData = metaData;
250 QMutexLocker locker(&m_timeMutex);
251 if (time > m_timeRecorded) {
252 m_timeRecorded = time;
253 emit durationChanged(time);
259 return allOfEncoders(&EncoderThread::isEndOfSourceStream);
270 Q_ASSERT(m_state == State::EncodersInitializing || m_state == State::Finalizing);
272 if (m_state == State::Finalizing)
275 ++m_initializedEncodersCount;
277 Q_ASSERT(m_initializedEncodersCount <= encodersCount());
279 if (m_initializedEncodersCount < encodersCount())
282 Q_ASSERT(allOfEncoders(&EncoderThread::isInitialized));
284 qCDebug(qLcFFmpegEncoder) <<
"Encoders initialized; writing header";
286 avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData);
288 const int res = avformat_write_header(avFormatContext(),
nullptr);
290 qWarning() <<
"could not write header, error:" << res << AVError(res);
291 emit sessionError(QMediaRecorder::ResourceError,
292 QLatin1StringView(
"Cannot start writing the stream"));
296 qCDebug(qLcFFmpegEncoder) <<
"Stream header is successfully written";
298 m_state = State::Encoding;
300 forEachEncoder(&EncoderThread::startEncoding,
true);
305 m_audioEncoders.clear();
306 m_videoEncoders.clear();
310template <
typename F,
typename... Args>
313 for (
auto &audioEncoder : m_audioEncoders)
314 std::invoke(f, audioEncoder.get(), args...);
315 for (
auto &videoEncoder : m_videoEncoders)
316 std::invoke(f, videoEncoder.get(), args...);
322 auto predicate = [&f](
const auto &encoder) {
return std::invoke(f, encoder.get()); };
324 return std::all_of(m_audioEncoders.cbegin(), m_audioEncoders.cend(), predicate)
325 && std::all_of(m_videoEncoders.cbegin(), m_videoEncoders.cend(), predicate);
331#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)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)