1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qffmpegsurfacecapturegrabber_p.h"
5
6#include <qelapsedtimer.h>
7#include <qloggingcategory.h>
8#include <qthread.h>
9#include <qtimer.h>
10
11QT_BEGIN_NAMESPACE
12
13Q_STATIC_LOGGING_CATEGORY(qLcScreenCaptureGrabber, "qt.multimedia.ffmpeg.surfacecapturegrabber");
14
15class QFFmpegSurfaceCaptureGrabber::GrabbingProfiler
16{
17public:
18 auto measure()
19 {
20 m_elapsedTimer.start();
21 return qScopeGuard(f: [&]() {
22 const auto nsecsElapsed = m_elapsedTimer.nsecsElapsed();
23 ++m_number;
24 m_wholeTime += nsecsElapsed;
25
26#ifdef DUMP_SCREEN_CAPTURE_PROFILING
27 qDebug() << "screen grabbing time:" << nsecsElapsed << "avg:" << avgTime()
28 << "number:" << m_number;
29#endif
30 });
31 }
32
33 qreal avgTime() const
34 {
35 return m_number ? m_wholeTime / (m_number * 1000000.) : 0.;
36 }
37
38 qint64 number() const
39 {
40 return m_number;
41 }
42
43private:
44 QElapsedTimer m_elapsedTimer;
45 qint64 m_wholeTime = 0;
46 qint64 m_number = 0;
47};
48
49struct QFFmpegSurfaceCaptureGrabber::GrabbingContext
50{
51 GrabbingProfiler profiler;
52 QTimer timer;
53 QElapsedTimer elapsedTimer;
54 qint64 lastFrameTime = 0;
55};
56
57class QFFmpegSurfaceCaptureGrabber::GrabbingThread : public QThread
58{
59public:
60 GrabbingThread(QFFmpegSurfaceCaptureGrabber& grabber)
61 : m_grabber(grabber)
62 {}
63
64protected:
65 void run() override
66 {
67 m_grabber.initializeGrabbingContext();
68
69 if (!m_grabber.isGrabbingContextInitialized())
70 return;
71
72 exec();
73 m_grabber.finalizeGrabbingContext();
74 }
75
76private:
77 QFFmpegSurfaceCaptureGrabber& m_grabber;
78};
79
80QFFmpegSurfaceCaptureGrabber::QFFmpegSurfaceCaptureGrabber(ThreadPolicy threadPolicy)
81{
82 setFrameRate(DefaultScreenCaptureFrameRate);
83
84 if (threadPolicy == CreateGrabbingThread)
85 m_thread = std::make_unique<GrabbingThread>(args&: *this);
86}
87
88void QFFmpegSurfaceCaptureGrabber::start()
89{
90 if (m_thread)
91 m_thread->start();
92 else if (!isGrabbingContextInitialized())
93 initializeGrabbingContext();
94}
95
96QFFmpegSurfaceCaptureGrabber::~QFFmpegSurfaceCaptureGrabber() = default;
97
98void QFFmpegSurfaceCaptureGrabber::setFrameRate(qreal rate)
99{
100 rate = qBound(min: MinScreenCaptureFrameRate, val: rate, max: MaxScreenCaptureFrameRate);
101 if (std::exchange(obj&: m_rate, new_val&: rate) != rate) {
102 qCDebug(qLcScreenCaptureGrabber) << "Screen capture rate has been changed:" << m_rate;
103
104 updateTimerInterval();
105 }
106}
107
108qreal QFFmpegSurfaceCaptureGrabber::frameRate() const
109{
110 return m_rate;
111}
112
113void QFFmpegSurfaceCaptureGrabber::stop()
114{
115 if (m_thread)
116 {
117 m_thread->quit();
118 m_thread->wait();
119 }
120 else if (isGrabbingContextInitialized())
121 {
122 finalizeGrabbingContext();
123 }
124}
125
126void QFFmpegSurfaceCaptureGrabber::updateError(QPlatformSurfaceCapture::Error error,
127 const QString &description)
128{
129 const auto prevError = std::exchange(obj&: m_prevError, new_val&: error);
130
131 if (error != QPlatformSurfaceCapture::NoError
132 || prevError != QPlatformSurfaceCapture::NoError) {
133 emit errorUpdated(error, description);
134 }
135
136 updateTimerInterval();
137}
138
139void QFFmpegSurfaceCaptureGrabber::updateTimerInterval()
140{
141 const qreal rate = m_prevError && *m_prevError != QPlatformSurfaceCapture::NoError
142 ? MinScreenCaptureFrameRate
143 : m_rate;
144 const int interval = static_cast<int>(1000 / rate);
145 if (m_context && m_context->timer.interval() != interval)
146 m_context->timer.setInterval(interval);
147}
148
149void QFFmpegSurfaceCaptureGrabber::initializeGrabbingContext()
150{
151 Q_ASSERT(!isGrabbingContextInitialized());
152 qCDebug(qLcScreenCaptureGrabber) << "screen capture started";
153
154 m_context = std::make_unique<GrabbingContext>();
155 m_context->timer.setTimerType(Qt::PreciseTimer);
156 updateTimerInterval();
157
158 m_context->elapsedTimer.start();
159
160 auto doGrab = [this]() {
161 auto measure = m_context->profiler.measure();
162
163 auto frame = grabFrame();
164
165 if (frame.isValid()) {
166 frame.setStartTime(m_context->lastFrameTime);
167 frame.setEndTime(m_context->elapsedTimer.nsecsElapsed() / 1000);
168 m_context->lastFrameTime = frame.endTime();
169
170 updateError(error: QPlatformSurfaceCapture::NoError);
171
172 emit frameGrabbed(frame);
173 }
174 };
175
176 doGrab();
177
178 m_context->timer.callOnTimeout(args: &m_context->timer, args&: doGrab);
179 m_context->timer.start();
180}
181
182void QFFmpegSurfaceCaptureGrabber::finalizeGrabbingContext()
183{
184 Q_ASSERT(isGrabbingContextInitialized());
185 qCDebug(qLcScreenCaptureGrabber)
186 << "end screen capture thread; avg grabbing time:" << m_context->profiler.avgTime()
187 << "ms, grabbings number:" << m_context->profiler.number();
188 m_context.reset();
189}
190
191bool QFFmpegSurfaceCaptureGrabber::isGrabbingContextInitialized() const
192{
193 return m_context != nullptr;
194}
195
196QT_END_NAMESPACE
197
198#include "moc_qffmpegsurfacecapturegrabber_p.cpp"
199

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegsurfacecapturegrabber.cpp