Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
6#include <private/qabstractvideobuffer_p.h>
7#include <private/qmultimediautils_p.h>
8#include <private/qwindowsmultimediautils_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
16#include "D3d11.h"
17#include "dxgi1_2.h"
18
19#include <system_error>
20#include <thread>
21#include <chrono>
22
23#include <mutex> // std::scoped_lock
24
26
27static Q_LOGGING_CATEGORY(qLcScreenCaptureDxgi, "qt.multimedia.ffmpeg.screencapturedxgi")
28
29using namespace std::chrono;
30using namespace QWindowsMultimediaUtils;
31using namespace Qt::StringLiterals;
32
33namespace {
34
35// Convenience wrapper that combines an HRESULT
36// status code with an optional textual description.
37class ComStatus
38{
39public:
40 ComStatus() = default;
41 ComStatus(HRESULT hr) : m_hr{ hr } { }
42 ComStatus(HRESULT hr, QAnyStringView msg) : m_hr{ hr }, m_msg{ msg.toString() } { }
43
44 ComStatus(const ComStatus &) = default;
45 ComStatus(ComStatus &&) = default;
46 ComStatus &operator=(const ComStatus &) = default;
47 ComStatus &operator=(ComStatus &&) = default;
48
49 explicit operator bool() const { return m_hr == S_OK; }
50
51 HRESULT code() const { return m_hr; }
52 QString str() const
53 {
54 if (!m_msg)
55 return errorString(m_hr);
56 return *m_msg + " " + errorString(m_hr);
57 }
58
59private:
60 HRESULT m_hr = S_OK;
61 std::optional<QString> m_msg;
62};
63
64template <typename T>
65using ComProduct = QMaybe<ComPtr<T>, ComStatus>;
66
67}
68
70{
71public:
72 QD3D11TextureVideoBuffer(const ComPtr<ID3D11Device> &device, std::shared_ptr<QMutex> &mutex,
73 const ComPtr<ID3D11Texture2D> &texture, QSize size)
75 , m_device(device)
76 , m_texture(texture)
77 , m_ctxMutex(mutex)
78 , m_size(size)
79 {}
80
85
87 {
88 return m_mapMode;
89 }
90
92 {
94 if (!m_ctx && mode == QVideoFrame::ReadOnly) {
95 D3D11_TEXTURE2D_DESC texDesc = {};
96 m_texture->GetDesc(&texDesc);
97 texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
98 texDesc.Usage = D3D11_USAGE_STAGING;
99 texDesc.MiscFlags = 0;
100 texDesc.BindFlags = 0;
101
102 HRESULT hr = m_device->CreateTexture2D(&texDesc, nullptr, m_cpuTexture.GetAddressOf());
103 if (FAILED(hr)) {
104 qCDebug(qLcScreenCaptureDxgi) << "Failed to create texture with CPU access"
105 << std::system_category().message(hr).c_str();
106 qCDebug(qLcScreenCaptureDxgi) << m_device->GetDeviceRemovedReason();
107 return {};
108 }
109
110 m_device->GetImmediateContext(m_ctx.GetAddressOf());
111 m_ctxMutex->lock();
112 m_ctx->CopyResource(m_cpuTexture.Get(), m_texture.Get());
113
114 D3D11_MAPPED_SUBRESOURCE resource = {};
115 hr = m_ctx->Map(m_cpuTexture.Get(), 0, D3D11_MAP_READ, 0, &resource);
116 m_ctxMutex->unlock();
117 if (FAILED(hr)) {
118 qCDebug(qLcScreenCaptureDxgi) << "Failed to map texture" << m_cpuTexture.Get()
119 << std::system_category().message(hr).c_str();
120 return {};
121 }
122
123 m_mapMode = mode;
124 mapData.nPlanes = 1;
125 mapData.bytesPerLine[0] = int(resource.RowPitch);
126 mapData.data[0] = reinterpret_cast<uchar*>(resource.pData);
127 mapData.size[0] = m_size.height() * int(resource.RowPitch);
128 }
129
130 return mapData;
131 }
132
133 void unmap() override
134 {
135 if (m_mapMode == QVideoFrame::NotMapped)
136 return;
137 if (m_ctx) {
138 m_ctxMutex->lock();
139 m_ctx->Unmap(m_cpuTexture.Get(), 0);
140 m_ctxMutex->unlock();
141 m_ctx.Reset();
142 }
143 m_cpuTexture.Reset();
144 m_mapMode = QVideoFrame::NotMapped;
145 }
146
148 {
149 if (!m_texture)
150 return {};
151
152 D3D11_TEXTURE2D_DESC desc{};
153 m_texture->GetDesc(&desc);
154
155 return { static_cast<int>(desc.Width), static_cast<int>(desc.Height) };
156 }
157
158private:
159 ComPtr<ID3D11Device> m_device;
160 ComPtr<ID3D11Texture2D> m_texture;
161 ComPtr<ID3D11Texture2D> m_cpuTexture;
162 ComPtr<ID3D11DeviceContext> m_ctx;
163 std::shared_ptr<QMutex> m_ctxMutex;
164 QSize m_size;
166};
167
168namespace {
169class DxgiDuplication
170{
171 struct DxgiScreen
172 {
173 ComPtr<IDXGIAdapter1> adapter;
174 ComPtr<IDXGIOutput> output;
175 };
176
177public:
178 ~DxgiDuplication()
179 {
180 if (m_releaseFrame)
181 m_dup->ReleaseFrame();
182 }
183
184 ComStatus initialize(QScreen const *screen)
185 {
186 const QMaybe<DxgiScreen, ComStatus> dxgiScreen = findDxgiScreen(screen);
187 if (!dxgiScreen)
188 return dxgiScreen.error();
189
190 const ComPtr<IDXGIAdapter1> adapter = dxgiScreen->adapter;
191
192 ComPtr<ID3D11Device> d3d11dev;
193 HRESULT hr =
194 D3D11CreateDevice(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, nullptr, 0,
195 D3D11_SDK_VERSION, d3d11dev.GetAddressOf(), nullptr, nullptr);
196 if (FAILED(hr))
197 return { hr, "Failed to create ID3D11Device device"_L1 };
198
199 ComPtr<IDXGIOutput1> output;
200 hr = dxgiScreen->output.As(&output);
201 if (FAILED(hr))
202 return { hr, "Failed to create IDXGIOutput1"_L1 };
203
204 ComPtr<IDXGIOutputDuplication> dup;
205 hr = output->DuplicateOutput(d3d11dev.Get(), dup.GetAddressOf());
206 if (FAILED(hr))
207 return { hr, "Failed to duplicate IDXGIOutput1"_L1 };
208
209 m_adapter = dxgiScreen->adapter;
210 m_output = output;
211 m_device = d3d11dev;
212 m_dup = dup;
213 return { S_OK };
214 }
215
216 bool valid() const { return m_dup != nullptr; }
217
218 QSize getFrameSize() const
219 {
220 DXGI_OUTDUPL_DESC outputDesc = {};
221 m_dup->GetDesc(&outputDesc);
222
223 return { static_cast<int>(outputDesc.ModeDesc.Width),
224 static_cast<int>(outputDesc.ModeDesc.Height) };
225 }
226
227 QMaybe<std::unique_ptr<QD3D11TextureVideoBuffer>, ComStatus> getNextVideoFrame()
228 {
229 const ComProduct<ID3D11Texture2D> texture = getNextFrame();
230
231 if (!texture)
232 return texture.error();
233
234 return std::make_unique<QD3D11TextureVideoBuffer>(m_device, m_ctxMutex, *texture,
235 getFrameSize());
236 }
237
238private:
239 ComProduct<ID3D11Texture2D> getNextFrame()
240 {
241 std::scoped_lock guard{ *m_ctxMutex };
242
243 if (m_releaseFrame) {
244 m_releaseFrame = false;
245
246 HRESULT hr = m_dup->ReleaseFrame();
247
248 if (hr != S_OK)
249 return ComStatus{ hr, "Failed to release duplication frame."_L1 };
250 }
251
252 ComPtr<IDXGIResource> frame;
253 DXGI_OUTDUPL_FRAME_INFO info;
254
255 HRESULT hr = m_dup->AcquireNextFrame(0, &info, frame.GetAddressOf());
256
257 if (hr != S_OK)
258 return { unexpect, hr, "Failed to grab the screen content"_L1 };
259
260 m_releaseFrame = true;
261
262 ComPtr<ID3D11Texture2D> tex;
263 hr = frame.As(&tex);
264 if (hr != S_OK)
265 return { unexpect, hr, "Failed to obtain D3D11 texture"_L1 };
266
267 D3D11_TEXTURE2D_DESC texDesc = {};
268 tex->GetDesc(&texDesc);
269 texDesc.MiscFlags = 0;
270 texDesc.BindFlags = 0;
271
272 ComPtr<ID3D11Texture2D> texCopy;
273 hr = m_device->CreateTexture2D(&texDesc, nullptr, texCopy.GetAddressOf());
274 if (hr != S_OK)
275 return { unexpect, hr, "Failed to create texture with CPU access"_L1 };
276
277 ComPtr<ID3D11DeviceContext> ctx;
278 m_device->GetImmediateContext(ctx.GetAddressOf());
279 ctx->CopyResource(texCopy.Get(), tex.Get());
280
281 return texCopy;
282 }
283
284 static QMaybe<DxgiScreen, ComStatus> findDxgiScreen(const QScreen *screen)
285 {
286 if (!screen)
287 return { unexpect, E_FAIL, "Cannot find nullptr screen"_L1 };
288
289 auto *winScreen = screen->nativeInterface<QNativeInterface::QWindowsScreen>();
290 HMONITOR handle = winScreen ? winScreen->handle() : nullptr;
291
292 ComPtr<IDXGIFactory1> factory;
293 HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
294 if (FAILED(hr))
295 return { unexpect, hr, "Failed to create IDXGIFactory"_L1 };
296
297 ComPtr<IDXGIAdapter1> adapter;
298 for (quint32 i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) == S_OK; i++) {
299 ComPtr<IDXGIOutput> output;
300 for (quint32 j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) == S_OK; ++j) {
301 DXGI_OUTPUT_DESC desc = {};
302 output->GetDesc(&desc);
303 qCDebug(qLcScreenCaptureDxgi) << i << j << QString::fromWCharArray(desc.DeviceName);
304 auto match = handle ? handle == desc.Monitor
305 : QString::fromWCharArray(desc.DeviceName) == screen->name();
306 if (match)
307 return DxgiScreen{ adapter, output };
308 }
309 }
310 return { unexpect, DXGI_ERROR_NOT_FOUND,
311 "Could not find screen adapter "_L1 + screen->name() };
312 }
313
314 ComPtr<IDXGIAdapter1> m_adapter;
315 ComPtr<IDXGIOutput> m_output;
316 ComPtr<ID3D11Device> m_device;
317 ComPtr<IDXGIOutputDuplication> m_dup;
318 bool m_releaseFrame = false;
319 std::shared_ptr<QMutex> m_ctxMutex = std::make_shared<QMutex>();
320};
321
322QSize getPhysicalSizePixels(const QScreen *screen)
323{
324 const auto *winScreen = screen->nativeInterface<QNativeInterface::QWindowsScreen>();
325 if (!winScreen)
326 return {};
327
328 const HMONITOR handle = winScreen->handle();
329 if (!handle)
330 return {};
331
332 MONITORINFO info{};
333 info.cbSize = sizeof(info);
334
335 if (!GetMonitorInfoW(handle, &info))
336 return {};
337
338 return { info.rcMonitor.right - info.rcMonitor.left,
339 info.rcMonitor.bottom - info.rcMonitor.top };
340}
341
342QVideoFrameFormat getFrameFormat(QScreen* screen)
343{
344 const QSize screenSize = getPhysicalSizePixels(screen);
345
347 format.setStreamFrameRate(static_cast<int>(screen->refreshRate()));
348
349 return format;
350}
351
352} // namespace
353
355{
356public:
366
368 stop();
369 }
370
372 return m_format;
373 }
374
376 {
378 if (!m_duplication.valid()) {
379 const ComStatus status = m_duplication.initialize(m_screen);
380 if (!status) {
381 if (status.code() == E_ACCESSDENIED) {
382 // May occur for some time after pushing Ctrl+Alt+Del.
384 qCWarning(qLcScreenCaptureDxgi) << status.str();
385 }
386 return frame;
387 }
388 }
389
390 auto maybeBuf = m_duplication.getNextVideoFrame();
391 const ComStatus &status = maybeBuf.error();
392
393 if (status.code() == DXGI_ERROR_WAIT_TIMEOUT) {
394 // All is good, we just didn't get a new frame yet
396 } else if (status.code() == DXGI_ERROR_ACCESS_LOST) {
397 // Can happen for example when pushing Ctrl + Alt + Del
398 m_duplication = {};
400 qCWarning(qLcScreenCaptureDxgi) << status.str();
401 } else if (!status) {
403 qCWarning(qLcScreenCaptureDxgi) << status.str();
404 } else if (maybeBuf) {
405 std::unique_ptr<QD3D11TextureVideoBuffer> buffer = std::move(*maybeBuf);
406
407 const QSize bufSize = buffer->getSize();
408 if (bufSize != m_format.frameSize())
409 m_format.setFrameSize(bufSize);
410
411 frame = { buffer.release(), format() };
412 }
413
414 return frame;
415 }
416
417 protected:
419 {
420 m_duplication = DxgiDuplication();
421 const ComStatus status = m_duplication.initialize(m_screen);
422 if (!status) {
423 updateError(CaptureFailed, status.str());
424 return;
425 }
426
428 }
429
430private:
431 const QScreen *m_screen = nullptr;
432 QVideoFrameFormat m_format;
433 DxgiDuplication m_duplication;
434};
435
437
439
441{
442 if (m_grabber)
443 return m_grabber->format();
444 return {};
445}
446
448{
449 if (static_cast<bool>(m_grabber) == active)
450 return true;
451
452 if (m_grabber) {
453 m_grabber.reset();
454 } else {
455 auto screen = source<ScreenSource>();
456
458 return false;
459
460 const QVideoFrameFormat format = getFrameFormat(screen);
461 if (!format.isValid()) {
462 updateError(NotFound, QLatin1String("Unable to determine screen size or format"));
463 return false;
464 }
465
466 m_grabber.reset(new Grabber(*this, screen, format));
467 m_grabber->start();
468 }
469
470 return true;
471}
472
IOBluetoothDevice * device
The QAbstractVideoBuffer class is an abstraction for video data. \inmodule QtMultimedia.
\inmodule QtCore
void unmap() override
Releases the memory mapped by the map() function.
MapData map(QVideoFrame::MapMode mode) override
Independently maps the planes of a video buffer to memory.
QVideoFrame::MapMode mapMode() const override
QD3D11TextureVideoBuffer(const ComPtr< ID3D11Device > &device, std::shared_ptr< QMutex > &mutex, const ComPtr< ID3D11Texture2D > &texture, QSize size)
Grabber(QFFmpegScreenCaptureDxgi &screenCapture, QScreen *screen, const QVideoFrameFormat &format)
QVideoFrameFormat frameFormat() const override
~QFFmpegScreenCaptureDxgi() override
bool setActiveInternal(bool active) override
void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description)
void addFrameCallback(Object &object, Method method)
void updateError(QPlatformSurfaceCapture::Error error, const QString &description={})
Native interface to a screen.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void updateError(Error error, const QString &errorString)
bool checkScreenWithError(ScreenSource &screen)
void newVideoFrame(const QVideoFrame &)
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal refreshRate
the approximate vertical refresh rate of the screen in Hz
Definition qscreen.h:64
QString name
a user presentable string representing the screen
Definition qscreen.h:36
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
void setFrameSize(const QSize &size)
Sets the size of frames in a video stream to size.
QSize frameSize() const
Returns the dimensions of frames in a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
MapMode
Enumerates how a video buffer's data is mapped to system memory.
Definition qvideoframe.h:37
EGLContext ctx
QString str
[2]
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
Definition qcompare.h:63
static bool initialize()
Definition qctf.cpp:94
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
static constexpr QUnexpect unexpect
Definition qmaybe_p.h:30
GLuint64 GLenum void * handle
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint buffer
GLenum GLuint texture
GLsizei bufSize
GLint GLsizei GLsizei GLenum format
static QAbstractVideoBuffer::MapData mapData(const camera_frame_nv12_t &frame, unsigned char *baseAddress)
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
QScreen * screen
[1]
Definition main.cpp:29
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
unsigned int quint32
Definition qtypes.h:50
unsigned char uchar
Definition qtypes.h:32
QT_BEGIN_NAMESPACE typedef uchar * output
long HRESULT
QMutex mutex
[2]
QItemEditorFactory * factory
QFrame frame
[0]
QHostInfo info
[0]
char * toString(const MyType &t)
[31]