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 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | static Q_LOGGING_CATEGORY(qLcScreenCaptureGrabber, "qt.multimedia.ffmpeg.surfacecapturegrabber"); |
14 | |
15 | namespace { |
16 | |
17 | class GrabbingProfiler |
18 | { |
19 | public: |
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 | |
45 | private: |
46 | QElapsedTimer m_elapsedTimer; |
47 | qint64 m_wholeTime = 0; |
48 | qint64 m_number = 0; |
49 | }; |
50 | |
51 | } // namespace |
52 | |
53 | struct QFFmpegSurfaceCaptureGrabber::GrabbingContext |
54 | { |
55 | GrabbingProfiler profiler; |
56 | QTimer timer; |
57 | QElapsedTimer elapsedTimer; |
58 | qint64 lastFrameTime = 0; |
59 | }; |
60 | |
61 | class QFFmpegSurfaceCaptureGrabber::GrabbingThread : public QThread |
62 | { |
63 | public: |
64 | GrabbingThread(QFFmpegSurfaceCaptureGrabber& grabber) |
65 | : m_grabber(grabber) |
66 | {} |
67 | |
68 | protected: |
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 | |
80 | private: |
81 | QFFmpegSurfaceCaptureGrabber& m_grabber; |
82 | }; |
83 | |
84 | QFFmpegSurfaceCaptureGrabber::QFFmpegSurfaceCaptureGrabber(ThreadPolicy threadPolicy) |
85 | { |
86 | setFrameRate(DefaultScreenCaptureFrameRate); |
87 | |
88 | if (threadPolicy == CreateGrabbingThread) |
89 | m_thread = std::make_unique<GrabbingThread>(args&: *this); |
90 | } |
91 | |
92 | void QFFmpegSurfaceCaptureGrabber::start() |
93 | { |
94 | if (m_thread) |
95 | m_thread->start(); |
96 | else if (!isGrabbingContextInitialized()) |
97 | initializeGrabbingContext(); |
98 | } |
99 | |
100 | QFFmpegSurfaceCaptureGrabber::~QFFmpegSurfaceCaptureGrabber() = default; |
101 | |
102 | void 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 | |
112 | qreal QFFmpegSurfaceCaptureGrabber::frameRate() const |
113 | { |
114 | return m_rate; |
115 | } |
116 | |
117 | void 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 | |
130 | void 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 | |
143 | void 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 | |
153 | void 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 | |
186 | void 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 | |
195 | bool QFFmpegSurfaceCaptureGrabber::isGrabbingContextInitialized() const |
196 | { |
197 | return m_context != nullptr; |
198 | } |
199 | |
200 | QT_END_NAMESPACE |
201 | |
202 | #include "moc_qffmpegsurfacecapturegrabber_p.cpp" |
203 |