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
qgdiwindowcapture.cpp
Go to the documentation of this file.
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
5
6#include "qvideoframe.h"
8#include "private/qcapturablewindow_p.h"
9#include "private/qmemoryvideobuffer_p.h"
10#include "private/qvideoframe_p.h"
11
12#include <qt_windows.h>
13#include <QtCore/qloggingcategory.h>
14
15Q_STATIC_LOGGING_CATEGORY(qLcGdiWindowCapture, "qt.multimedia.ffmpeg.gdiwindowcapture");
16
17QT_BEGIN_NAMESPACE
18
19class QGdiWindowCapture::Grabber : public QFFmpegSurfaceCaptureGrabber
20{
21public:
22 static std::unique_ptr<Grabber> create(QGdiWindowCapture &capture, HWND hWnd)
23 {
24 auto hdcWindow = GetDC(hWnd);
25 if (!hdcWindow) {
26 capture.updateError(QPlatformSurfaceCapture::CaptureFailed,
27 QLatin1String("Cannot create a window drawing context"));
28 return nullptr;
29 }
30
31 auto hdcMem = CreateCompatibleDC(hdcWindow);
32
33 if (!hdcMem) {
34 capture.updateError(QPlatformSurfaceCapture::CaptureFailed,
35 QLatin1String("Cannot create a compatible drawing context"));
36 return nullptr;
37 }
38
39 std::unique_ptr<Grabber> result(new Grabber(capture, hWnd, hdcWindow, hdcMem));
40 if (!result->update())
41 return nullptr;
42
43 result->start();
44 return result;
45 }
46
47 ~Grabber() override
48 {
49 stop();
50
51 if (m_hBitmap)
52 DeleteObject(m_hBitmap);
53
54 if (m_hdcMem)
55 DeleteDC(m_hdcMem);
56
57 if (m_hdcWindow)
58 ReleaseDC(m_hwnd, m_hdcWindow);
59 }
60
61 QVideoFrameFormat format() const { return m_format; }
62
63private:
64 Grabber(QGdiWindowCapture &capture, HWND hWnd, HDC hdcWindow, HDC hdcMem)
65 : QFFmpegSurfaceCaptureGrabber(),
66 m_hwnd(hWnd),
67 m_hdcWindow(hdcWindow),
68 m_hdcMem(hdcMem)
69 {
70 addFrameCallback(&capture, &QGdiWindowCapture::newVideoFrame);
71 connect(this, &Grabber::errorUpdated, &capture, &QGdiWindowCapture::updateError);
72
73 const auto vrefresh = GetDeviceCaps(hdcWindow, VREFRESH);
74 const auto defaultRate = vrefresh > 0
75 ? qMin(static_cast<qreal>(vrefresh), DefaultScreenCaptureFrameRate)
76 : DefaultScreenCaptureFrameRate;
77 setFrameRate(capture.frameRate().value_or(defaultRate));
78 }
79
80 bool update()
81 {
82 RECT windowRect{};
83 if (!GetWindowRect(m_hwnd, &windowRect)) {
84 updateError(QPlatformSurfaceCapture::CaptureFailed,
85 QLatin1String("Cannot get window size"));
86 return false;
87 }
88
89 const QSize size{ windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
90
91 if (m_format.isValid() && size == m_format.frameSize() && m_hBitmap)
92 return true;
93
94 if (m_hBitmap)
95 DeleteObject(std::exchange(m_hBitmap, nullptr));
96
97 if (size.isEmpty()) {
98 m_format = {};
99 updateError(QPlatformSurfaceCapture::CaptureFailed,
100 QLatin1String("Invalid window size"));
101 return false;
102 }
103
104 m_hBitmap = CreateCompatibleBitmap(m_hdcWindow, size.width(), size.height());
105
106 if (!m_hBitmap) {
107 m_format = {};
108 updateError(QPlatformSurfaceCapture::CaptureFailed,
109 QLatin1String("Cannot create a compatible bitmap"));
110 return false;
111 }
112
113 QVideoFrameFormat format(size, QVideoFrameFormat::Format_BGRX8888);
114 format.setStreamFrameRate(frameRate());
115 m_format = format;
116 return true;
117 }
118
119 QVideoFrame grabFrame() override
120 {
121 if (!update())
122 return {};
123
124 const auto oldBitmap = SelectObject(m_hdcMem, m_hBitmap);
125 auto deselect = qScopeGuard([&]() { SelectObject(m_hdcMem, oldBitmap); });
126
127 const auto size = m_format.frameSize();
128
129 if (!BitBlt(m_hdcMem, 0, 0, size.width(), size.height(), m_hdcWindow, 0, 0, SRCCOPY)) {
130 updateError(QPlatformSurfaceCapture::CaptureFailed,
131 QLatin1String("Cannot copy image to the compatible DC"));
132 return {};
133 }
134
135 BITMAPINFO info{};
136 auto &header = info.bmiHeader;
137 header.biSize = sizeof(BITMAPINFOHEADER);
138 header.biWidth = size.width();
139 header.biHeight = -size.height(); // negative height to ensure top-down orientation
140 header.biPlanes = 1;
141 header.biBitCount = 32;
142 header.biCompression = BI_RGB;
143
144 const auto bytesPerLine = size.width() * 4;
145
146 QByteArray array(size.height() * bytesPerLine, Qt::Uninitialized);
147
148 const auto copiedHeight = GetDIBits(m_hdcMem, m_hBitmap, 0, size.height(), array.data(), &info, DIB_RGB_COLORS);
149 if (copiedHeight != size.height()) {
150 qCWarning(qLcGdiWindowCapture) << copiedHeight << "lines have been copied, expected:" << size.height();
151 // In practice, it might fail randomly first time after start. So we don't consider it as an error.
152 // TODO: investigate reasons and properly handle the error
153 // updateError(QPlatformSurfaceCapture::CaptureFailed,
154 // QLatin1String("Cannot get raw image data"));
155 return {};
156 }
157
158 if (header.biWidth != size.width() || header.biHeight != -size.height()
159 || header.biPlanes != 1 || header.biBitCount != 32 || header.biCompression != BI_RGB) {
160 updateError(QPlatformSurfaceCapture::CaptureFailed,
161 QLatin1String("Output bitmap info is unexpected"));
162 return {};
163 }
164
165 return QVideoFramePrivate::createFrame(
166 std::make_unique<QMemoryVideoBuffer>(std::move(array), bytesPerLine), m_format);
167 }
168
169private:
170 HWND m_hwnd = {};
171 QVideoFrameFormat m_format;
172 HDC m_hdcWindow = {};
173 HDC m_hdcMem = {};
174 HBITMAP m_hBitmap = {};
175};
176
177QGdiWindowCapture::QGdiWindowCapture() : QPlatformSurfaceCapture(WindowSource{}) { }
178
179QGdiWindowCapture::~QGdiWindowCapture() = default;
180
181QVideoFrameFormat QGdiWindowCapture::frameFormat() const
182{
183 return m_grabber ? m_grabber->format() : QVideoFrameFormat();
184}
185
186bool QGdiWindowCapture::setActiveInternal(bool active)
187{
188 if (active == static_cast<bool>(m_grabber))
189 return true;
190
191 if (m_grabber) {
192 m_grabber.reset();
193 } else {
194 auto window = source<WindowSource>();
195 auto handle = QCapturableWindowPrivate::handle(window);
196
197 m_grabber = Grabber::create(*this, reinterpret_cast<HWND>(handle ? handle->id : 0));
198 }
199
200 return static_cast<bool>(m_grabber) == active;
201}
202
203QT_END_NAMESPACE
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)