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
qffmpegscreencapture_dxgi.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
7#include <private/qmultimediautils_p.h>
8#include <private/qvideoframe_p.h>
9#include <QtGui/qscreen_platform.h>
10#include "qvideoframe.h"
11
12#include <qloggingcategory.h>
13#include <qwaitcondition.h>
14#include <qmutex.h>
15#include <QtCore/private/qsystemerror_p.h>
16#include <QtCore/private/qexpected_p.h>
17
18#include "D3d11.h"
19#include "dxgi1_2.h"
20
21#include <system_error>
22
23#include <mutex> // std::scoped_lock
24
25QT_BEGIN_NAMESPACE
26
27Q_STATIC_LOGGING_CATEGORY(qLcScreenCaptureDxgi, "qt.multimedia.ffmpeg.screencapturedxgi");
28
29using namespace Qt::StringLiterals;
30
31namespace {
32
33// Convenience wrapper that combines an HRESULT
34// status code with an optional textual description.
35class ComStatus
36{
37public:
38 ComStatus() = default;
39 ComStatus(HRESULT hr) : m_hr{ hr } { }
40 ComStatus(HRESULT hr, QAnyStringView msg) : m_hr{ hr }, m_msg{ msg.toString() } { }
41
42 ComStatus(const ComStatus &) = default;
43 ComStatus(ComStatus &&) = default;
44 ComStatus &operator=(const ComStatus &) = default;
45 ComStatus &operator=(ComStatus &&) = default;
46
47 explicit operator bool() const { return m_hr == S_OK; }
48
49 HRESULT code() const { return m_hr; }
50 QString str() const
51 {
52 if (!m_msg)
53 return QSystemError::windowsComString(m_hr);
54 return *m_msg + u" " + QSystemError::windowsComString(m_hr);
55 }
56
57private:
58 HRESULT m_hr = S_OK;
59 std::optional<QString> m_msg;
60};
61
62template <typename T>
63using ComProduct = q23::expected<ComPtr<T>, ComStatus>;
64}
65
67{
68public:
69 QD3D11TextureVideoBuffer(const ComPtr<ID3D11Device> &device, std::shared_ptr<QMutex> &mutex,
70 const ComPtr<ID3D11Texture2D> &texture)
72 {}
73
74 ~QD3D11TextureVideoBuffer() { Q_ASSERT(m_mapMode == QVideoFrame::NotMapped); }
75
76 MapData map(QVideoFrame::MapMode mode) override
77 {
78 MapData mapData;
79 if (!m_ctx && mode == QVideoFrame::ReadOnly) {
80 D3D11_TEXTURE2D_DESC texDesc = {};
81 m_texture->GetDesc(&texDesc);
82 texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
83 texDesc.Usage = D3D11_USAGE_STAGING;
84 texDesc.MiscFlags = 0;
85 texDesc.BindFlags = 0;
86
87 HRESULT hr = m_device->CreateTexture2D(&texDesc, nullptr, m_cpuTexture.GetAddressOf());
88 if (FAILED(hr)) {
89 qCDebug(qLcScreenCaptureDxgi) << "Failed to create texture with CPU access"
90 << std::system_category().message(hr).c_str();
91 qCDebug(qLcScreenCaptureDxgi) << m_device->GetDeviceRemovedReason();
92 return {};
93 }
94
95 m_device->GetImmediateContext(m_ctx.GetAddressOf());
96 m_ctxMutex->lock();
97 m_ctx->CopyResource(m_cpuTexture.Get(), m_texture.Get());
98
99 D3D11_MAPPED_SUBRESOURCE resource = {};
100 hr = m_ctx->Map(m_cpuTexture.Get(), 0, D3D11_MAP_READ, 0, &resource);
101 m_ctxMutex->unlock();
102 if (FAILED(hr)) {
103 qCDebug(qLcScreenCaptureDxgi) << "Failed to map texture" << m_cpuTexture.Get()
104 << std::system_category().message(hr).c_str();
105 return {};
106 }
107
108 m_mapMode = mode;
109 mapData.planeCount = 1;
110 mapData.bytesPerLine[0] = int(resource.RowPitch);
111 mapData.data[0] = reinterpret_cast<uchar*>(resource.pData);
112 mapData.dataSize[0] = int(texDesc.Height * resource.RowPitch);
113 }
114
115 return mapData;
116 }
117
119 {
120 if (m_mapMode == QVideoFrame::NotMapped)
121 return;
122 if (m_ctx) {
123 m_ctxMutex->lock();
124 m_ctx->Unmap(m_cpuTexture.Get(), 0);
125 m_ctxMutex->unlock();
126 m_ctx.Reset();
127 }
128 m_cpuTexture.Reset();
129 m_mapMode = QVideoFrame::NotMapped;
130 }
131
132 QVideoFrameFormat format() const override { return {}; }
133
134 QSize getSize() const
135 {
136 if (!m_texture)
137 return {};
138
139 D3D11_TEXTURE2D_DESC desc{};
140 m_texture->GetDesc(&desc);
141
142 return { static_cast<int>(desc.Width), static_cast<int>(desc.Height) };
143 }
144
145private:
146 ComPtr<ID3D11Device> m_device;
147 ComPtr<ID3D11Texture2D> m_texture;
148 ComPtr<ID3D11Texture2D> m_cpuTexture;
149 ComPtr<ID3D11DeviceContext> m_ctx;
150 std::shared_ptr<QMutex> m_ctxMutex;
151 QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
152};
153
154namespace {
155
156struct DxgiScreen
157{
158 q23::expected<QSize, ComStatus> physicalSize() const
159 {
160 DXGI_OUTPUT_DESC desc{};
161 const HRESULT hr = output->GetDesc(&desc);
162 if (hr != S_OK)
163 return q23::unexpected{ hr };
164
165 const RECT bounds = desc.DesktopCoordinates;
166 const QRect displayRect{ bounds.left, bounds.top,
167 bounds.right - bounds.left,
168 bounds.bottom - bounds.top };
169 return displayRect.size();
170 }
171
172 q23::expected<QtVideo::Rotation, ComStatus> rotation() const
173 {
174 DXGI_OUTPUT_DESC desc{};
175 const HRESULT hr = output->GetDesc(&desc);
176 if (hr != S_OK)
177 return q23::unexpected{ hr };
178
179 switch (desc.Rotation) {
180
181 case DXGI_MODE_ROTATION_ROTATE90:
182 return QtVideo::Rotation::Clockwise90;
183 case DXGI_MODE_ROTATION_ROTATE180:
184 return QtVideo::Rotation::Clockwise180;
185 case DXGI_MODE_ROTATION_ROTATE270:
186 return QtVideo::Rotation::Clockwise270;
187 default:
188 return QtVideo::Rotation::None;
189 }
190 }
191
192 ComPtr<IDXGIAdapter1> adapter;
193 ComPtr<IDXGIOutput> output;
194};
195
196q23::expected<DxgiScreen, ComStatus> findDxgiScreen(const QScreen *screen)
197{
198 if (!screen)
199 return q23::unexpected{ ComStatus{ E_FAIL, "Cannot find nullptr screen"_L1 } };
200
201 auto *winScreen = screen->nativeInterface<QNativeInterface::QWindowsScreen>();
202 HMONITOR handle = winScreen ? winScreen->handle() : nullptr;
203
204 ComPtr<IDXGIFactory1> factory;
205 HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
206 if (FAILED(hr))
207 return q23::unexpected{ ComStatus{ hr, "Failed to create IDXGIFactory"_L1 } };
208
209 ComPtr<IDXGIAdapter1> adapter;
210 for (quint32 i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) == S_OK; i++) {
211 ComPtr<IDXGIOutput> output;
212 for (quint32 j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) == S_OK; ++j) {
213 DXGI_OUTPUT_DESC desc = {};
214 output->GetDesc(&desc);
215 qCDebug(qLcScreenCaptureDxgi) << i << j << QString::fromWCharArray(desc.DeviceName);
216 auto match = handle ? handle == desc.Monitor
217 : QString::fromWCharArray(desc.DeviceName) == screen->name();
218 if (match)
219 return DxgiScreen{ adapter, output };
220 }
221 }
222 return q23::unexpected{ ComStatus{
223 DXGI_ERROR_NOT_FOUND,
224 "Could not find screen adapter "_L1 + screen->name(),
225 } };
226}
227
228class DxgiDuplication
229{
230public:
231 ~DxgiDuplication()
232 {
233 if (m_releaseFrame)
234 m_dup->ReleaseFrame();
235 }
236
237 ComStatus initialize(QScreen const *screen)
238 {
239 q23::expected<DxgiScreen, ComStatus> dxgiScreen = findDxgiScreen(screen);
240 if (!dxgiScreen)
241 return dxgiScreen.error();
242
243 const ComPtr<IDXGIAdapter1> adapter = dxgiScreen->adapter;
244
245 ComPtr<ID3D11Device> d3d11dev;
246 HRESULT hr =
247 D3D11CreateDevice(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, nullptr, 0,
248 D3D11_SDK_VERSION, d3d11dev.GetAddressOf(), nullptr, nullptr);
249 if (FAILED(hr))
250 return { hr, "Failed to create ID3D11Device device"_L1 };
251
252 ComPtr<IDXGIOutput1> output;
253 hr = dxgiScreen->output.As(&output);
254 if (FAILED(hr))
255 return { hr, "Failed to create IDXGIOutput1"_L1 };
256
257 ComPtr<IDXGIOutputDuplication> dup;
258 hr = output->DuplicateOutput(d3d11dev.Get(), dup.GetAddressOf());
259 if (FAILED(hr))
260 return { hr, "Failed to duplicate IDXGIOutput1"_L1 };
261
262 m_adapter = dxgiScreen->adapter;
263 m_output = output;
264 m_device = d3d11dev;
265 m_dup = dup;
266 return { S_OK };
267 }
268
269 bool valid() const { return m_dup != nullptr; }
270 q23::expected<std::unique_ptr<QD3D11TextureVideoBuffer>, ComStatus> getNextVideoFrame()
271 {
272 const ComProduct<ID3D11Texture2D> texture = getNextFrame();
273
274 if (!texture)
275 return q23::unexpected{ texture.error() };
276
277 return std::make_unique<QD3D11TextureVideoBuffer>(m_device, m_ctxMutex, *texture);
278 }
279
280private:
281 ComProduct<ID3D11Texture2D> getNextFrame()
282 {
283 std::scoped_lock guard{ *m_ctxMutex };
284
285 if (m_releaseFrame) {
286 m_releaseFrame = false;
287
288 HRESULT hr = m_dup->ReleaseFrame();
289
290 if (hr != S_OK)
291 return q23::unexpected{ ComStatus{ hr,
292 "Failed to release duplication frame."_L1 } };
293 }
294
295 ComPtr<IDXGIResource> frame;
296 DXGI_OUTDUPL_FRAME_INFO info;
297
298 HRESULT hr = m_dup->AcquireNextFrame(0, &info, frame.GetAddressOf());
299
300 if (hr != S_OK)
301 return q23::unexpected{ ComStatus{ hr, "Failed to grab the screen content"_L1 } };
302
303 m_releaseFrame = true;
304
305 ComPtr<ID3D11Texture2D> tex;
306 hr = frame.As(&tex);
307 if (hr != S_OK)
308 return q23::unexpected{ ComStatus{ hr, "Failed to obtain D3D11 texture"_L1 } };
309
310 D3D11_TEXTURE2D_DESC texDesc = {};
311 tex->GetDesc(&texDesc);
312 texDesc.MiscFlags = 0;
313 texDesc.BindFlags = 0;
314
315 ComPtr<ID3D11Texture2D> texCopy;
316 hr = m_device->CreateTexture2D(&texDesc, nullptr, texCopy.GetAddressOf());
317 if (hr != S_OK)
318 return q23::unexpected{ ComStatus{ hr,
319 "Failed to create texture with CPU access"_L1 } };
320
321 ComPtr<ID3D11DeviceContext> ctx;
322 m_device->GetImmediateContext(ctx.GetAddressOf());
323 ctx->CopyResource(texCopy.Get(), tex.Get());
324
325 return texCopy;
326 }
327
328 ComPtr<IDXGIAdapter1> m_adapter;
329 ComPtr<IDXGIOutput> m_output;
330 ComPtr<ID3D11Device> m_device;
331 ComPtr<IDXGIOutputDuplication> m_dup;
332 bool m_releaseFrame = false;
333 std::shared_ptr<QMutex> m_ctxMutex = std::make_shared<QMutex>();
334};
335
336q23::expected<QVideoFrameFormat, ComStatus> getFrameFormat(const QScreen *screen)
337{
338 const auto dxgiScreen = findDxgiScreen(screen);
339 if (!dxgiScreen)
340 return q23::unexpected{ dxgiScreen.error() };
341
342 const auto screenSize = dxgiScreen->physicalSize();
343 if (!screenSize)
344 return q23::unexpected{ screenSize.error() };
345
346 const auto rotation = dxgiScreen->rotation();
347 if (!rotation)
348 return q23::unexpected{ rotation.error() };
349
350 QVideoFrameFormat format = { *screenSize, QVideoFrameFormat::Format_BGRA8888 };
351 format.setRotation(*rotation);
352 format.setStreamFrameRate(static_cast<int>(screen->refreshRate()));
353
354 return format;
355}
356
357} // namespace
358
359class QFFmpegScreenCaptureDxgi::Grabber : public QFFmpegSurfaceCaptureGrabber
360{
361public:
362 Grabber(QFFmpegScreenCaptureDxgi &screenCapture, QScreen *screen,
363 const QVideoFrameFormat &format)
366 {
367 setFrameRate(screen->refreshRate());
368 addFrameCallback(screenCapture, &QFFmpegScreenCaptureDxgi::newVideoFrame);
369 connect(this, &Grabber::errorUpdated, &screenCapture, &QFFmpegScreenCaptureDxgi::updateError);
370 }
371
373 stop();
374 }
375
377 return m_format;
378 }
379
380 QVideoFrame grabFrame() override
381 {
382 QVideoFrame frame;
383 if (!m_duplication.valid()) {
384 const ComStatus status = m_duplication.initialize(m_screen);
385 if (!status) {
386 if (status.code() == E_ACCESSDENIED) {
387 // May occur for some time after pushing Ctrl+Alt+Del.
388 updateError(QPlatformSurfaceCapture::NoError, status.str());
389 qCWarning(qLcScreenCaptureDxgi) << status.str();
390 }
391 return frame;
392 }
393 }
394
395 auto maybeBuf = m_duplication.getNextVideoFrame();
396 if (maybeBuf) {
397 std::unique_ptr<QD3D11TextureVideoBuffer> buffer = std::move(*maybeBuf);
398
399 const QSize bufSize = buffer->getSize();
400 if (bufSize != m_format.frameSize())
401 m_format.setFrameSize(bufSize);
402
403 frame = QVideoFramePrivate::createFrame(std::move(buffer), format());
404 } else {
405 const ComStatus &status = maybeBuf.error();
406
407 if (status.code() == DXGI_ERROR_WAIT_TIMEOUT) {
408 // All is good, we just didn't get a new frame yet
409 updateError(QPlatformSurfaceCapture::NoError, status.str());
410 } else if (status.code() == DXGI_ERROR_ACCESS_LOST) {
411 // Can happen for example when pushing Ctrl + Alt + Del
412 m_duplication = {};
413 updateError(QPlatformSurfaceCapture::NoError, status.str());
414 qCWarning(qLcScreenCaptureDxgi) << status.str();
415 } else if (!status) {
416 updateError(QPlatformSurfaceCapture::CaptureFailed, status.str());
417 qCWarning(qLcScreenCaptureDxgi) << status.str();
418 }
419 }
420
421 return frame;
422 }
423
424 protected:
426 {
427 m_duplication = DxgiDuplication();
428 const ComStatus status = m_duplication.initialize(m_screen);
429 if (!status) {
430 updateError(CaptureFailed, status.str());
431 return;
432 }
433
435 }
436
437private:
438 const QScreen *m_screen = nullptr;
439 QVideoFrameFormat m_format;
440 DxgiDuplication m_duplication;
441};
442
443QFFmpegScreenCaptureDxgi::QFFmpegScreenCaptureDxgi() : QPlatformSurfaceCapture(ScreenSource{}) { }
444
445QFFmpegScreenCaptureDxgi::~QFFmpegScreenCaptureDxgi() = default;
446
447QVideoFrameFormat QFFmpegScreenCaptureDxgi::frameFormat() const
448{
449 if (m_grabber)
450 return m_grabber->format();
451 return {};
452}
453
454bool QFFmpegScreenCaptureDxgi::setActiveInternal(bool active)
455{
456 if (static_cast<bool>(m_grabber) == active)
457 return true;
458
459 if (m_grabber) {
460 m_grabber.reset();
461 } else {
462 auto screen = source<ScreenSource>();
463
464 if (!checkScreenWithError(screen))
465 return false;
466
467 const auto format = getFrameFormat(screen);
468 if (!format) {
469 updateError(NotFound, "Unable to determine screen size or format"_L1 + format.error().str());
470 return false;
471 }
472
473 m_grabber.reset(new Grabber(*this, screen, *format));
474 m_grabber->start();
475 }
476
477 return true;
478}
479
480QT_END_NAMESPACE
void unmap() override
Releases the memory mapped by the map() function.
MapData map(QVideoFrame::MapMode mode) override
Maps the planes of a video buffer to memory.
QD3D11TextureVideoBuffer(const ComPtr< ID3D11Device > &device, std::shared_ptr< QMutex > &mutex, const ComPtr< ID3D11Texture2D > &texture)
QVideoFrameFormat format() const override
Gets \l QVideoFrameFormat of the underlying video buffer.
Grabber(QFFmpegScreenCaptureDxgi &screenCapture, QScreen *screen, const QVideoFrameFormat &format)
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)