Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsurfacecapturegrabber.cpp
Go to the documentation of this file.
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
5
6#include <QtCore/qchronotimer.h>
7#include <QtCore/qelapsedtimer.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qmetaobject.h>
10#include <QtCore/qthread.h>
11#include <QtCore/qtimer.h>
12
13#include <mutex>
14
16
17Q_STATIC_LOGGING_CATEGORY(qLcScreenCaptureGrabber, "qt.multimedia.ffmpeg.surfacecapturegrabber");
18
20{
21public:
22 auto measure()
23 {
24 m_elapsedTimer.start();
25 return qScopeGuard([&]() {
26 const auto nsecsElapsed = m_elapsedTimer.nsecsElapsed();
27 ++m_number;
28 m_wholeTime += nsecsElapsed;
29
30#ifdef DUMP_SCREEN_CAPTURE_PROFILING
31 qDebug() << "screen grabbing time:" << nsecsElapsed << "avg:" << avgTime()
32 << "number:" << m_number;
33#endif
34 });
35 }
36
37 qreal avgTime() const
38 {
39 return m_number ? m_wholeTime / (m_number * 1000000.) : 0.;
40 }
41
42 qint64 number() const
43 {
44 return m_number;
45 }
46
47private:
48 QElapsedTimer m_elapsedTimer;
49 qint64 m_wholeTime = 0;
50 qint64 m_number = 0;
51};
52
53struct QSurfaceCaptureGrabber::GrabbingContext
54{
55 GrabbingProfiler profiler;
56 QChronoTimer timer;
57 QElapsedTimer elapsedTimer;
58 qint64 lastFrameTime = 0;
59};
60
61class QSurfaceCaptureGrabber::GrabbingThread : public QThread
62{
63public:
64 GrabbingThread(QSurfaceCaptureGrabber& 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 QSurfaceCaptureGrabber& m_grabber;
82};
83
84QSurfaceCaptureGrabber::QSurfaceCaptureGrabber(ThreadPolicy threadPolicy)
85{
86 setFrameRate(DefaultScreenCaptureFrameRate);
87
88 if (threadPolicy == CreateGrabbingThread)
89 m_thread = std::make_unique<GrabbingThread>(*this);
90}
91
93{
94 Q_ASSERT(thread()->isCurrentThread());
95 {
96 // Locked: pairs with the read in updateTimerInterval().
97 std::lock_guard locker{ m_mutex };
98 m_activeRate = m_rate;
99 }
100
101 if (m_thread)
102 m_thread->start();
103 else if (!isGrabbingContextInitialized())
104 initializeGrabbingContext();
105}
106
107QSurfaceCaptureGrabber::~QSurfaceCaptureGrabber() = default;
108
109void QSurfaceCaptureGrabber::setFrameRate(std::optional<qreal> rate)
110{
111 if (!rate)
112 rate = DefaultScreenCaptureFrameRate;
113
114 // Bound to a minimum
115 if (*rate < MinScreenCaptureFrameRate)
116 rate = MinScreenCaptureFrameRate;
117
118 // New rate applies to the next stream only.
119 Q_ASSERT(thread()->isCurrentThread());
120 Q_ASSERT(!isGrabbingContextInitialized());
121 if (m_rate == *rate)
122 return;
123
124 m_rate = *rate;
125 m_activeRate = *rate; // Also visible pre-start via activeFrameRate().
126 qCDebug(qLcScreenCaptureGrabber) << "Screen capture rate has been changed:" << *rate;
127}
128
129qreal QSurfaceCaptureGrabber::frameRate() const
130{
131 Q_ASSERT(thread()->isCurrentThread());
132 return m_rate;
133}
134
135qreal QSurfaceCaptureGrabber::activeFrameRate() const
136{
137 // Owning thread before start(), grabbing thread after.
138 return m_activeRate;
139}
140
142{
143 if (m_thread)
144 {
145 m_thread->quit();
146 m_thread->wait();
147 }
148 else if (isGrabbingContextInitialized())
149 {
150 finalizeGrabbingContext();
151 }
152}
153
154void QSurfaceCaptureGrabber::updateError(QPlatformSurfaceCapture::Error error,
155 const QString &description)
156{
157 std::optional<QPlatformSurfaceCapture::Error> prevError;
158 {
159 std::lock_guard locker{ m_mutex };
160 prevError = std::exchange(m_prevError, error);
161 }
162
163 if (error != QPlatformSurfaceCapture::Error::NoError
164 || prevError != QPlatformSurfaceCapture::Error::NoError) {
165 Q_EMIT errorUpdated(error, description);
166 }
167
168 updateTimerInterval();
169}
170
171void QSurfaceCaptureGrabber::updateTimerInterval()
172{
173 using namespace std::chrono;
174
175 std::lock_guard locker{ m_mutex };
176 if (!m_context)
177 return;
178
179 const qreal rate = m_prevError && *m_prevError != QPlatformSurfaceCapture::Error::NoError
180 ? MinScreenCaptureFrameRate
181 : m_activeRate;
182 const nanoseconds interval = round<nanoseconds>(nanoseconds(1s) / rate);
183
184 if (m_timerInterval == interval)
185 return;
186
187 QChronoTimer *timer = &m_context->timer;
188 if (timer->thread()->isCurrentThread()) {
189 timer->setInterval(interval);
190 m_timerInterval = interval;
191 } else {
192 const quint64 generation = m_contextGeneration;
193 QMetaObject::invokeMethod(timer, [this, interval, generation] {
194 setTimerInterval(interval, generation);
195 }, Qt::QueuedConnection);
196 }
197}
198
199void QSurfaceCaptureGrabber::setTimerInterval(std::chrono::nanoseconds interval, quint64 generation)
200{
201 std::lock_guard locker{ m_mutex };
202 if (!m_context || generation != m_contextGeneration || m_timerInterval == interval)
203 return;
204
205 m_context->timer.setInterval(interval);
206 m_timerInterval = interval;
207}
208
209void QSurfaceCaptureGrabber::initializeGrabbingContext()
210{
211 Q_ASSERT(!isGrabbingContextInitialized());
212 qCDebug(qLcScreenCaptureGrabber) << "screen capture started";
213
214 {
215 std::lock_guard locker{ m_mutex };
216 m_context = std::make_unique<GrabbingContext>();
217 ++m_contextGeneration;
218 m_timerInterval = std::chrono::nanoseconds{ 0 };
219 }
220 m_context->timer.setTimerType(Qt::PreciseTimer);
221 updateTimerInterval();
222
223 m_context->elapsedTimer.start();
224
225 auto doGrab = [this]() {
226 auto measure = m_context->profiler.measure();
227
228 auto frame = grabFrame();
229
230 if (frame.isValid()) {
231 frame.setStartTime(m_context->lastFrameTime);
232 frame.setEndTime(m_context->elapsedTimer.nsecsElapsed() / 1000);
233 m_context->lastFrameTime = frame.endTime();
234
235 updateError(QPlatformSurfaceCapture::Error::NoError);
236
237 Q_EMIT frameGrabbed(frame);
238 }
239 };
240
241 doGrab();
242
243 m_context->timer.callOnTimeout(&m_context->timer, doGrab);
244 m_context->timer.start();
245}
246
247void QSurfaceCaptureGrabber::finalizeGrabbingContext()
248{
249 Q_ASSERT(isGrabbingContextInitialized());
250 qCDebug(qLcScreenCaptureGrabber)
251 << "end screen capture thread; avg grabbing time:" << m_context->profiler.avgTime()
252 << "ms, grabbings number:" << m_context->profiler.number();
253 {
254 // Bump the generation first so queued interval updates posted from
255 // another thread are dropped instead of touching a dead timer.
256 std::lock_guard locker{ m_mutex };
257 ++m_contextGeneration;
258 m_timerInterval = std::chrono::nanoseconds{ 0 };
259 m_context.reset();
260 }
261}
262
263bool QSurfaceCaptureGrabber::isGrabbingContextInitialized() const
264{
265 std::lock_guard locker{ m_mutex };
266 return m_context != nullptr;
267}
268
269void QSurfaceCaptureGrabber::injectContextToGrabbingThread(QObject *context)
270{
271 Q_ASSERT(m_thread);
272 Q_ASSERT(QThread::currentThread() == context->thread());
273 context->moveToThread(m_thread.get());
274}
275
276QT_END_NAMESPACE
277
278#include "moc_qsurfacecapturegrabber_p.cpp"
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)