1 | // Copyright (C) 2023 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 "qeglfsscreencapture_p.h" |
5 | |
6 | #include "qffmpegsurfacecapturegrabber_p.h" |
7 | #include "qguiapplication.h" |
8 | #include "qopenglvideobuffer_p.h" |
9 | #include "private/qimagevideobuffer_p.h" |
10 | #include "private/qvideoframe_p.h" |
11 | |
12 | #include <QtOpenGL/private/qopenglcompositor_p.h> |
13 | #include <QtOpenGL/private/qopenglframebufferobject_p.h> |
14 | |
15 | #include <QtQuick/qquickwindow.h> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | class QEglfsScreenCapture::Grabber : public QFFmpegSurfaceCaptureGrabber |
20 | { |
21 | public: |
22 | Grabber(QEglfsScreenCapture &screenCapture, QScreen *screen) |
23 | : QFFmpegSurfaceCaptureGrabber(QFFmpegSurfaceCaptureGrabber::UseCurrentThread) |
24 | { |
25 | addFrameCallback(object&: screenCapture, method: &QEglfsScreenCapture::newVideoFrame); |
26 | connect(sender: this, signal: &Grabber::errorUpdated, context: &screenCapture, slot: &QEglfsScreenCapture::updateError); |
27 | // Limit frame rate to 30 fps for performance reasons, |
28 | // to be reviewed at the next optimization round |
29 | setFrameRate(std::min(a: screen->refreshRate(), b: qreal(30.0))); |
30 | } |
31 | |
32 | ~Grabber() override { stop(); } |
33 | |
34 | QVideoFrameFormat format() { return m_format; } |
35 | |
36 | protected: |
37 | QVideoFrame grabFrame() override |
38 | { |
39 | auto nativeSize = QOpenGLCompositor::instance()->nativeTargetGeometry().size(); |
40 | auto fbo = std::make_unique<QOpenGLFramebufferObject>(args&: nativeSize); |
41 | |
42 | if (!QOpenGLCompositor::instance()->grabToFrameBufferObject( |
43 | fbo: fbo.get(), orientation: QOpenGLCompositor::NotFlipped)) { |
44 | updateError(error: Error::InternalError, description: QLatin1String("Couldn't grab to framebuffer object" )); |
45 | return {}; |
46 | } |
47 | |
48 | if (!fbo->isValid()) { |
49 | updateError(error: Error::InternalError, description: QLatin1String("Framebuffer object invalid" )); |
50 | return {}; |
51 | } |
52 | |
53 | auto videoBuffer = std::make_unique<QOpenGLVideoBuffer>(args: std::move(fbo)); |
54 | |
55 | if (!m_format.isValid()) { |
56 | auto image = videoBuffer->ensureImageBuffer().underlyingImage(); |
57 | m_format = { image.size(), QVideoFrameFormat::pixelFormatFromImageFormat(format: image.format()) }; |
58 | m_format.setStreamFrameRate(frameRate()); |
59 | } |
60 | |
61 | return QVideoFramePrivate::createFrame(buffer: std::move(videoBuffer), format: m_format); |
62 | } |
63 | |
64 | QVideoFrameFormat m_format; |
65 | }; |
66 | |
67 | class QEglfsScreenCapture::QuickGrabber : public Grabber |
68 | { |
69 | public: |
70 | QuickGrabber(QEglfsScreenCapture &screenCapture, QScreen *screen, QQuickWindow *quickWindow) |
71 | : Grabber(screenCapture, screen), m_quickWindow(quickWindow) |
72 | { |
73 | Q_ASSERT(m_quickWindow); |
74 | } |
75 | |
76 | protected: |
77 | QVideoFrame grabFrame() override |
78 | { |
79 | if (!m_quickWindow) { |
80 | updateError(error: Error::InternalError, description: QLatin1String("Window deleted" )); |
81 | return {}; |
82 | } |
83 | |
84 | QImage image = m_quickWindow->grabWindow(); |
85 | |
86 | if (image.isNull()) { |
87 | updateError(error: Error::InternalError, description: QLatin1String("Image invalid" )); |
88 | return {}; |
89 | } |
90 | |
91 | if (!m_format.isValid()) { |
92 | m_format = { image.size(), |
93 | QVideoFrameFormat::pixelFormatFromImageFormat(format: image.format()) }; |
94 | m_format.setStreamFrameRate(frameRate()); |
95 | } |
96 | |
97 | return QVideoFramePrivate::createFrame( |
98 | buffer: std::make_unique<QImageVideoBuffer>(args: std::move(image)), format: m_format); |
99 | } |
100 | |
101 | private: |
102 | QPointer<QQuickWindow> m_quickWindow; |
103 | }; |
104 | |
105 | QEglfsScreenCapture::QEglfsScreenCapture() : QPlatformSurfaceCapture(ScreenSource{}) { } |
106 | |
107 | QEglfsScreenCapture::~QEglfsScreenCapture() = default; |
108 | |
109 | QVideoFrameFormat QEglfsScreenCapture::frameFormat() const |
110 | { |
111 | return m_grabber ? m_grabber->format() : QVideoFrameFormat(); |
112 | } |
113 | |
114 | bool QEglfsScreenCapture::setActiveInternal(bool active) |
115 | { |
116 | if (static_cast<bool>(m_grabber) == active) |
117 | return true; |
118 | |
119 | if (m_grabber) |
120 | m_grabber.reset(); |
121 | |
122 | if (!active) |
123 | return true; |
124 | |
125 | m_grabber = createGrabber(); |
126 | |
127 | if (!m_grabber) { |
128 | // TODO: This could mean that the UI is not started yet, so we should wait and try again, |
129 | // and then give error if still not started. Might not be possible here. |
130 | return false; |
131 | } |
132 | |
133 | m_grabber->start(); |
134 | return true; |
135 | } |
136 | |
137 | bool QEglfsScreenCapture::isSupported() |
138 | { |
139 | return QGuiApplication::platformName() == QLatin1String("eglfs" ); |
140 | } |
141 | |
142 | std::unique_ptr<QEglfsScreenCapture::Grabber> QEglfsScreenCapture::createGrabber() |
143 | { |
144 | auto screen = source<ScreenSource>(); |
145 | if (!checkScreenWithError(screen)) |
146 | return nullptr; |
147 | |
148 | QOpenGLCompositor *compositor = QOpenGLCompositor::instance(); |
149 | |
150 | if (compositor->context()) { |
151 | // Create OpenGL grabber |
152 | if (!compositor->targetWindow()) { |
153 | updateError(error: Error::CaptureFailed, |
154 | errorString: QLatin1String("Target window is not set for OpenGL compositor" )); |
155 | return nullptr; |
156 | } |
157 | |
158 | return std::make_unique<Grabber>(args&: *this, args&: screen); |
159 | } |
160 | |
161 | // Check for QQuickWindow |
162 | auto windows = QGuiApplication::topLevelWindows(); |
163 | auto it = std::find_if(first: windows.begin(), last: windows.end(), pred: [screen](QWindow *window) { |
164 | auto quickWindow = qobject_cast<QQuickWindow *>(object: window); |
165 | if (!quickWindow) |
166 | return false; |
167 | |
168 | return quickWindow->screen() == screen; |
169 | }); |
170 | |
171 | if (it != windows.end()) { |
172 | // Create grabber that calls QQuickWindow::grabWindow |
173 | return std::make_unique<QuickGrabber>(args&: *this, args&: screen, args: qobject_cast<QQuickWindow *>(object: *it)); |
174 | } |
175 | |
176 | updateError(error: Error::CaptureFailed, errorString: QLatin1String("No existing OpenGL context or QQuickWindow" )); |
177 | return nullptr; |
178 | } |
179 | |
180 | QT_END_NAMESPACE |
181 | |