diff options
author | Mikko Hallamaa <mikko.hallamaa@qt.io> | 2023-12-07 18:17:26 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-12-14 22:37:07 +0000 |
commit | cc4dd87764b4c9bd897138f891f42063cc218a75 (patch) | |
tree | a59f62d2355c1551be14ecc39c302782f3b98241 /src | |
parent | 312c718a8b7c65e16a266a71d8d39efd8ff1a589 (diff) |
Use composition to run screen capture on main thread if needed
EGLFS screen capture on embedded Linux platform crashes because of
calling QScreen::grabWindow on a separate thread. Need to refactor
FFmpeg surface capture to allow choosing whether to run on a separate
thread or not.
Pick-to: 6.6 6.5
Task-number: QTBUG-117746
Task-number: QTBUG-117878
Change-Id: I30629a3806220f02ca2cc3dc8c2749bd337914de
Reviewed-by: Artem Dyomin <artem.dyomin@qt.io>
(cherry picked from commit e5d614f599a25e9a7280d16f6a6fe18f73120c56)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'src')
5 files changed, 139 insertions, 72 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qffmpegscreencapture_dxgi.cpp b/src/plugins/multimedia/ffmpeg/qffmpegscreencapture_dxgi.cpp index 06c83f78f..e29c2ab03 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegscreencapture_dxgi.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegscreencapture_dxgi.cpp @@ -21,6 +21,8 @@ #include <thread> #include <chrono> +#include <mutex> // std::scoped_lock + QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(qLcScreenCaptureDxgi, "qt.multimedia.ffmpeg.screencapturedxgi") @@ -367,18 +369,6 @@ public: stop(); } - void run() override - { - m_duplication = DxgiDuplication(); - const ComStatus status = m_duplication.initialize(m_screen); - if (!status) { - updateError(CaptureFailed, status.str()); - return; - } - - QFFmpegSurfaceCaptureThread::run(); - } - QVideoFrameFormat format() { return m_format; } @@ -425,6 +415,19 @@ public: return frame; } + protected: + void initializeGrabbingContext() override + { + m_duplication = DxgiDuplication(); + const ComStatus status = m_duplication.initialize(m_screen); + if (!status) { + updateError(CaptureFailed, status.str()); + return; + } + + QFFmpegSurfaceCaptureThread::initializeGrabbingContext(); + } + private: const QScreen *m_screen = nullptr; QVideoFrameFormat m_format; diff --git a/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread.cpp b/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread.cpp index d503d9af2..b53d393ee 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread.cpp @@ -3,8 +3,10 @@ #include "qffmpegsurfacecapturethread_p.h" -#include <qtimer.h> +#include <qelapsedtimer.h> #include <qloggingcategory.h> +#include <qthread.h> +#include <qtimer.h> QT_BEGIN_NAMESPACE @@ -48,9 +50,53 @@ private: } // namespace -QFFmpegSurfaceCaptureThread::QFFmpegSurfaceCaptureThread() +struct QFFmpegSurfaceCaptureThread::GrabbingContext +{ + GrabbingProfiler profiler; + QTimer timer; + QElapsedTimer elapsedTimer; + qint64 lastFrameTime = 0; +}; + +class QFFmpegSurfaceCaptureThread::GrabbingThread : public QThread +{ +public: + GrabbingThread(QFFmpegSurfaceCaptureThread& grabber) + : m_grabber(grabber) + {} + +protected: + void run() override + { + m_grabber.initializeGrabbingContext(); + + if (!m_grabber.isGrabbingContextInitialized()) + return; + + exec(); + m_grabber.finalizeGrabbingContext(); + } + +private: + QFFmpegSurfaceCaptureThread& m_grabber; +}; + +QFFmpegSurfaceCaptureThread::QFFmpegSurfaceCaptureThread(bool runInThread) { setFrameRate(DefaultScreenCaptureFrameRate); + + if (!runInThread) + return; + + m_thread = std::make_unique<GrabbingThread>(*this); +} + +void QFFmpegSurfaceCaptureThread::start() +{ + if (m_thread) + m_thread->start(); + else if (!isGrabbingContextInitialized()) + initializeGrabbingContext(); } QFFmpegSurfaceCaptureThread::~QFFmpegSurfaceCaptureThread() = default; @@ -72,36 +118,60 @@ qreal QFFmpegSurfaceCaptureThread::frameRate() const void QFFmpegSurfaceCaptureThread::stop() { - quit(); - wait(); + if (m_thread) + { + m_thread->quit(); + m_thread->wait(); + } + else if (isGrabbingContextInitialized()) + { + finalizeGrabbingContext(); + } } -void QFFmpegSurfaceCaptureThread::run() +void QFFmpegSurfaceCaptureThread::updateError(QPlatformSurfaceCapture::Error error, + const QString &description) { - qCDebug(qLcScreenCaptureThread) << "start screen capture thread"; + const auto prevError = std::exchange(m_prevError, error); + + if (error != QPlatformSurfaceCapture::NoError + || prevError != QPlatformSurfaceCapture::NoError) { + emit errorUpdated(error, description); + } - m_timer = std::make_unique<QTimer>(); - // should be deleted in this thread - auto deleter = qScopeGuard([this]() { m_timer.reset(); }); - m_timer->setTimerType(Qt::PreciseTimer); updateTimerInterval(); +} - QElapsedTimer elapsedTimer; - elapsedTimer.start(); +void QFFmpegSurfaceCaptureThread::updateTimerInterval() +{ + const qreal rate = m_prevError && *m_prevError != QPlatformSurfaceCapture::NoError + ? MinScreenCaptureFrameRate + : m_rate; + const int interval = static_cast<int>(1000 / rate); + if (m_context && m_context->timer.interval() != interval) + m_context->timer.setInterval(interval); +} - qint64 lastFrameTime = 0; +void QFFmpegSurfaceCaptureThread::initializeGrabbingContext() +{ + Q_ASSERT(!isGrabbingContextInitialized()); + qCDebug(qLcScreenCaptureThread) << "screen capture started"; - GrabbingProfiler profiler; + m_context = std::make_unique<GrabbingContext>(); + m_context->timer.setTimerType(Qt::PreciseTimer); + updateTimerInterval(); + + m_context->elapsedTimer.start(); - auto doGrab = [&]() { - auto measure = profiler.measure(); + auto doGrab = [this]() { + auto measure = m_context->profiler.measure(); auto frame = grabFrame(); if (frame.isValid()) { - frame.setStartTime(lastFrameTime); - frame.setEndTime(elapsedTimer.nsecsElapsed() / 1000); - lastFrameTime = frame.endTime(); + frame.setStartTime(m_context->lastFrameTime); + frame.setEndTime(m_context->elapsedTimer.nsecsElapsed() / 1000); + m_context->lastFrameTime = frame.endTime(); updateError(QPlatformSurfaceCapture::NoError); @@ -111,37 +181,22 @@ void QFFmpegSurfaceCaptureThread::run() doGrab(); - m_timer->callOnTimeout(m_timer.get(), doGrab); - m_timer->start(); - - exec(); - - qCDebug(qLcScreenCaptureThread) - << "end screen capture thread; avg grabbing time:" << profiler.avgTime() - << "ms, grabbings number:" << profiler.number(); + m_context->timer.callOnTimeout(&m_context->timer, doGrab); + m_context->timer.start(); } -void QFFmpegSurfaceCaptureThread::updateError(QPlatformSurfaceCapture::Error error, - const QString &description) +void QFFmpegSurfaceCaptureThread::finalizeGrabbingContext() { - const auto prevError = std::exchange(m_prevError, error); - - if (error != QPlatformSurfaceCapture::NoError - || prevError != QPlatformSurfaceCapture::NoError) { - emit errorUpdated(error, description); - } - - updateTimerInterval(); + Q_ASSERT(isGrabbingContextInitialized()); + qCDebug(qLcScreenCaptureThread) + << "end screen capture thread; avg grabbing time:" << m_context->profiler.avgTime() + << "ms, grabbings number:" << m_context->profiler.number(); + m_context.reset(); } -void QFFmpegSurfaceCaptureThread::updateTimerInterval() +bool QFFmpegSurfaceCaptureThread::isGrabbingContextInitialized() const { - const qreal rate = m_prevError && *m_prevError != QPlatformSurfaceCapture::NoError - ? MinScreenCaptureFrameRate - : m_rate; - const int interval = static_cast<int>(1000 / rate); - if (m_timer && m_timer->interval() != interval) - m_timer->setInterval(interval); + return m_context != nullptr; } QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread_p.h b/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread_p.h index e64e261bd..9c8fb91f5 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread_p.h +++ b/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturethread_p.h @@ -15,8 +15,6 @@ // We mean it. // -#include <qthread.h> - #include "qvideoframe.h" #include "private/qplatformsurfacecapture_p.h" @@ -25,7 +23,7 @@ QT_BEGIN_NAMESPACE -class QTimer; +class QThread; static constexpr qreal DefaultScreenCaptureFrameRate = 60.; @@ -36,14 +34,15 @@ static constexpr qreal DefaultScreenCaptureFrameRate = 60.; static constexpr qreal MaxScreenCaptureFrameRate = 60.; static constexpr qreal MinScreenCaptureFrameRate = 1.; -class QFFmpegSurfaceCaptureThread : public QThread +class QFFmpegSurfaceCaptureThread : public QObject { Q_OBJECT public: - QFFmpegSurfaceCaptureThread(); + QFFmpegSurfaceCaptureThread(bool runInThread = true); ~QFFmpegSurfaceCaptureThread() override; + void start(); void stop(); template<typename Object, typename Method> @@ -58,23 +57,29 @@ signals: void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description); protected: - void run() override; - void updateError(QPlatformSurfaceCapture::Error error, const QString &description = {}); virtual QVideoFrame grabFrame() = 0; -protected: void setFrameRate(qreal rate); qreal frameRate() const; void updateTimerInterval(); + virtual void initializeGrabbingContext(); + virtual void finalizeGrabbingContext(); + + bool isGrabbingContextInitialized() const; + private: + struct GrabbingContext; + class GrabbingThread; + + std::unique_ptr<GrabbingContext> m_context; qreal m_rate = 0; - std::unique_ptr<QTimer> m_timer; std::optional<QPlatformSurfaceCapture::Error> m_prevError; + std::unique_ptr<QThread> m_thread; }; QT_END_NAMESPACE diff --git a/src/plugins/multimedia/ffmpeg/qffmpegwindowcapture_uwp.cpp b/src/plugins/multimedia/ffmpeg/qffmpegwindowcapture_uwp.cpp index b2d62306e..a5f1c76b2 100644 --- a/src/plugins/multimedia/ffmpeg/qffmpegwindowcapture_uwp.cpp +++ b/src/plugins/multimedia/ffmpeg/qffmpegwindowcapture_uwp.cpp @@ -30,6 +30,7 @@ auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout); #include "qvideoframe.h" #include <qwindow.h> +#include <qthread.h> #include <qloggingcategory.h> #include <qguiapplication.h> #include <private/qmultimediautils_p.h> @@ -280,6 +281,7 @@ private: return texture.as<IDXGISurface>(); } + MultithreadedApartment m_comApartment{}; HWND m_captureWindow{}; winrt::Windows::Graphics::SizeInt32 m_frameSize{}; com_ptr<ID3D11Device> m_device; @@ -317,19 +319,15 @@ public: protected: - void run() override + void initializeGrabbingContext() override { if (!m_adapter || !IsWindow(m_hwnd)) return; // Error already logged try { - MultithreadedApartment comApartment; - m_windowGrabber = std::make_unique<WindowGrabber>(m_adapter.get(), m_hwnd); - QFFmpegSurfaceCaptureThread::run(); - - m_windowGrabber = nullptr; + QFFmpegSurfaceCaptureThread::initializeGrabbingContext(); } catch (const winrt::hresult_error &err) { const QString message = QLatin1String("Unable to capture window: ") @@ -339,6 +337,12 @@ protected: } } + void finalizeGrabbingContext() override + { + QFFmpegSurfaceCaptureThread::finalizeGrabbingContext(); + m_windowGrabber = nullptr; + } + QVideoFrame grabFrame() override { try { diff --git a/src/plugins/multimedia/ffmpeg/qgrabwindowsurfacecapture.cpp b/src/plugins/multimedia/ffmpeg/qgrabwindowsurfacecapture.cpp index 4e49f0f7d..034dc5813 100644 --- a/src/plugins/multimedia/ffmpeg/qgrabwindowsurfacecapture.cpp +++ b/src/plugins/multimedia/ffmpeg/qgrabwindowsurfacecapture.cpp @@ -91,7 +91,7 @@ public: private: Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen, WindowUPtr window) - : m_capture(capture), m_screen(screen), m_window(std::move(window)) + : QFFmpegSurfaceCaptureThread(QGuiApplication::platformName() != QLatin1String("eglfs")), m_capture(capture), m_screen(screen), m_window(std::move(window)) { connect(qApp, &QGuiApplication::screenRemoved, this, &Grabber::onScreenRemoved); addFrameCallback(m_capture, &QGrabWindowSurfaceCapture::newVideoFrame); |