// 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 "qffmpegsurfacecapturegrabber_p.h" #include #include #include #include QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(qLcScreenCaptureGrabber, "qt.multimedia.ffmpeg.surfacecapturegrabber"); namespace { class GrabbingProfiler { public: auto measure() { m_elapsedTimer.start(); return qScopeGuard([&]() { const auto nsecsElapsed = m_elapsedTimer.nsecsElapsed(); ++m_number; m_wholeTime += nsecsElapsed; #ifdef DUMP_SCREEN_CAPTURE_PROFILING qDebug() << "screen grabbing time:" << nsecsElapsed << "avg:" << avgTime() << "number:" << m_number; #endif }); } qreal avgTime() const { return m_number ? m_wholeTime / (m_number * 1000000.) : 0.; } qint64 number() const { return m_number; } private: QElapsedTimer m_elapsedTimer; qint64 m_wholeTime = 0; qint64 m_number = 0; }; } // namespace struct QFFmpegSurfaceCaptureGrabber::GrabbingContext { GrabbingProfiler profiler; QTimer timer; QElapsedTimer elapsedTimer; qint64 lastFrameTime = 0; }; class QFFmpegSurfaceCaptureGrabber::GrabbingThread : public QThread { public: GrabbingThread(QFFmpegSurfaceCaptureGrabber& grabber) : m_grabber(grabber) {} protected: void run() override { m_grabber.initializeGrabbingContext(); if (!m_grabber.isGrabbingContextInitialized()) return; exec(); m_grabber.finalizeGrabbingContext(); } private: QFFmpegSurfaceCaptureGrabber& m_grabber; }; QFFmpegSurfaceCaptureGrabber::QFFmpegSurfaceCaptureGrabber(bool runInThread) { setFrameRate(DefaultScreenCaptureFrameRate); if (!runInThread) return; m_thread = std::make_unique(*this); } void QFFmpegSurfaceCaptureGrabber::start() { if (m_thread) m_thread->start(); else if (!isGrabbingContextInitialized()) initializeGrabbingContext(); } QFFmpegSurfaceCaptureGrabber::~QFFmpegSurfaceCaptureGrabber() = default; void QFFmpegSurfaceCaptureGrabber::setFrameRate(qreal rate) { rate = qBound(MinScreenCaptureFrameRate, rate, MaxScreenCaptureFrameRate); if (std::exchange(m_rate, rate) != rate) { qCDebug(qLcScreenCaptureGrabber) << "Screen capture rate has been changed:" << m_rate; updateTimerInterval(); } } qreal QFFmpegSurfaceCaptureGrabber::frameRate() const { return m_rate; } void QFFmpegSurfaceCaptureGrabber::stop() { if (m_thread) { m_thread->quit(); m_thread->wait(); } else if (isGrabbingContextInitialized()) { finalizeGrabbingContext(); } } void QFFmpegSurfaceCaptureGrabber::updateError(QPlatformSurfaceCapture::Error error, const QString &description) { const auto prevError = std::exchange(m_prevError, error); if (error != QPlatformSurfaceCapture::NoError || prevError != QPlatformSurfaceCapture::NoError) { emit errorUpdated(error, description); } updateTimerInterval(); } void QFFmpegSurfaceCaptureGrabber::updateTimerInterval() { const qreal rate = m_prevError && *m_prevError != QPlatformSurfaceCapture::NoError ? MinScreenCaptureFrameRate : m_rate; const int interval = static_cast(1000 / rate); if (m_context && m_context->timer.interval() != interval) m_context->timer.setInterval(interval); } void QFFmpegSurfaceCaptureGrabber::initializeGrabbingContext() { Q_ASSERT(!isGrabbingContextInitialized()); qCDebug(qLcScreenCaptureGrabber) << "screen capture started"; m_context = std::make_unique(); m_context->timer.setTimerType(Qt::PreciseTimer); updateTimerInterval(); m_context->elapsedTimer.start(); auto doGrab = [this]() { auto measure = m_context->profiler.measure(); auto frame = grabFrame(); if (frame.isValid()) { frame.setStartTime(m_context->lastFrameTime); frame.setEndTime(m_context->elapsedTimer.nsecsElapsed() / 1000); m_context->lastFrameTime = frame.endTime(); updateError(QPlatformSurfaceCapture::NoError); emit frameGrabbed(frame); } }; doGrab(); m_context->timer.callOnTimeout(&m_context->timer, doGrab); m_context->timer.start(); } void QFFmpegSurfaceCaptureGrabber::finalizeGrabbingContext() { Q_ASSERT(isGrabbingContextInitialized()); qCDebug(qLcScreenCaptureGrabber) << "end screen capture thread; avg grabbing time:" << m_context->profiler.avgTime() << "ms, grabbings number:" << m_context->profiler.number(); m_context.reset(); } bool QFFmpegSurfaceCaptureGrabber::isGrabbingContextInitialized() const { return m_context != nullptr; } QT_END_NAMESPACE #include "moc_qffmpegsurfacecapturegrabber_p.cpp"