diff options
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp')
-rw-r--r-- | src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp | 190 |
1 files changed, 143 insertions, 47 deletions
diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp index 23bd4c02d..469cd1c48 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp @@ -1,23 +1,25 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qffmpegrecordingengine_p.h" -#include "qffmpegmediaformatinfo_p.h" -#include "qffmpegvideoframeencoder_p.h" -#include "private/qmultimediautils_p.h" - -#include <qdebug.h> +#include "qffmpegencodinginitializer_p.h" #include "qffmpegaudioencoder_p.h" #include "qffmpegaudioinput_p.h" -#include <private/qplatformcamera_p.h> -#include "qffmpegvideobuffer_p.h" +#include "qffmpegrecordingengineutils_p.h" + +#include "private/qmultimediautils_p.h" +#include "private/qplatformaudiobufferinput_p.h" +#include "private/qplatformvideosource_p.h" +#include "private/qplatformvideoframeinput_p.h" + +#include "qdebug.h" #include "qffmpegvideoencoder_p.h" #include "qffmpegmediametadata_p.h" #include "qffmpegmuxer_p.h" -#include <qloggingcategory.h> +#include "qloggingcategory.h" QT_BEGIN_NAMESPACE -static Q_LOGGING_CATEGORY(qLcFFmpegEncoder, "qt.multimedia.ffmpeg.encoder"); +Q_STATIC_LOGGING_CATEGORY(qLcFFmpegEncoder, "qt.multimedia.ffmpeg.encoder"); namespace QFFmpeg { @@ -36,21 +38,71 @@ RecordingEngine::~RecordingEngine() void RecordingEngine::addAudioInput(QFFmpegAudioInput *input) { - m_audioEncoder = new AudioEncoder(*this, input, m_settings); - addMediaFrameHandler(input, &QFFmpegAudioInput::newAudioBuffer, m_audioEncoder, - &AudioEncoder::addBuffer); + Q_ASSERT(input); + + if (input->device.isNull()) { + emit streamInitializationError(QMediaRecorder::ResourceError, + QLatin1StringView("Audio device is null")); + return; + } + + const QAudioFormat format = input->device.preferredFormat(); + + if (!format.isValid()) { + emit streamInitializationError( + QMediaRecorder::FormatError, + QLatin1StringView("Audio device has invalid preferred format")); + return; + } + + AudioEncoder *audioEncoder = createAudioEncoder(format); + connectEncoderToSource(audioEncoder, input); + input->setRunning(true); } -void RecordingEngine::addVideoSource(QPlatformVideoSource * source) +void RecordingEngine::addAudioBufferInput(QPlatformAudioBufferInput *input, + const QAudioBuffer &firstBuffer) { - auto frameFormat = source->frameFormat(); + Q_ASSERT(input); + const QAudioFormat format = firstBuffer.isValid() ? firstBuffer.format() : input->audioFormat(); - if (!frameFormat.isValid()) { - qCWarning(qLcFFmpegEncoder) << "Cannot add source; invalid vide frame format"; - emit error(QMediaRecorder::ResourceError, - QLatin1StringView("Cannot get video source format")); - return; + AudioEncoder *audioEncoder = createAudioEncoder(format); + + // set the buffer before connecting to avoid potential races + if (firstBuffer.isValid()) + audioEncoder->addBuffer(firstBuffer); + + connectEncoderToSource(audioEncoder, input); +} + +AudioEncoder *RecordingEngine::createAudioEncoder(const QAudioFormat &format) +{ + Q_ASSERT(format.isValid()); + + auto audioEncoder = new AudioEncoder(*this, format, m_settings); + m_audioEncoders.push_back(audioEncoder); + connect(audioEncoder, &EncoderThread::endOfSourceStream, this, + &RecordingEngine::handleSourceEndOfStream); + if (m_autoStop) + audioEncoder->setAutoStop(true); + + return audioEncoder; +} + +void RecordingEngine::addVideoSource(QPlatformVideoSource *source, const QVideoFrame &firstFrame) +{ + QVideoFrameFormat frameFormat = + firstFrame.isValid() ? firstFrame.surfaceFormat() : source->frameFormat(); + + Q_ASSERT(frameFormat.isValid()); + + if (firstFrame.isValid() && frameFormat.streamFrameRate() <= 0.f) { + const qint64 startTime = firstFrame.startTime(); + const qint64 endTime = firstFrame.endTime(); + if (startTime != -1 && endTime > startTime) + frameFormat.setStreamFrameRate(static_cast<qreal>(VideoFrameTimeBase) + / (endTime - startTime)); } std::optional<AVPixelFormat> hwPixelFormat = source->ffmpegHWPixelFormat() @@ -65,17 +117,37 @@ void RecordingEngine::addVideoSource(QPlatformVideoSource * source) auto veUPtr = std::make_unique<VideoEncoder>(*this, m_settings, frameFormat, hwPixelFormat); if (!veUPtr->isValid()) { - emit error(QMediaRecorder::FormatError, QLatin1StringView("Cannot initialize encoder")); + emit streamInitializationError(QMediaRecorder::FormatError, + QLatin1StringView("Cannot initialize encoder")); return; } - auto ve = veUPtr.release(); - addMediaFrameHandler(source, &QPlatformVideoSource::newVideoFrame, ve, &VideoEncoder::addFrame); - m_videoEncoders.append(ve); + auto videoEncoder = veUPtr.release(); + m_videoEncoders.append(videoEncoder); + if (m_autoStop) + videoEncoder->setAutoStop(true); + + connect(videoEncoder, &EncoderThread::endOfSourceStream, this, + &RecordingEngine::handleSourceEndOfStream); + + // set the frame before connecting to avoid potential races + if (firstFrame.isValid()) + videoEncoder->addFrame(firstFrame); + + connectEncoderToSource(videoEncoder, source); } void RecordingEngine::start() { + Q_ASSERT(m_initializer); + m_initializer.reset(); + + if (m_audioEncoders.empty() && m_videoEncoders.empty()) { + emit sessionError(QMediaRecorder::ResourceError, + QLatin1StringView("No valid stream found for encoding")); + return; + } + qCDebug(qLcFFmpegEncoder) << "RecordingEngine::start!"; avFormatContext()->metadata = QFFmpegMetaData::toAVMetaData(m_metaData); @@ -85,7 +157,8 @@ void RecordingEngine::start() int res = avformat_write_header(avFormatContext(), nullptr); if (res < 0) { qWarning() << "could not write header, error:" << res << err2str(res); - emit error(QMediaRecorder::ResourceError, "Cannot start writing the stream"); + emit sessionError(QMediaRecorder::ResourceError, + QLatin1StringView("Cannot start writing the stream")); return; } @@ -94,11 +167,17 @@ void RecordingEngine::start() qCDebug(qLcFFmpegEncoder) << "stream header is successfully written"; m_muxer->start(); - if (m_audioEncoder) - m_audioEncoder->start(); - for (auto *videoEncoder : m_videoEncoders) - if (videoEncoder->isValid()) - videoEncoder->start(); + + forEachEncoder([](QThread *thread) { thread->start(); }); +} + +void RecordingEngine::initialize(const std::vector<QPlatformAudioBufferInputBase *> &audioSources, + const std::vector<QPlatformVideoSource *> &videoSources) +{ + qCDebug(qLcFFmpegEncoder) << ">>>>>>>>>>>>>>> initialize"; + + m_initializer = std::make_unique<EncodingInitializer>(*this); + m_initializer->start(audioSources, videoSources); } RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recordingEngine) @@ -109,10 +188,7 @@ RecordingEngine::EncodingFinalizer::EncodingFinalizer(RecordingEngine &recording void RecordingEngine::EncodingFinalizer::run() { - if (m_recordingEngine.m_audioEncoder) - m_recordingEngine.m_audioEncoder->stopAndDelete(); - for (auto &videoEncoder : m_recordingEngine.m_videoEncoders) - videoEncoder->stopAndDelete(); + m_recordingEngine.forEachEncoder(&EncoderThread::stopAndDelete); m_recordingEngine.m_muxer->stopAndDelete(); if (m_recordingEngine.m_isHeaderWritten) { @@ -120,9 +196,9 @@ void RecordingEngine::EncodingFinalizer::run() if (res < 0) { const auto errorDescription = err2str(res); qCWarning(qLcFFmpegEncoder) << "could not write trailer" << res << errorDescription; - emit m_recordingEngine.error(QMediaRecorder::FormatError, - QLatin1String("Cannot write trailer: ") - + errorDescription); + emit m_recordingEngine.sessionError(QMediaRecorder::FormatError, + QLatin1String("Cannot write trailer: ") + + errorDescription); } } // else ffmpeg might crash @@ -140,19 +216,24 @@ void RecordingEngine::finalize() { qCDebug(qLcFFmpegEncoder) << ">>>>>>>>>>>>>>> finalize"; - for (auto &conn : m_connections) - disconnect(conn); + m_initializer.reset(); + + forEachEncoder(&disconnectEncoderFromSource); auto *finalizer = new EncodingFinalizer(*this); finalizer->start(); } -void RecordingEngine::setPaused(bool p) +void RecordingEngine::setPaused(bool paused) +{ + forEachEncoder(&EncoderThread::setPaused, paused); +} + +void RecordingEngine::setAutoStop(bool autoStop) { - if (m_audioEncoder) - m_audioEncoder->setPaused(p); - for (auto &videoEncoder : m_videoEncoders) - videoEncoder->setPaused(p); + m_autoStop = autoStop; + forEachEncoder(&EncoderThread::setAutoStop, autoStop); + handleSourceEndOfStream(); } void RecordingEngine::setMetaData(const QMediaMetaData &metaData) @@ -169,11 +250,26 @@ void RecordingEngine::newTimeStamp(qint64 time) } } -template<typename... Args> -void RecordingEngine::addMediaFrameHandler(Args &&...args) +bool RecordingEngine::isEndOfSourceStreams() const +{ + auto isAtEnd = [](EncoderThread *encoder) { return encoder->isEndOfSourceStream(); }; + return std::all_of(m_videoEncoders.cbegin(), m_videoEncoders.cend(), isAtEnd) + && std::all_of(m_audioEncoders.cbegin(), m_audioEncoders.cend(), isAtEnd); +} + +void RecordingEngine::handleSourceEndOfStream() +{ + if (m_autoStop && isEndOfSourceStreams()) + emit autoStopped(); +} + +template <typename F, typename... Args> +void RecordingEngine::forEachEncoder(F &&f, Args &&...args) { - auto connection = connect(std::forward<Args>(args)..., Qt::DirectConnection); - m_connections.append(connection); + for (AudioEncoder *audioEncoder : m_audioEncoders) + std::invoke(f, audioEncoder, args...); + for (VideoEncoder *videoEncoder : m_videoEncoders) + std::invoke(f, videoEncoder, args...); } } |