From ed9807159c24278bd87205fa171af738c84ade01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8ger=20Hanseg=C3=A5rd?= Date: Mon, 1 Apr 2024 16:23:00 +0200 Subject: Move FFmpeg VideoEncoder to its own files This helps readability because qffmpegvideoencoder_p.h file is quite big and contains multiple classes. Task-number: QTBUG-121792 Pick-to: 6.5 Change-Id: Ia7295daa879375547e0b4db37c270f9aad768fc3 Reviewed-by: Artem Dyomin (cherry picked from commit ba1da65044863c79a23bb21ea0126de572d917cd) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit 0e9a4bc4c83f4c4d1597dbd65e2a54d52a432edc) --- src/plugins/multimedia/ffmpeg/CMakeLists.txt | 2 + .../recordingengine/qffmpegrecordingengine.cpp | 87 +---------------- .../recordingengine/qffmpegrecordingengine_p.h | 36 ------- .../ffmpeg/recordingengine/qffmpegvideoencoder.cpp | 104 +++++++++++++++++++++ .../ffmpeg/recordingengine/qffmpegvideoencoder_p.h | 55 +++++++++++ 5 files changed, 162 insertions(+), 122 deletions(-) create mode 100644 src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp create mode 100644 src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h diff --git a/src/plugins/multimedia/ffmpeg/CMakeLists.txt b/src/plugins/multimedia/ffmpeg/CMakeLists.txt index c94fa3e1f..62956556c 100644 --- a/src/plugins/multimedia/ffmpeg/CMakeLists.txt +++ b/src/plugins/multimedia/ffmpeg/CMakeLists.txt @@ -64,6 +64,8 @@ qt_internal_add_plugin(QFFmpegMediaPlugin recordingengine/qffmpegmuxer.cpp recordingengine/qffmpegrecordingengine_p.h recordingengine/qffmpegrecordingengine.cpp + recordingengine/qffmpegvideoencoder_p.h + recordingengine/qffmpegvideoencoder.cpp recordingengine/qffmpegvideoencoderutils_p.h recordingengine/qffmpegvideoencoderutils.cpp recordingengine/qffmpegvideoframeencoder_p.h diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp index b6fce596f..fb9c4bd3c 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine.cpp @@ -12,6 +12,7 @@ #include #include #include "qffmpegvideobuffer_p.h" +#include "qffmpegvideoencoder_p.h" #include "qffmpegmediametadata_p.h" #include "qffmpegmuxer_p.h" #include @@ -179,92 +180,6 @@ void RecordingEngine::addMediaFrameHandler(Args &&...args) m_connections.append(connection); } -VideoEncoder::VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings, - const QVideoFrameFormat &format, std::optional hwFormat) - : EncoderThread(encoder) -{ - setObjectName(QLatin1String("VideoEncoder")); - - AVPixelFormat swFormat = QFFmpegVideoBuffer::toAVPixelFormat(format.pixelFormat()); - AVPixelFormat ffmpegPixelFormat = - hwFormat && *hwFormat != AV_PIX_FMT_NONE ? *hwFormat : swFormat; - auto frameRate = format.frameRate(); - if (frameRate <= 0.) { - qWarning() << "Invalid frameRate" << frameRate << "; Using the default instead"; - - // set some default frame rate since ffmpeg has UB if it's 0. - frameRate = 30.; - } - - m_frameEncoder = - VideoFrameEncoder::create(settings, format.frameSize(), frameRate, ffmpegPixelFormat, - swFormat, encoder->avFormatContext()); -} - -VideoEncoder::~VideoEncoder() = default; - -bool VideoEncoder::isValid() const -{ - return m_frameEncoder != nullptr; -} - -void VideoEncoder::addFrame(const QVideoFrame &frame) -{ - QMutexLocker locker(&m_queueMutex); - - // Drop frames if encoder can not keep up with the video source data rate - const bool queueFull = m_videoFrameQueue.size() >= m_maxQueueSize; - - if (queueFull) { - qCDebug(qLcFFmpegEncoder) << "RecordingEngine frame queue full. Frame lost."; - } else if (!m_paused.loadRelaxed()) { - m_videoFrameQueue.push(frame); - - locker.unlock(); // Avoid context switch on wake wake-up - - dataReady(); - } -} - -QVideoFrame VideoEncoder::takeFrame() -{ - QMutexLocker locker(&m_queueMutex); - return dequeueIfPossible(m_videoFrameQueue); -} - -void VideoEncoder::retrievePackets() -{ - if (!m_frameEncoder) - return; - while (auto packet = m_frameEncoder->retrievePacket()) - m_encoder->m_muxer->addPacket(std::move(packet)); -} - -void VideoEncoder::init() -{ - qCDebug(qLcFFmpegEncoder) << "VideoEncoder::init started video device thread."; - bool ok = m_frameEncoder->open(); - if (!ok) - emit m_encoder->error(QMediaRecorder::ResourceError, "Could not initialize encoder"); -} - -void VideoEncoder::cleanup() -{ - while (!m_videoFrameQueue.empty()) - processOne(); - if (m_frameEncoder) { - while (m_frameEncoder->sendFrame(nullptr) == AVERROR(EAGAIN)) - retrievePackets(); - retrievePackets(); - } -} - -bool VideoEncoder::hasData() const -{ - QMutexLocker locker(&m_queueMutex); - return !m_videoFrameQueue.empty(); -} - struct QVideoFrameHolder { QVideoFrame f; diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h index ca8fbf465..02f3e7d51 100644 --- a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegrecordingengine_p.h @@ -131,42 +131,6 @@ protected: RecordingEngine *m_encoder = nullptr; }; -class VideoEncoder : public EncoderThread -{ -public: - VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings, - const QVideoFrameFormat &format, std::optional hwFormat); - ~VideoEncoder() override; - - bool isValid() const; - - void addFrame(const QVideoFrame &frame); - - void setPaused(bool b) override - { - EncoderThread::setPaused(b); - if (b) - m_baseTime.storeRelease(-1); - } - -private: - QVideoFrame takeFrame(); - void retrievePackets(); - - void init() override; - void cleanup() override; - bool hasData() const override; - void processOne() override; - -private: - mutable QMutex m_queueMutex; - std::queue m_videoFrameQueue; - const size_t m_maxQueueSize = 10; // Arbitrarily chosen to limit memory usage (332 MB @ 4K) - - std::unique_ptr m_frameEncoder; - QAtomicInteger m_baseTime = std::numeric_limits::min(); - qint64 m_lastFrameTime = 0; -}; } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp new file mode 100644 index 000000000..8a3883264 --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder.cpp @@ -0,0 +1,104 @@ +// Copyright (C) 2024 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 "qffmpegvideoencoder_p.h" +#include "qffmpegmuxer_p.h" +#include "qffmpegvideobuffer_p.h" +#include "qffmpegvideoframeencoder_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +static Q_LOGGING_CATEGORY(qLcFFmpegVideoEncoder, "qt.multimedia.ffmpeg.videoencoder"); + + +VideoEncoder::VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings, + const QVideoFrameFormat &format, std::optional hwFormat) + : EncoderThread(encoder) +{ + setObjectName(QLatin1String("VideoEncoder")); + + AVPixelFormat swFormat = QFFmpegVideoBuffer::toAVPixelFormat(format.pixelFormat()); + AVPixelFormat ffmpegPixelFormat = + hwFormat && *hwFormat != AV_PIX_FMT_NONE ? *hwFormat : swFormat; + auto frameRate = format.frameRate(); + if (frameRate <= 0.) { + qWarning() << "Invalid frameRate" << frameRate << "; Using the default instead"; + + // set some default frame rate since ffmpeg has UB if it's 0. + frameRate = 30.; + } + + m_frameEncoder = + VideoFrameEncoder::create(settings, format.frameSize(), frameRate, ffmpegPixelFormat, + swFormat, encoder->avFormatContext()); +} + +VideoEncoder::~VideoEncoder() = default; + +bool VideoEncoder::isValid() const +{ + return m_frameEncoder != nullptr; +} + +void VideoEncoder::addFrame(const QVideoFrame &frame) +{ + QMutexLocker locker(&m_queueMutex); + + // Drop frames if encoder can not keep up with the video source data rate + const bool queueFull = m_videoFrameQueue.size() >= m_maxQueueSize; + + if (queueFull) { + qCDebug(qLcFFmpegVideoEncoder) << "RecordingEngine frame queue full. Frame lost."; + } else if (!m_paused.loadRelaxed()) { + m_videoFrameQueue.push(frame); + + locker.unlock(); // Avoid context switch on wake wake-up + + dataReady(); + } +} + +QVideoFrame VideoEncoder::takeFrame() +{ + QMutexLocker locker(&m_queueMutex); + return dequeueIfPossible(m_videoFrameQueue); +} + +void VideoEncoder::retrievePackets() +{ + if (!m_frameEncoder) + return; + while (auto packet = m_frameEncoder->retrievePacket()) + m_encoder->m_muxer->addPacket(std::move(packet)); +} + +void VideoEncoder::init() +{ + qCDebug(qLcFFmpegVideoEncoder) << "VideoEncoder::init started video device thread."; + bool ok = m_frameEncoder->open(); + if (!ok) + emit m_encoder->error(QMediaRecorder::ResourceError, "Could not initialize encoder"); +} + +void VideoEncoder::cleanup() +{ + while (!m_videoFrameQueue.empty()) + processOne(); + if (m_frameEncoder) { + while (m_frameEncoder->sendFrame(nullptr) == AVERROR(EAGAIN)) + retrievePackets(); + retrievePackets(); + } +} + +bool VideoEncoder::hasData() const +{ + QMutexLocker locker(&m_queueMutex); + return !m_videoFrameQueue.empty(); +} + +} // namespace QFFmpeg + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h new file mode 100644 index 000000000..dd7d5014a --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/recordingengine/qffmpegvideoencoder_p.h @@ -0,0 +1,55 @@ +// Copyright (C) 2024 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 +#ifndef QFFMPEGVIDEOENCODER_P_H +#define QFFMPEGVIDEOENCODER_P_H + +#include "qffmpegrecordingengine_p.h" +#include "qffmpeg_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace QFFmpeg { + +class VideoEncoder : public EncoderThread +{ +public: + VideoEncoder(RecordingEngine *encoder, const QMediaEncoderSettings &settings, + const QVideoFrameFormat &format, std::optional hwFormat); + ~VideoEncoder() override; + + bool isValid() const; + + void addFrame(const QVideoFrame &frame); + + void setPaused(bool b) override + { + EncoderThread::setPaused(b); + if (b) + m_baseTime.storeRelease(-1); + } + +private: + QVideoFrame takeFrame(); + void retrievePackets(); + + void init() override; + void cleanup() override; + bool hasData() const override; + void processOne() override; + +private: + mutable QMutex m_queueMutex; + std::queue m_videoFrameQueue; + const size_t m_maxQueueSize = 10; // Arbitrarily chosen to limit memory usage (332 MB @ 4K) + + std::unique_ptr m_frameEncoder; + QAtomicInteger m_baseTime = std::numeric_limits::min(); + qint64 m_lastFrameTime = 0; +}; + +} // namespace QFFmpeg + +QT_END_NAMESPACE + +#endif -- cgit v1.2.3