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

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