Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qffmpegwindowcapture_uwp.cpp
Go to the documentation of this file.
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
6#include <private/qabstractvideobuffer_p.h>
7
8#include <unknwn.h>
9#include <winrt/base.h>
10#include <QtCore/private/qfactorycacheregistration_p.h>
11// Workaround for Windows SDK bug.
12// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
13namespace winrt::impl
14{
15template <typename Async>
16auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
17}
18#include <winrt/Windows.Foundation.h>
19#include <winrt/Windows.Foundation.Collections.h>
20#include <winrt/Windows.Graphics.Capture.h>
21#include <winrt/Windows.Graphics.DirectX.h>
22#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
23#include <Windows.Graphics.Capture.h>
24#include <Windows.Graphics.Capture.Interop.h>
25#include <windows.graphics.directx.direct3d11.interop.h>
26
27#include <D3d11.h>
28#include <dxgi1_2.h>
29#include <dwmapi.h>
30#include <lowlevelmonitorconfigurationapi.h>
31#include <physicalmonitorenumerationapi.h>
32
33#include "qvideoframe.h"
34#include <qwindow.h>
35#include <qloggingcategory.h>
36#include <qguiapplication.h>
37#include <private/qmultimediautils_p.h>
38#include <private/qwindowsmultimediautils_p.h>
39#include <private/qcapturablewindow_p.h>
40#include <qpa/qplatformscreen_p.h>
41
42#include <memory>
43#include <system_error>
44#include <variant>
45
46static Q_LOGGING_CATEGORY(qLcWindowCaptureUwp, "qt.multimedia.ffmpeg.windowcapture.uwp");
47
48namespace winrt {
49 using namespace winrt::Windows::Graphics::Capture;
50 using namespace winrt::Windows::Graphics::DirectX;
51 using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
52}
53
55
56using namespace Windows::Graphics::DirectX::Direct3D11;
57using namespace std::chrono;
58using namespace QWindowsMultimediaUtils;
59
61{
62 winrt::IDirect3DDevice iDirect3DDevice;
63 winrt::com_ptr<ID3D11Device> d3d11dev;
64 winrt::Direct3D11CaptureFramePool framePool;
65};
66
68{
69public:
70 QUwpTextureVideoBuffer(winrt::com_ptr<IDXGISurface> &&surface)
72 , m_surface(surface)
73 {
74 }
76 {
78 }
79
80 QVideoFrame::MapMode mapMode() const override { return m_mapMode; }
81
83 {
84 if (m_mapMode != QVideoFrame::NotMapped)
85 return {};
86
88 DXGI_MAPPED_RECT rect = {};
89 HRESULT hr = m_surface->Map(&rect, DXGI_MAP_READ);
90 if (SUCCEEDED(hr)) {
91 DXGI_SURFACE_DESC desc = {};
92 hr = m_surface->GetDesc(&desc);
93
94 MapData md = {};
95 md.nPlanes = 1;
96 md.bytesPerLine[0] = rect.Pitch;
97 md.data[0] = rect.pBits;
98 md.size[0] = desc.Width * desc.Height;
99
100 m_mapMode = QVideoFrame::ReadOnly;
101
102 return md;
103 } else {
104 qCDebug(qLcWindowCaptureUwp) << "Failed to map DXGI surface" << errorString(hr);
105 return {};
106 }
107 }
108
109 return {};
110 }
111
112 void unmap() override
113 {
114 if (m_mapMode == QVideoFrame::NotMapped)
115 return;
116
117 HRESULT hr = m_surface->Unmap();
118 if (FAILED(hr)) {
119 qCDebug(qLcWindowCaptureUwp) << "Failed to unmap surface" << errorString(hr);
120 }
121 m_mapMode = QVideoFrame::NotMapped;
122 }
123
124private:
126 winrt::com_ptr<IDXGISurface> m_surface;
127};
128
130{
132public:
134 winrt::GraphicsCaptureItem item, qreal maxFrameRate)
135 : m_capture(capture),
136 m_devicePool(devicePool),
137 m_session(devicePool.framePool.CreateCaptureSession(item)),
138 m_frameSize(item.Size())
139 {
140 setFrameRate(maxFrameRate);
143 }
144
145 ~Grabber() override { stop(); }
146
147protected:
148
149 void run() override
150 {
151 m_session.IsCursorCaptureEnabled(false);
152 m_session.StartCapture();
153
155
156 m_session.Close();
157 }
158
160 {
161 auto d3dFrame = m_devicePool.framePool.TryGetNextFrame();
162 if (!d3dFrame)
163 return {};
164
165 if (m_frameSize != d3dFrame.ContentSize()) {
166 m_frameSize = d3dFrame.ContentSize();
167 m_devicePool.framePool.Recreate(m_devicePool.iDirect3DDevice,
168 winrt::DirectXPixelFormat::R8G8B8A8UIntNormalized, 1,
169 d3dFrame.ContentSize());
170 }
171
172 auto d3dSurface = d3dFrame.Surface();
173 winrt::com_ptr<IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess{ d3dSurface.as<IDirect3DDxgiInterfaceAccess>() };
174 if (!dxgiInterfaceAccess)
175 return {};
176
177 winrt::com_ptr<IDXGISurface> dxgiSurface;
178 HRESULT hr = dxgiInterfaceAccess->GetInterface(__uuidof(dxgiSurface), dxgiSurface.put_void());
179 if (FAILED(hr)) {
181 "Failed to get DXGI surface interface");
182 return {};
183 }
184
185 DXGI_SURFACE_DESC desc = {};
186 hr = dxgiSurface->GetDesc(&desc);
187 if (FAILED(hr)) {
189 "Failed to get DXGI surface description");
190 return {};
191 }
192
193 D3D11_TEXTURE2D_DESC texDesc = {};
194 texDesc.Width = desc.Width;
195 texDesc.Height = desc.Height;
196 texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
197 texDesc.Usage = D3D11_USAGE_STAGING;
198 texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
199 texDesc.MiscFlags = 0;
200 texDesc.BindFlags = 0;
201 texDesc.ArraySize = 1;
202 texDesc.MipLevels = 1;
203 texDesc.SampleDesc = { 1, 0 };
204
205 winrt::com_ptr<ID3D11Texture2D> texture;
206 hr = m_devicePool.d3d11dev->CreateTexture2D(&texDesc, nullptr, texture.put());
207 if (FAILED(hr)) {
208 updateError(QPlatformSurfaceCapture::CaptureFailed, "Failed to create ID3D11Texture2D");
209 return {};
210 }
211
212 winrt::com_ptr<ID3D11DeviceContext> ctx;
213 m_devicePool.d3d11dev->GetImmediateContext(ctx.put());
214 ctx->CopyResource(texture.as<ID3D11Resource>().get(), dxgiSurface.as<ID3D11Resource>().get());
215
217 format.setFrameRate(frameRate());
218
219 return QVideoFrame(new QUwpTextureVideoBuffer(std::move(texture.as<IDXGISurface>())), format);
220 }
221
222private:
223 QFFmpegWindowCaptureUwp &m_capture;
224 DeviceFramePool m_devicePool;
225 winrt::GraphicsCaptureSession m_session;
226 winrt::Windows::Graphics::SizeInt32 m_frameSize;
227};
228
230{
231 qCDebug(qLcWindowCaptureUwp) << "Creating UWP screen capture";
232}
233
235
237createCaptureFramePool(IDXGIAdapter1 *adapter, const winrt::GraphicsCaptureItem &item)
238{
239 winrt::com_ptr<ID3D11Device> d3d11dev;
240 HRESULT hr =
241 D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0,
242 nullptr, 0, D3D11_SDK_VERSION, d3d11dev.put(), nullptr, nullptr);
243 if (FAILED(hr))
244 return { "Failed to create ID3D11Device device" + errorString(hr) };
245
246 winrt::com_ptr<IDXGIDevice> dxgiDevice;
247 hr = d3d11dev->QueryInterface(__uuidof(IDXGIDevice), dxgiDevice.put_void());
248 if (FAILED(hr))
249 return { "Failed to obtain dxgi for D3D11Device face" };
250
251 winrt::IDirect3DDevice iDirect3DDevice{};
252 hr = CreateDirect3D11DeviceFromDXGIDevice(
253 dxgiDevice.get(), reinterpret_cast<::IInspectable **>(winrt::put_abi(iDirect3DDevice)));
254 if (FAILED(hr))
255 return { "Failed to create IDirect3DDevice device" + errorString(hr) };
256
257 auto pool = winrt::Direct3D11CaptureFramePool::Create(
258 iDirect3DDevice, winrt::DirectXPixelFormat::R8G8B8A8UIntNormalized, 1, item.Size());
259 if (pool)
260 return DeviceFramePool{ iDirect3DDevice, d3d11dev, pool };
261 else
262 return { "Failed to create capture frame pool" };
263}
264
265struct Monitor {
266 winrt::com_ptr<IDXGIAdapter1> adapter;
267 HMONITOR handle = nullptr;
268};
269
271{
272 if (!screen)
273 return { "Cannot find nullptr screen" };
274
275 auto *winScreen = screen->nativeInterface<QNativeInterface::Private::QWindowsScreen>();
276 HMONITOR handle = winScreen ? winScreen->handle() : nullptr;
277
278 winrt::com_ptr<IDXGIFactory1> factory;
279 HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), factory.put_void());
280 if (FAILED(hr))
281 return { "Failed to create IDXGIFactory" + errorString(hr) };
282
283 winrt::com_ptr<IDXGIAdapter1> adapter;
284 for (quint32 i = 0; SUCCEEDED(factory->EnumAdapters1(i, adapter.put())); i++, adapter = {}) {
285 winrt::com_ptr<IDXGIOutput> output;
286 for (quint32 j = 0; SUCCEEDED(adapter->EnumOutputs(j, output.put())); j++, output = {}) {
287 DXGI_OUTPUT_DESC desc = {};
288 output->GetDesc(&desc);
289 qCDebug(qLcWindowCaptureUwp) << i << j << QString::fromWCharArray(desc.DeviceName);
290 auto match = handle ? handle == desc.Monitor
291 : QString::fromWCharArray(desc.DeviceName) == screen->name();
292 if (match)
293 return Monitor { adapter, desc.Monitor };
294 }
295 }
296 return { "Could not find screen adapter " + screen->name() };
297}
298
300{
301 HMONITOR handle = MonitorFromWindow(wh, MONITOR_DEFAULTTONULL);
302 if (!handle)
303 return { "Cannot find window screen" };
304
305 winrt::com_ptr<IDXGIFactory1> factory;
306 HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), factory.put_void());
307 if (FAILED(hr))
308 return { "Failed to create IDXGIFactory" + errorString(hr) };
309
310 winrt::com_ptr<IDXGIAdapter1> adapter;
311 for (quint32 i = 0; SUCCEEDED(factory->EnumAdapters1(i, adapter.put())); i++, adapter = {}) {
312 winrt::com_ptr<IDXGIOutput> output;
313 for (quint32 j = 0; SUCCEEDED(adapter->EnumOutputs(j, output.put())); j++, output = {}) {
314 DXGI_OUTPUT_DESC desc = {};
315 output->GetDesc(&desc);
316 qCDebug(qLcWindowCaptureUwp) << i << j << QString::fromWCharArray(desc.DeviceName);
317 if (desc.Monitor == handle)
318 return Monitor { adapter, handle };
319 }
320 }
321
322 return { "Could not find window screen adapter" };
323}
324
326{
327 auto factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem>();
328 auto interop = factory.as<IGraphicsCaptureItemInterop>();
329
330 winrt::GraphicsCaptureItem item = {nullptr};
331 HRESULT hr = interop->CreateForMonitor(handle, __uuidof(ABI::Windows::Graphics::Capture::IGraphicsCaptureItem), winrt::put_abi(item));
332 if (FAILED(hr))
333 return "Failed to create capture item for monitor" + errorString(hr);
334 else
335 return item;
336}
337
339{
340 auto factory = winrt::get_activation_factory<winrt::GraphicsCaptureItem>();
341 auto interop = factory.as<IGraphicsCaptureItemInterop>();
342
343 winrt::GraphicsCaptureItem item = {nullptr};
344 HRESULT hr = interop->CreateForWindow(handle, __uuidof(ABI::Windows::Graphics::Capture::IGraphicsCaptureItem), winrt::put_abi(item));
345 if (FAILED(hr))
346 return "Failed to create capture item for window" + errorString(hr);
347 else
348 return item;
349}
350
352{
353 if (hwnd == GetShellWindow())
354 return "Cannot capture the shell window";
355
356 wchar_t className[MAX_PATH] = {};
357 GetClassName(hwnd, className, MAX_PATH);
359 return "Cannot capture windows without a class name";
360
361 if (!IsWindowVisible(hwnd))
362 return "Cannot capture invisible windows";
363
364 if (GetParent(hwnd) != 0)
365 return "Can only capture root windows";
366
367 LONG style = GetWindowLong(hwnd, GWL_STYLE);
368 if (style & WS_DISABLED)
369 return "Cannot capture disabled windows";
370
371 LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
372 if (exStyle & WS_EX_TOOLWINDOW)
373 return "No tooltips";
374
375 DWORD cloaked = FALSE;
376 HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
377 if (SUCCEEDED(hr) && cloaked == DWM_CLOAKED_SHELL)
378 return "Cannot capture cloaked windows";
379
380 return {};
381}
382
384{
385 DWORD count = 0;
386 if (GetNumberOfPhysicalMonitorsFromHMONITOR(handle, &count)) {
387 std::vector<PHYSICAL_MONITOR> monitors{ count };
388 if (GetPhysicalMonitorsFromHMONITOR(handle, count, monitors.data())) {
389 for (auto monitor : monitors) {
390 MC_TIMING_REPORT screenTiming = {};
391 if (GetTimingReport(monitor.hPhysicalMonitor, &screenTiming))
392 return qreal(screenTiming.dwVerticalFrequencyInHZ) / 100.;
393 }
394 }
395 }
396 return 60.;
397}
398
400{
401 if (bool(m_grabber) == active)
402 return false;
403
404 if (m_grabber) {
405 m_grabber.reset();
406 m_format = {};
407 return true;
408 }
409
412
413 const auto windowHandle = reinterpret_cast<HWND>(handle ? handle->id : 0);
414 if (windowHandle) {
415 QString error = isCapturableWindow(windowHandle);
416 if (!error.isEmpty()) {
418 return false;
419 }
420 }
421
422 auto maybeMonitor = findScreenForWindow(windowHandle);
423 if (!maybeMonitor) {
424 updateError(NotFound, maybeMonitor.error());
425 return false;
426 }
427
428 auto maybeItem = createWindowCaptureItem(windowHandle);
429 if (!maybeItem) {
430 updateError(NotFound, maybeItem.error());
431 return false;
432 }
433
434 auto maybePool = createCaptureFramePool(maybeMonitor.value().adapter.get(), maybeItem.value());
435 if (!maybePool) {
436 updateError(InternalError, maybePool.error());
437 return false;
438 }
439
440 qreal refreshRate = getMonitorRefreshRateHz(maybeMonitor.value().handle);
441
442 m_format = QVideoFrameFormat({ maybeItem.value().Size().Width, maybeItem.value().Size().Height },
444 m_format.setFrameRate(refreshRate);
445
446 m_grabber = std::make_unique<Grabber>(*this, maybePool.value(), maybeItem.value(), refreshRate);
447 m_grabber->start();
448
449 return true;
450}
451
453{
454 return winrt::GraphicsCaptureSession::IsSupported();
455}
456
458{
459 return m_format;
460}
461
463
464#include "qffmpegwindowcapture_uwp.moc"
The QAbstractVideoBuffer class is an abstraction for video data. \inmodule QtMultimedia.
static const QCapturableWindowPrivate * handle(const QCapturableWindow &window)
\inmodule QtMultimedia
void addFrameCallback(Object &object, Method method)
void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description)
Grabber(QFFmpegWindowCaptureUwp &capture, DeviceFramePool &devicePool, winrt::GraphicsCaptureItem item, qreal maxFrameRate)
bool setActiveInternal(bool active) override
~QFFmpegWindowCaptureUwp() override
QVideoFrameFormat frameFormat() const override
Native interface to QScreen, to be retrieved from QPlatformIntegration. \inmodule QtGui.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void updateError(Error error, const QString &errorString)
void newVideoFrame(const QVideoFrame &)
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
QString name
a user presentable string representing the screen
Definition qscreen.h:36
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1164
QVideoFrame::MapMode mapMode() const override
QUwpTextureVideoBuffer(winrt::com_ptr< IDXGISurface > &&surface)
MapData map(QVideoFrame::MapMode mode) override
Independently maps the planes of a video buffer to memory.
void unmap() override
Releases the memory mapped by the map() function.
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
void setFrameRate(qreal rate)
Sets the frame rate of a video stream in frames per second.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
MapMode
Enumerates how a video buffer's data is mapped to system memory.
Definition qvideoframe.h:36
EGLContext ctx
rect
[4]
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
auto wait_for(Async const &async, Windows::Foundation::TimeSpan const &timeout)
static qreal getMonitorRefreshRateHz(HMONITOR handle)
static QMaybe< Monitor > findScreenForWindow(HWND wh)
static QString isCapturableWindow(HWND hwnd)
static QMaybe< DeviceFramePool > createCaptureFramePool(IDXGIAdapter1 *adapter, const winrt::GraphicsCaptureItem &item)
static QMaybe< winrt::GraphicsCaptureItem > createWindowCaptureItem(HWND handle)
static QMaybe< winrt::GraphicsCaptureItem > createScreenCaptureItem(HMONITOR handle)
static QMaybe< Monitor > findScreen(const QScreen *screen)
#define Size(name)
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLuint64 GLenum void * handle
GLenum mode
GLuint * monitors
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLbitfield GLuint64 timeout
[4]
GLenum GLuint texture
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
#define MAX_PATH
QScreen * screen
[1]
Definition main.cpp:29
#define Q_OBJECT
@ desc
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
unsigned int quint32
Definition qtypes.h:45
double qreal
Definition qtypes.h:92
QT_BEGIN_NAMESPACE typedef uchar * output
long HRESULT
const char className[16]
[1]
Definition qwizard.cpp:100
QGraphicsItem * item
QItemEditorFactory * factory
aWidget window() -> setWindowTitle("New Window Title")
[2]
winrt::Direct3D11CaptureFramePool framePool
winrt::IDirect3DDevice iDirect3DDevice
winrt::com_ptr< ID3D11Device > d3d11dev
winrt::com_ptr< IDXGIAdapter1 > adapter