Qt 6.x
The Qt SDK
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
3// GPL-3.0-only
4
7#include <private/qabstractvideobuffer_p.h>
8#include <private/qmultimediautils_p.h>
9#include <private/qwindowsmultimediautils_p.h>
10#include <qpa/qplatformscreen_p.h>
11#include "qvideoframe.h"
12
13#include <qloggingcategory.h>
14#include <qwaitcondition.h>
15#include <qmutex.h>
16
17#include "D3d11.h"
18#include "dxgi1_2.h"
19
20#include <system_error>
21#include <thread>
22#include <chrono>
23
25
26static Q_LOGGING_CATEGORY(qLcScreenCaptureDxgi, "qt.multimedia.ffmpeg.screencapturedxgi")
27
28using namespace std::chrono;
29using namespace QWindowsMultimediaUtils;
30using namespace Qt::StringLiterals;
31
32namespace {
33
34// Convenience wrapper that combines an HRESULT
35// status code with an optional textual description.
36class ComStatus
37{
38public:
39 ComStatus() = default;
40 ComStatus(HRESULT hr) : m_hr{ hr } { }
41 ComStatus(HRESULT hr, QAnyStringView msg) : m_hr{ hr }, m_msg{ msg.toString() } { }
42
43 ComStatus(const ComStatus &) = default;
44 ComStatus(ComStatus &&) = default;
45 ComStatus &operator=(const ComStatus &) = default;
46 ComStatus &operator=(ComStatus &&) = default;
47
48 explicit operator bool() const { return m_hr == S_OK; }
49
50 HRESULT code() const { return m_hr; }
51 QString str() const
52 {
53 if (!m_msg)
54 return errorString(m_hr);
55 return *m_msg + " " + errorString(m_hr);
56 }
57
58private:
59 HRESULT m_hr = S_OK;
60 std::optional<QString> m_msg;
61};
62
63template <typename T>
64using ComProduct = QMaybe<ComPtr<T>, ComStatus>;
65
66}
67
69{
70public:
71 QD3D11TextureVideoBuffer(const ComPtr<ID3D11Device> &device, std::shared_ptr<QMutex> &mutex,
72 const ComPtr<ID3D11Texture2D> &texture, QSize size)
74 , m_device(device)
75 , m_texture(texture)
76 , m_ctxMutex(mutex)
77 , m_size(size)
78 {}
79
81 {
83 }
84
86 {
87 return m_mapMode;
88 }
89
91 {
93 if (!m_ctx && mode == QVideoFrame::ReadOnly) {
94 D3D11_TEXTURE2D_DESC texDesc = {};
95 m_texture->GetDesc(&texDesc);
96 texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
97 texDesc.Usage = D3D11_USAGE_STAGING;
98 texDesc.MiscFlags = 0;
99 texDesc.BindFlags = 0;
100
101 HRESULT hr = m_device->CreateTexture2D(&texDesc, nullptr, m_cpuTexture.GetAddressOf());
102 if (FAILED(hr)) {
103 qCDebug(qLcScreenCaptureDxgi) << "Failed to create texture with CPU access"
104 << std::system_category().message(hr).c_str();
105 qCDebug(qLcScreenCaptureDxgi) << m_device->GetDeviceRemovedReason();
106 return {};
107 }
108
109 m_device->GetImmediateContext(m_ctx.GetAddressOf());
110 m_ctxMutex->lock();
111 m_ctx->CopyResource(m_cpuTexture.Get(), m_texture.Get());
112
113 D3D11_MAPPED_SUBRESOURCE resource = {};
114 hr = m_ctx->Map(m_cpuTexture.Get(), 0, D3D11_MAP_READ, 0, &resource);
115 m_ctxMutex->unlock();
116 if (FAILED(hr)) {
117 qCDebug(qLcScreenCaptureDxgi) << "Failed to map texture" << m_cpuTexture.Get()
118 << std::system_category().message(hr).c_str();
119 return {};
120 }
121
122 m_mapMode = mode;
123 mapData.nPlanes = 1;
124 mapData.bytesPerLine[0] = int(resource.RowPitch);
125 mapData.data[0] = reinterpret_cast<uchar*>(resource.pData);
126 mapData.size[0] = m_size.height() * int(resource.RowPitch);
127 }
128
129 return mapData;
130 }
131
132 void unmap() override
133 {
134 if (m_mapMode == QVideoFrame::NotMapped)
135 return;
136 if (m_ctx) {
137 m_ctxMutex->lock();
138 m_ctx->Unmap(m_cpuTexture.Get(), 0);
139 m_ctxMutex->unlock();
140 m_ctx.Reset();
141 }
142 m_cpuTexture.Reset();
143 m_mapMode = QVideoFrame::NotMapped;
144 }
145
146private:
147 ComPtr<ID3D11Device> m_device;
148 ComPtr<ID3D11Texture2D> m_texture;
149 ComPtr<ID3D11Texture2D> m_cpuTexture;
150 ComPtr<ID3D11DeviceContext> m_ctx;
151 std::shared_ptr<QMutex> m_ctxMutex;
152 QSize m_size;
154};
155
156namespace {
157class DxgiDuplication
158{
159 struct DxgiScreen
160 {
161 ComPtr<IDXGIAdapter1> adapter;
162 ComPtr<IDXGIOutput> output;
163 };
164
165public:
166 ~DxgiDuplication()
167 {
168 if (m_releaseFrame)
169 m_dup->ReleaseFrame();
170 }
171
172 ComStatus initialize(QScreen const *screen)
173 {
174 const QMaybe<DxgiScreen, ComStatus> dxgiScreen = findDxgiScreen(screen);
175 if (!dxgiScreen)
176 return dxgiScreen.error();
177
178 const ComPtr<IDXGIAdapter1> adapter = dxgiScreen->adapter;
179
180 ComPtr<ID3D11Device> d3d11dev;
181 HRESULT hr =
182 D3D11CreateDevice(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, nullptr, 0,
183 D3D11_SDK_VERSION, d3d11dev.GetAddressOf(), nullptr, nullptr);
184 if (FAILED(hr))
185 return { hr, "Failed to create ID3D11Device device"_L1 };
186
187 ComPtr<IDXGIOutput1> output;
188 hr = dxgiScreen->output.As(&output);
189 if (FAILED(hr))
190 return { hr, "Failed to create IDXGIOutput1"_L1 };
191
192 ComPtr<IDXGIOutputDuplication> dup;
193 hr = output->DuplicateOutput(d3d11dev.Get(), dup.GetAddressOf());
194 if (FAILED(hr))
195 return { hr, "Failed to duplicate IDXGIOutput1"_L1 };
196
197 m_adapter = dxgiScreen->adapter;
198 m_output = output;
199 m_device = d3d11dev;
200 m_dup = dup;
201 return { S_OK };
202 }
203
204 bool valid() const { return m_dup != nullptr; }
205
206 QSize getFrameSize() const
207 {
208 DXGI_OUTDUPL_DESC outputDesc = {};
209 m_dup->GetDesc(&outputDesc);
210
211 return { static_cast<int>(outputDesc.ModeDesc.Width),
212 static_cast<int>(outputDesc.ModeDesc.Height) };
213 }
214
215 QMaybe<std::unique_ptr<QD3D11TextureVideoBuffer>, ComStatus> getNextVideoFrame()
216 {
217 const ComProduct<ID3D11Texture2D> texture = getNextFrame();
218
219 if (!texture)
220 return texture.error();
221
222 return std::make_unique<QD3D11TextureVideoBuffer>(m_device, m_ctxMutex, *texture,
223 getFrameSize());
224 }
225
226private:
227 ComProduct<ID3D11Texture2D> getNextFrame()
228 {
229 std::scoped_lock guard{ *m_ctxMutex };
230
231 if (m_releaseFrame) {
232 m_releaseFrame = false;
233
234 HRESULT hr = m_dup->ReleaseFrame();
235
236 if (hr != S_OK)
237 return ComStatus{ hr, "Failed to release duplication frame."_L1 };
238 }
239
240 ComPtr<IDXGIResource> frame;
241 DXGI_OUTDUPL_FRAME_INFO info;
242
243 HRESULT hr = m_dup->AcquireNextFrame(0, &info, frame.GetAddressOf());
244
245 if (hr != S_OK)
246 return { unexpect, hr, "Failed to grab the screen content"_L1 };
247
248 m_releaseFrame = true;
249
250 ComPtr<ID3D11Texture2D> tex;
251 hr = frame.As(&tex);
252 if (hr != S_OK)
253 return { unexpect, hr, "Failed to obtain D3D11 texture"_L1 };
254
255 D3D11_TEXTURE2D_DESC texDesc = {};
256 tex->GetDesc(&texDesc);
257 texDesc.MiscFlags = 0;
258 texDesc.BindFlags = 0;
259
260 ComPtr<ID3D11Texture2D> texCopy;
261 hr = m_device->CreateTexture2D(&texDesc, nullptr, texCopy.GetAddressOf());
262 if (hr != S_OK)
263 return { unexpect, hr, "Failed to create texture with CPU access"_L1 };
264
265 ComPtr<ID3D11DeviceContext> ctx;
266 m_device->GetImmediateContext(ctx.GetAddressOf());
267 ctx->CopyResource(texCopy.Get(), tex.Get());
268
269 return texCopy;
270 }
271
272 static QMaybe<DxgiScreen, ComStatus> findDxgiScreen(const QScreen *screen)
273 {
274 if (!screen)
275 return { unexpect, E_FAIL, "Cannot find nullptr screen"_L1 };
276
277 auto *winScreen = screen->nativeInterface<QNativeInterface::Private::QWindowsScreen>();
278 HMONITOR handle = winScreen ? winScreen->handle() : nullptr;
279
280 ComPtr<IDXGIFactory1> factory;
281 HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
282 if (FAILED(hr))
283 return { unexpect, hr, "Failed to create IDXGIFactory"_L1 };
284
285 ComPtr<IDXGIAdapter1> adapter;
286 for (quint32 i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) == S_OK; i++) {
287 ComPtr<IDXGIOutput> output;
288 for (quint32 j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) == S_OK; ++j) {
289 DXGI_OUTPUT_DESC desc = {};
290 output->GetDesc(&desc);
291 qCDebug(qLcScreenCaptureDxgi) << i << j << QString::fromWCharArray(desc.DeviceName);
292 auto match = handle ? handle == desc.Monitor
293 : QString::fromWCharArray(desc.DeviceName) == screen->name();
294 if (match)
295 return DxgiScreen{ adapter, output };
296 }
297 }
298 return { unexpect, DXGI_ERROR_NOT_FOUND,
299 "Could not find screen adapter "_L1 + screen->name() };
300 }
301
302 ComPtr<IDXGIAdapter1> m_adapter;
303 ComPtr<IDXGIOutput> m_output;
304 ComPtr<ID3D11Device> m_device;
305 ComPtr<IDXGIOutputDuplication> m_dup;
306 bool m_releaseFrame = false;
307 std::shared_ptr<QMutex> m_ctxMutex = std::make_shared<QMutex>();
308};
309} // namespace
310
312{
313public:
316 , m_screen(screen)
317 {
321 }
322
324 stop();
325 }
326
327 void run() override
328 {
329 m_duplication = DxgiDuplication();
330 const ComStatus status = m_duplication.initialize(m_screen);
331
332 QSize frameSize = m_duplication.getFrameSize();
334
335 m_formatMutex.lock();
336 m_format = frameFormat;
337 m_format.setFrameRate(int(frameRate()));
338 m_formatMutex.unlock();
339 m_waitForFormat.wakeAll();
340
341 if (!status) {
343 return;
344 }
346 }
347
349 QMutexLocker locker(&m_formatMutex);
350 if (!m_format.isValid())
351 m_waitForFormat.wait(&m_formatMutex);
352 return m_format;
353 }
354
356 {
358 if (!m_duplication.valid()) {
359 const ComStatus status = m_duplication.initialize(m_screen);
360 if (!status) {
361 if (status.code() == E_ACCESSDENIED) {
362 // May occur for some time after pushing Ctrl+Alt+Del.
364 qCWarning(qLcScreenCaptureDxgi) << status.str();
365 }
366 return frame;
367 }
368 }
369
370 auto maybeBuf = m_duplication.getNextVideoFrame();
371 const ComStatus &status = maybeBuf.error();
372
373 if (status.code() == DXGI_ERROR_WAIT_TIMEOUT) {
374 // All is good, we just didn't get a new frame yet
376 } else if (status.code() == DXGI_ERROR_ACCESS_LOST) {
377 // Can happen for example when pushing Ctrl + Alt + Del
378 m_duplication = {};
380 qCWarning(qLcScreenCaptureDxgi) << status.str();
381 } else if (!status) {
383 qCWarning(qLcScreenCaptureDxgi) << status.str();
384 } else if (maybeBuf) {
385 std::unique_ptr<QD3D11TextureVideoBuffer> buffer = std::move(*maybeBuf);
386 frame = { buffer.release(), format() };
387 }
388
389 return frame;
390 }
391
392private:
393 QScreen *const m_screen = nullptr;
394 QWaitCondition m_waitForFormat;
395 QVideoFrameFormat m_format;
396 QMutex m_formatMutex;
397 DxgiDuplication m_duplication;
398};
399
401
403
405{
406 if (m_grabber)
407 return m_grabber->format();
408 return {};
409}
410
412{
413 if (static_cast<bool>(m_grabber) == active)
414 return true;
415
416 if (m_grabber) {
417 m_grabber.reset();
418 } else {
420
422 return false;
423
424 m_grabber.reset(new Grabber(*this, screen));
425 m_grabber->start();
426 }
427
428 return true;
429}
430
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)
QVideoFrameFormat frameFormat() const override
~QFFmpegScreenCaptureDxgi() override
bool setActiveInternal(bool active) override
void addFrameCallback(Object &object, Method method)
void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description)
constexpr const Error & error() const
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qmutex.h:285
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:293
void lock() noexcept
Locks the mutex.
Definition qmutex.h:290
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)
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:132
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1164
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
bool isValid() const
Identifies if a video surface format has a valid pixel format and frame size.
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
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
EGLContext ctx
QString str
[2]
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
static bool initialize()
Definition qctf.cpp:67
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
static constexpr QUnexpect unexpect
GLuint64 GLenum void * handle
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint buffer
GLenum GLuint texture
GLsizei GLsizei GLchar * source
static QAbstractVideoBuffer::MapData mapData(const camera_frame_nv12_t &frame, unsigned char *baseAddress)
static constexpr QSize frameSize(const T &frame)
QScreen * screen
[1]
Definition main.cpp:29
@ desc
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
unsigned int quint32
Definition qtypes.h:45
unsigned char uchar
Definition qtypes.h:27
QT_BEGIN_NAMESPACE typedef uchar * output
long HRESULT
QFileInfo info(fileName)
[8]
QMutex mutex
[2]
QItemEditorFactory * factory
QFrame frame
[0]
char * toString(const MyType &t)
[31]