5#include <QtMultimedia/private/qsurfacecapturegrabber_p.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qscopeguard.h>
9#include <QtCore/qthread.h>
10#include <QtCore/private/qexpected_p.h>
11#include <QtCore/private/qfactorycacheregistration_p.h>
12#include <QtCore/private/qsystemerror_p.h>
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qwindow.h>
15#include <QtGui/qpa/qplatformscreen_p.h>
16#include <QtMultimedia/qabstractvideobuffer.h>
17#include <QtMultimedia/qvideoframe.h>
18#include <QtMultimedia/private/qcapturablewindow_p.h>
19#include <QtMultimedia/private/qmultimediautils_p.h>
20#include <QtMultimedia/private/qvideoframe_p.h>
23#include <winrt/base.h>
28template <
typename Async>
29auto wait_for(Async
const& async, Windows::Foundation::TimeSpan
const& timeout);
31#include <winrt/Windows.Foundation.Collections.h>
32#include <winrt/Windows.Graphics.Capture.h>
33#include <winrt/Windows.Graphics.DirectX.h>
34#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
35#include <Windows.Graphics.Capture.h>
36#include <Windows.Graphics.Capture.Interop.h>
37#include <windows.graphics.directx.direct3d11.interop.h>
41#include <lowlevelmonitorconfigurationapi.h>
42#include <physicalmonitorenumerationapi.h>
45#include <system_error>
49using namespace Qt::StringLiterals;
51using namespace winrt::Windows::Graphics::Capture;
52using namespace winrt::Windows::Graphics::DirectX;
53using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
54using namespace Windows::Graphics::DirectX::Direct3D11;
56using winrt::check_hresult;
62Q_LOGGING_CATEGORY(qLcWindowCaptureUwp,
"qt.multimedia.ffmpeg.windowcapture.uwp");
64winrt::Windows::Graphics::SizeInt32 getWindowSize(HWND hwnd)
67 ::GetWindowRect(hwnd, &windowRect);
69 return { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
72QSize asQSize(winrt::Windows::Graphics::SizeInt32 size)
74 return { size.Width, size.Height };
77struct MultithreadedApartment
79 MultithreadedApartment(
const MultithreadedApartment &) =
delete;
80 MultithreadedApartment &operator=(
const MultithreadedApartment &) =
delete;
82 MultithreadedApartment() { winrt::init_apartment(); }
83 ~MultithreadedApartment() { winrt::uninit_apartment(); }
86class QUwpTextureVideoBuffer :
public QAbstractVideoBuffer
89 QUwpTextureVideoBuffer(com_ptr<IDXGISurface> &&surface) : m_surface(surface) { }
91 ~QUwpTextureVideoBuffer() override { Q_ASSERT(m_mapMode == QVideoFrame::NotMapped); }
93 MapData map(QVideoFrame::MapMode mode) override
95 if (m_mapMode != QVideoFrame::NotMapped)
98 if (mode == QVideoFrame::ReadOnly) {
99 DXGI_MAPPED_RECT rect = {};
100 HRESULT hr = m_surface->Map(&rect, DXGI_MAP_READ);
102 DXGI_SURFACE_DESC desc = {};
103 hr = m_surface->GetDesc(&desc);
107 md.bytesPerLine[0] = rect.Pitch;
108 md.data[0] = rect.pBits;
109 md.dataSize[0] = rect.Pitch * desc.Height;
111 m_mapMode = QVideoFrame::ReadOnly;
115 qCDebug(qLcWindowCaptureUwp)
116 <<
"Failed to map DXGI surface" << QSystemError::windowsComString(hr);
124 void unmap() override
126 if (m_mapMode == QVideoFrame::NotMapped)
129 const HRESULT hr = m_surface->Unmap();
131 qCDebug(qLcWindowCaptureUwp)
132 <<
"Failed to unmap surface" << QSystemError::windowsComString(hr);
134 m_mapMode = QVideoFrame::NotMapped;
137 QVideoFrameFormat format()
const override {
return {}; }
140 QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
141 com_ptr<IDXGISurface> m_surface;
146 WindowGrabber() =
default;
148 WindowGrabber(IDXGIAdapter1 *adapter, HWND hwnd,
bool ignoreCursor)
149 : m_captureWindow{ hwnd }
151 check_hresult(D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN,
nullptr, 0,
nullptr, 0,
152 D3D11_SDK_VERSION, m_device.put(),
nullptr,
nullptr));
154 const auto captureItem = createCaptureItem(hwnd);
159 m_frameSize = captureItem.Size();
161 m_framePool = Direct3D11CaptureFramePool::CreateFreeThreaded(
162 getCaptureDevice(m_device), m_pixelFormat, 1,
165 m_session = m_framePool.CreateCaptureSession(captureItem);
168 if (
const auto session2 = m_session.try_as<IGraphicsCaptureSession2>())
169 session2.IsCursorCaptureEnabled(!ignoreCursor);
172 if (
const auto session3 = m_session.try_as<IGraphicsCaptureSession3>())
173 session3.IsBorderRequired(
false);
175 m_session.StartCapture();
184 com_ptr<IDXGISurface> tryGetFrame()
186 const Direct3D11CaptureFrame frame = m_framePool.TryGetNextFrame();
193 if (!IsWindow(m_captureWindow))
194 throw std::runtime_error(
"Window was closed");
201 if (m_frameSize != frame.ContentSize()) {
202 m_frameSize = frame.ContentSize();
203 m_framePool.Recreate(getCaptureDevice(m_device), m_pixelFormat, 1, frame.ContentSize());
207 return copyTexture(m_device, frame.Surface());
211 static GraphicsCaptureItem createCaptureItem(HWND hwnd)
213 const auto factory = winrt::get_activation_factory<GraphicsCaptureItem>();
214 const auto interop = factory.as<IGraphicsCaptureItemInterop>();
216 GraphicsCaptureItem item = {
nullptr };
217 winrt::hresult status = S_OK;
222 constexpr int maxRetry = 10;
223 constexpr std::chrono::milliseconds retryDelay{ 100 };
224 for (
int retryNum = 0; retryNum < maxRetry; ++retryNum) {
226 status = interop->CreateForWindow(hwnd, winrt::guid_of<GraphicsCaptureItem>(),
227 winrt::put_abi(item));
229 if (status != E_INVALIDARG)
232 qCWarning(qLcWindowCaptureUwp)
233 <<
"Failed to create capture item:"
234 << QString::fromStdWString(winrt::hresult_error(status).message().c_str())
235 <<
"Retry number" << retryNum;
237 if (retryNum + 1 < maxRetry)
238 QThread::sleep(retryDelay);
242 check_hresult(status);
247 static IDirect3DDevice getCaptureDevice(
const com_ptr<ID3D11Device> &d3dDevice)
249 const auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
251 com_ptr<IInspectable> device;
252 check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), device.put()));
254 return device.as<IDirect3DDevice>();
257 static com_ptr<IDXGISurface> copyTexture(
const com_ptr<ID3D11Device> &device,
258 const IDirect3DSurface &capturedTexture)
260 const auto dxgiInterop{ capturedTexture.as<IDirect3DDxgiInterfaceAccess>() };
264 com_ptr<IDXGISurface> dxgiSurface;
265 check_hresult(dxgiInterop->GetInterface(guid_of<IDXGISurface>(), dxgiSurface.put_void()));
267 DXGI_SURFACE_DESC desc = {};
268 check_hresult(dxgiSurface->GetDesc(&desc));
270 D3D11_TEXTURE2D_DESC texDesc = {};
271 texDesc.Width = desc.Width;
272 texDesc.Height = desc.Height;
273 texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
274 texDesc.Usage = D3D11_USAGE_STAGING;
275 texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
276 texDesc.MiscFlags = 0;
277 texDesc.BindFlags = 0;
278 texDesc.ArraySize = 1;
279 texDesc.MipLevels = 1;
280 texDesc.SampleDesc = { 1, 0 };
282 com_ptr<ID3D11Texture2D> texture;
283 check_hresult(device->CreateTexture2D(&texDesc,
nullptr, texture.put()));
285 com_ptr<ID3D11DeviceContext> ctx;
286 device->GetImmediateContext(ctx.put());
287 ctx->CopyResource(texture.get(), dxgiSurface.as<ID3D11Resource>().get());
289 return texture.as<IDXGISurface>();
292 MultithreadedApartment m_comApartment{};
293 HWND m_captureWindow{};
294 winrt::Windows::Graphics::SizeInt32 m_frameSize{};
295 com_ptr<ID3D11Device> m_device;
296 Direct3D11CaptureFramePool m_framePool{
nullptr };
297 GraphicsCaptureSession m_session{
nullptr };
298 const DirectXPixelFormat m_pixelFormat = DirectXPixelFormat::R8G8B8A8UIntNormalized;
306 Grabber(QFFmpegWindowCaptureUwp &capture, HWND hwnd)
309 m_ignoreCursor(capture.ignoreCursor()),
313 const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
314 m_adapter = getAdapter(monitor);
316 addFrameCallback(&capture, &QFFmpegWindowCaptureUwp::newVideoFrame);
317 connect(
this, &Grabber::errorUpdated, &capture, &QFFmpegWindowCaptureUwp::updateError);
319 const auto refreshRate = getMonitorRefreshRateHz(monitor);
320 const auto defaultRate = refreshRate ? qMin(*refreshRate, DefaultScreenCaptureFrameRate)
321 : DefaultScreenCaptureFrameRate;
322 setFrameRate(capture.frameRate().value_or(defaultRate));
323 m_format.setStreamFrameRate(frameRate());
334 if (!m_adapter || !IsWindow(m_hwnd))
338 m_windowGrabber = std::make_unique<WindowGrabber>(
343 QSurfaceCaptureGrabber::initializeGrabbingContext();
344 }
catch (
const winrt::hresult_error &err) {
346 const QString message = QLatin1String(
"Unable to capture window: ")
347 + QString::fromWCharArray(err.message().c_str());
349 updateError(QPlatformSurfaceCapture::Error::InternalError, message);
355 QSurfaceCaptureGrabber::finalizeGrabbingContext();
356 m_windowGrabber =
nullptr;
362 com_ptr<IDXGISurface> texture = m_windowGrabber->tryGetFrame();
366 const QSize size = getTextureSize(texture);
368 m_format.setFrameSize(size);
370 if (m_format.streamFrameRate() != activeFrameRate())
371 m_format.setStreamFrameRate(activeFrameRate());
373 return QVideoFramePrivate::createFrame(
374 std::make_unique<QUwpTextureVideoBuffer>(std::move(texture)), m_format);
376 }
catch (
const winrt::hresult_error &err) {
378 const QString message = QLatin1String(
"Window capture failed: ")
379 + QString::fromWCharArray(err.message().c_str());
381 updateError(QPlatformSurfaceCapture::Error::InternalError, message);
382 }
catch (
const std::runtime_error& e) {
383 updateError(QPlatformSurfaceCapture::Error::CaptureFailed, QString::fromLatin1(e.what()));
390 static com_ptr<IDXGIAdapter1> getAdapter(HMONITOR handle)
392 com_ptr<IDXGIFactory1> factory;
393 check_hresult(CreateDXGIFactory1(guid_of<IDXGIFactory1>(), factory.put_void()));
395 com_ptr<IDXGIAdapter1> adapter;
396 for (quint32 i = 0; factory->EnumAdapters1(i, adapter.put()) == S_OK; adapter =
nullptr, i++) {
397 com_ptr<IDXGIOutput> output;
398 for (quint32 j = 0; adapter->EnumOutputs(j, output.put()) == S_OK; output =
nullptr, j++) {
399 DXGI_OUTPUT_DESC desc = {};
400 HRESULT hr = output->GetDesc(&desc);
401 if (hr == S_OK && desc.Monitor == handle)
408 static QSize getTextureSize(
const com_ptr<IDXGISurface> &surf)
413 DXGI_SURFACE_DESC desc;
414 check_hresult(surf->GetDesc(&desc));
416 return {
static_cast<
int>(desc.Width),
static_cast<
int>(desc.Height) };
419 static q23::expected<qreal, QString> getMonitorRefreshRateHz(HMONITOR handle)
422 return q23::unexpected{ u"No monitor"_s };
425 if (GetNumberOfPhysicalMonitorsFromHMONITOR(handle, &count)) {
426 std::vector<PHYSICAL_MONITOR> monitors{ count };
427 if (GetPhysicalMonitorsFromHMONITOR(handle, count, monitors.data())) {
428 const auto cleanup = qScopeGuard([count, &monitors] {
429 DestroyPhysicalMonitors(count, monitors.data());
431 for (
const auto &monitor : std::as_const(monitors)) {
432 MC_TIMING_REPORT screenTiming = {};
433 if (GetTimingReport(monitor.hPhysicalMonitor, &screenTiming)) {
437 return static_cast<qreal>(screenTiming.dwVerticalFrequencyInHZ) / 100.0;
442 return q23::unexpected{ u"Couldn't get monitor refresh rate"_s };
446 bool m_ignoreCursor =
false;
447 com_ptr<IDXGIAdapter1> m_adapter{};
448 std::unique_ptr<WindowGrabber> m_windowGrabber;
449 QVideoFrameFormat m_format;
452QFFmpegWindowCaptureUwp::QFFmpegWindowCaptureUwp() : QPlatformSurfaceCapture(WindowSource{})
454 qCDebug(qLcWindowCaptureUwp) <<
"Creating UWP screen capture";
457QFFmpegWindowCaptureUwp::~QFFmpegWindowCaptureUwp() =
default;
462 return u"Invalid window handle"_s;
464 if (hwnd == GetShellWindow())
465 return u"Cannot capture the shell window"_s;
467 wchar_t className[MAX_PATH] = {};
468 GetClassName(hwnd, className, MAX_PATH);
469 if (QString::fromWCharArray(className).length() == 0)
470 return u"Cannot capture windows without a class name"_s;
472 if (!IsWindowVisible(hwnd))
473 return u"Cannot capture invisible windows"_s;
475 if (GetAncestor(hwnd, GA_ROOT) != hwnd)
476 return u"Can only capture root windows"_s;
478 const LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
479 if (style & WS_DISABLED)
480 return u"Cannot capture disabled windows"_s;
482 const LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
483 if (exStyle & WS_EX_TOOLWINDOW)
484 return u"No tooltips"_s;
486 DWORD cloaked = FALSE;
487 const HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked,
sizeof(cloaked));
488 if (SUCCEEDED(hr) && cloaked == DWM_CLOAKED_SHELL)
489 return u"Cannot capture cloaked windows"_s;
494bool QFFmpegWindowCaptureUwp::setActiveInternal(
bool active)
496 if (
static_cast<
bool>(m_grabber) == active)
504 const auto window = source<WindowSource>();
505 const auto handle = QCapturableWindowPrivate::handle(window);
507 const auto hwnd =
reinterpret_cast<HWND>(handle ? handle->id : 0);
508 if (
const QString error = isCapturableWindow(hwnd); !error.isEmpty()) {
509 updateError(QPlatformSurfaceCapture::Error::InternalError, error);
513 m_grabber = std::make_unique<Grabber>(*
this, hwnd);
519bool QFFmpegWindowCaptureUwp::isSupported()
525 return GraphicsCaptureSession::IsSupported();
526 }
catch (
const winrt::hresult_error &err) {
527 qCWarning(qLcWindowCaptureUwp)
528 <<
"GraphicsCaptureSession::IsSupported() threw exception:"
529 << QString::fromWCharArray(err.message().c_str());
531 }
catch (
const std::exception &err) {
532 qCWarning(qLcWindowCaptureUwp)
533 <<
"GraphicsCaptureSession::IsSupported() failed, threw exception:"
539QVideoFrameFormat QFFmpegWindowCaptureUwp::frameFormat()
const
542 return m_grabber->frameFormat();
QVideoFrameFormat frameFormat() const
QVideoFrame grabFrame() override
Grabber(QFFmpegWindowCaptureUwp &capture, HWND hwnd)
void finalizeGrabbingContext() override
void initializeGrabbingContext() override
auto wait_for(Async const &async, Windows::Foundation::TimeSpan const &timeout)
Windows::Foundation::AsyncStatus AsyncStatus
static QString isCapturableWindow(HWND hwnd)