1// Copyright (C) 2022 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 "qvideoframe.h"
5#include "qgrabwindowsurfacecapture_p.h"
6#include "qscreencapture.h"
7#include "qffmpegsurfacecapturegrabber_p.h"
8
9#include "private/qimagevideobuffer_p.h"
10#include "private/qcapturablewindow_p.h"
11#include "private/qvideoframe_p.h"
12
13#include "qscreen.h"
14#include "qmutex.h"
15#include "qwaitcondition.h"
16#include "qpixmap.h"
17#include "qguiapplication.h"
18#include "qwindow.h"
19#include "qpointer.h"
20
21#include <QtCore/qloggingcategory.h>
22
23QT_BEGIN_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27namespace {
28
29using WindowUPtr = std::unique_ptr<QWindow>;
30
31} // namespace
32
33class QGrabWindowSurfaceCapture::Grabber : public QFFmpegSurfaceCaptureGrabber
34{
35public:
36 Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen) : Grabber(capture, screen, nullptr)
37 {
38 Q_ASSERT(screen);
39 }
40
41 Grabber(QGrabWindowSurfaceCapture &capture, WindowUPtr window)
42 : Grabber(capture, nullptr, std::move(window))
43 {
44 Q_ASSERT(m_window);
45 }
46
47 ~Grabber() override {
48 stop();
49
50 Q_ASSERT(!m_screenRemovingLocked);
51 }
52
53 const QVideoFrameFormat &format()
54 {
55 QMutexLocker locker(&m_formatMutex);
56 while (!m_format)
57 m_waitForFormat.wait(lockedMutex: &m_formatMutex);
58 return *m_format;
59 }
60
61private:
62 Grabber(QGrabWindowSurfaceCapture &capture, QScreen *screen, WindowUPtr window)
63 : QFFmpegSurfaceCaptureGrabber(
64 QGuiApplication::platformName() == QLatin1String("eglfs")
65 ? QFFmpegSurfaceCaptureGrabber::UseCurrentThread
66 : QFFmpegSurfaceCaptureGrabber::CreateGrabbingThread),
67 m_capture(capture),
68 m_screen(screen),
69 m_window(std::move(window))
70 {
71 connect(qApp, signal: &QGuiApplication::screenRemoved, context: this, slot: &Grabber::onScreenRemoved);
72 addFrameCallback(object&: m_capture, method: &QGrabWindowSurfaceCapture::newVideoFrame);
73 connect(sender: this, signal: &Grabber::errorUpdated, context: &m_capture, slot: &QGrabWindowSurfaceCapture::updateError);
74 }
75
76 void onScreenRemoved(QScreen *screen)
77 {
78 /* The hack allows to lock screens removing while QScreen::grabWindow is in progress.
79 * The current solution works since QGuiApplication::screenRemoved is emitted from
80 * the destructor of QScreen before destruction members of the object.
81 * Note, QGuiApplication works with screens in the main thread, and any removing of a screen
82 * must be synchronized with grabbing thread.
83 */
84 QMutexLocker locker(&m_screenRemovingMutex);
85
86 if (m_screenRemovingLocked) {
87 qDebug() << "Screen" << screen->name()
88 << "is removed while screen window grabbing lock is active";
89 }
90
91 while (m_screenRemovingLocked)
92 m_screenRemovingWc.wait(lockedMutex: &m_screenRemovingMutex);
93 }
94
95 void setScreenRemovingLocked(bool locked)
96 {
97 Q_ASSERT(locked != m_screenRemovingLocked);
98
99 {
100 QMutexLocker locker(&m_screenRemovingMutex);
101 m_screenRemovingLocked = locked;
102 }
103
104 if (!locked)
105 m_screenRemovingWc.wakeAll();
106 }
107
108 void updateFormat(const QVideoFrameFormat &format)
109 {
110 if (m_format && m_format->isValid())
111 return;
112
113 {
114 QMutexLocker locker(&m_formatMutex);
115 m_format = format;
116 }
117
118 m_waitForFormat.wakeAll();
119 }
120
121 QVideoFrame grabFrame() override
122 {
123 setScreenRemovingLocked(true);
124 auto screenGuard = qScopeGuard(f: [this] {
125 setScreenRemovingLocked(false);
126 });
127
128 WId wid = m_window ? m_window->winId() : 0;
129 QScreen *screen = m_window ? m_window->screen() : m_screen ? m_screen.data() : nullptr;
130
131 if (!screen) {
132 updateError(error: QPlatformSurfaceCapture::CaptureFailed, description: u"Screen not found"_s);
133 return {};
134 }
135
136 setFrameRate(screen->refreshRate());
137
138 QPixmap p = screen->grabWindow(window: wid);
139 auto buffer = std::make_unique<QImageVideoBuffer>(args: p.toImage());
140 const auto img = buffer->underlyingImage();
141
142 QVideoFrameFormat format(img.size(),
143 QVideoFrameFormat::pixelFormatFromImageFormat(format: img.format()));
144 format.setStreamFrameRate(screen->refreshRate());
145 updateFormat(format);
146
147 if (!format.isValid()) {
148 updateError(error: QPlatformSurfaceCapture::CaptureFailed,
149 description: u"Failed to grab the screen content"_s);
150 return {};
151 }
152
153 return QVideoFramePrivate::createFrame(buffer: std::move(buffer), format: std::move(format));
154 }
155
156private:
157 QGrabWindowSurfaceCapture &m_capture;
158 QPointer<QScreen> m_screen;
159 WindowUPtr m_window;
160
161 QMutex m_formatMutex;
162 QWaitCondition m_waitForFormat;
163 std::optional<QVideoFrameFormat> m_format;
164
165 QMutex m_screenRemovingMutex;
166 bool m_screenRemovingLocked = false;
167 QWaitCondition m_screenRemovingWc;
168};
169
170QGrabWindowSurfaceCapture::QGrabWindowSurfaceCapture(Source initialSource)
171 : QPlatformSurfaceCapture(initialSource)
172{
173}
174
175QGrabWindowSurfaceCapture::~QGrabWindowSurfaceCapture() = default;
176
177QVideoFrameFormat QGrabWindowSurfaceCapture::frameFormat() const
178{
179 if (m_grabber)
180 return m_grabber->format();
181 else
182 return {};
183}
184
185bool QGrabWindowSurfaceCapture::setActiveInternal(bool active)
186{
187 if (active == static_cast<bool>(m_grabber))
188 return true;
189
190 if (m_grabber)
191 m_grabber.reset();
192 else
193 std::visit(visitor: [this](auto source) { activate(source); }, variants: source());
194
195 return static_cast<bool>(m_grabber) == active;
196}
197
198void QGrabWindowSurfaceCapture::activate(ScreenSource screen)
199{
200 if (!checkScreenWithError(screen))
201 return;
202
203 m_grabber = std::make_unique<Grabber>(args&: *this, args&: screen);
204 m_grabber->start();
205}
206
207void QGrabWindowSurfaceCapture::activate(WindowSource window)
208{
209 auto handle = QCapturableWindowPrivate::handle(window);
210 auto wid = handle ? handle->id : 0;
211 if (auto wnd = WindowUPtr(QWindow::fromWinId(id: wid))) {
212 if (!wnd->screen()) {
213 updateError(error: InternalError,
214 errorString: u"Window " + QString::number(wid) + u" doesn't belong to any screen");
215 } else {
216 m_grabber = std::make_unique<Grabber>(args&: *this, args: std::move(wnd));
217 m_grabber->start();
218 }
219 } else {
220 updateError(error: NotFound,
221 errorString: u"Window " + QString::number(wid) + u"doesn't exist or permissions denied");
222 }
223}
224
225QT_END_NAMESPACE
226

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