7#include <private/qmultimediautils_p.h>
8#include <private/qvideoframe_p.h>
9#include <QtGui/qscreen_platform.h>
12#include <qloggingcategory.h>
13#include <qwaitcondition.h>
15#include <QtCore/private/qsystemerror_p.h>
16#include <QtCore/private/qexpected_p.h>
21#include <system_error>
29using namespace Qt::StringLiterals;
38 ComStatus() =
default;
39 ComStatus(HRESULT hr) : m_hr{ hr } { }
40 ComStatus(HRESULT hr, QAnyStringView msg) : m_hr{ hr }, m_msg{ msg.toString() } { }
42 ComStatus(
const ComStatus &) =
default;
43 ComStatus(ComStatus &&) =
default;
44 ComStatus &operator=(
const ComStatus &) =
default;
45 ComStatus &operator=(ComStatus &&) =
default;
47 explicit operator
bool()
const {
return m_hr == S_OK; }
49 HRESULT code()
const {
return m_hr; }
53 return QSystemError::windowsComString(m_hr);
54 return *m_msg + u" " + QSystemError::windowsComString(m_hr);
59 std::optional<QString> m_msg;
63using ComProduct = q23::expected<ComPtr<T>, ComStatus>;
70 const ComPtr<ID3D11Texture2D> &texture)
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;
87 HRESULT hr = m_device->CreateTexture2D(&texDesc,
nullptr, m_cpuTexture.GetAddressOf());
89 qCDebug(qLcScreenCaptureDxgi) <<
"Failed to create texture with CPU access"
90 << std::system_category().message(hr).c_str();
91 qCDebug(qLcScreenCaptureDxgi) << m_device->GetDeviceRemovedReason();
95 m_device->GetImmediateContext(m_ctx.GetAddressOf());
97 m_ctx->CopyResource(m_cpuTexture.Get(), m_texture.Get());
99 D3D11_MAPPED_SUBRESOURCE resource = {};
100 hr = m_ctx->Map(m_cpuTexture.Get(), 0, D3D11_MAP_READ, 0, &resource);
101 m_ctxMutex->unlock();
103 qCDebug(qLcScreenCaptureDxgi) <<
"Failed to map texture" << m_cpuTexture.Get()
104 << std::system_category().message(hr).c_str();
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);
120 if (m_mapMode == QVideoFrame::NotMapped)
124 m_ctx->Unmap(m_cpuTexture.Get(), 0);
125 m_ctxMutex->unlock();
128 m_cpuTexture.Reset();
129 m_mapMode = QVideoFrame::NotMapped;
139 D3D11_TEXTURE2D_DESC desc{};
140 m_texture->GetDesc(&desc);
142 return {
static_cast<
int>(desc.Width),
static_cast<
int>(desc.Height) };
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;
158 q23::expected<QSize, ComStatus> physicalSize()
const
160 DXGI_OUTPUT_DESC desc{};
161 const HRESULT hr = output->GetDesc(&desc);
163 return q23::unexpected{ hr };
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();
172 q23::expected<QtVideo::Rotation, ComStatus> rotation()
const
174 DXGI_OUTPUT_DESC desc{};
175 const HRESULT hr = output->GetDesc(&desc);
177 return q23::unexpected{ hr };
179 switch (desc.Rotation) {
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;
188 return QtVideo::Rotation::None;
192 ComPtr<IDXGIAdapter1> adapter;
193 ComPtr<IDXGIOutput> output;
196q23::expected<DxgiScreen, ComStatus> findDxgiScreen(
const QScreen *screen)
199 return q23::unexpected{ ComStatus{ E_FAIL,
"Cannot find nullptr screen"_L1 } };
201 auto *winScreen = screen->nativeInterface<QNativeInterface::QWindowsScreen>();
202 HMONITOR handle = winScreen ? winScreen->handle() :
nullptr;
204 ComPtr<IDXGIFactory1> factory;
205 HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
207 return q23::unexpected{ ComStatus{ hr,
"Failed to create IDXGIFactory"_L1 } };
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();
219 return DxgiScreen{ adapter, output };
222 return q23::unexpected{ ComStatus{
223 DXGI_ERROR_NOT_FOUND,
224 "Could not find screen adapter "_L1 + screen->name(),
234 m_dup->ReleaseFrame();
237 ComStatus initialize(QScreen
const *screen)
239 q23::expected<DxgiScreen, ComStatus> dxgiScreen = findDxgiScreen(screen);
241 return dxgiScreen.error();
243 const ComPtr<IDXGIAdapter1> adapter = dxgiScreen->adapter;
245 ComPtr<ID3D11Device> d3d11dev;
247 D3D11CreateDevice(adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN,
nullptr, 0,
nullptr, 0,
248 D3D11_SDK_VERSION, d3d11dev.GetAddressOf(),
nullptr,
nullptr);
250 return { hr,
"Failed to create ID3D11Device device"_L1 };
252 ComPtr<IDXGIOutput1> output;
253 hr = dxgiScreen->output.As(&output);
255 return { hr,
"Failed to create IDXGIOutput1"_L1 };
257 ComPtr<IDXGIOutputDuplication> dup;
258 hr = output->DuplicateOutput(d3d11dev.Get(), dup.GetAddressOf());
260 return { hr,
"Failed to duplicate IDXGIOutput1"_L1 };
262 m_adapter = dxgiScreen->adapter;
269 bool valid()
const {
return m_dup !=
nullptr; }
270 q23::expected<std::unique_ptr<QD3D11TextureVideoBuffer>, ComStatus> getNextVideoFrame()
272 const ComProduct<ID3D11Texture2D> texture = getNextFrame();
275 return q23::unexpected{ texture.error() };
277 return std::make_unique<QD3D11TextureVideoBuffer>(m_device, m_ctxMutex, *texture);
281 ComProduct<ID3D11Texture2D> getNextFrame()
283 std::scoped_lock guard{ *m_ctxMutex };
285 if (m_releaseFrame) {
286 m_releaseFrame =
false;
288 HRESULT hr = m_dup->ReleaseFrame();
291 return q23::unexpected{ ComStatus{ hr,
292 "Failed to release duplication frame."_L1 } };
295 ComPtr<IDXGIResource> frame;
296 DXGI_OUTDUPL_FRAME_INFO info;
298 HRESULT hr = m_dup->AcquireNextFrame(0, &info, frame.GetAddressOf());
301 return q23::unexpected{ ComStatus{ hr,
"Failed to grab the screen content"_L1 } };
303 m_releaseFrame =
true;
305 ComPtr<ID3D11Texture2D> tex;
308 return q23::unexpected{ ComStatus{ hr,
"Failed to obtain D3D11 texture"_L1 } };
310 D3D11_TEXTURE2D_DESC texDesc = {};
311 tex->GetDesc(&texDesc);
312 texDesc.MiscFlags = 0;
313 texDesc.BindFlags = 0;
315 ComPtr<ID3D11Texture2D> texCopy;
316 hr = m_device->CreateTexture2D(&texDesc,
nullptr, texCopy.GetAddressOf());
318 return q23::unexpected{ ComStatus{ hr,
319 "Failed to create texture with CPU access"_L1 } };
321 ComPtr<ID3D11DeviceContext> ctx;
322 m_device->GetImmediateContext(ctx.GetAddressOf());
323 ctx->CopyResource(texCopy.Get(), tex.Get());
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>();
336q23::expected<QVideoFrameFormat, ComStatus> getFrameFormat(
const QScreen *screen)
338 const auto dxgiScreen = findDxgiScreen(screen);
340 return q23::unexpected{ dxgiScreen.error() };
342 const auto screenSize = dxgiScreen->physicalSize();
344 return q23::unexpected{ screenSize.error() };
346 const auto rotation = dxgiScreen->rotation();
348 return q23::unexpected{ rotation.error() };
351 format.setRotation(*rotation);
352 format.setStreamFrameRate(
static_cast<
int>(screen->refreshRate()));
362 Grabber(QFFmpegScreenCaptureDxgi &screenCapture, QScreen *screen,
363 const QVideoFrameFormat &format)
367 setFrameRate(screen->refreshRate());
368 addFrameCallback(screenCapture, &QFFmpegScreenCaptureDxgi::newVideoFrame);
369 connect(
this, &Grabber::errorUpdated, &screenCapture, &QFFmpegScreenCaptureDxgi::updateError);
383 if (!m_duplication.valid()) {
384 const ComStatus status = m_duplication.initialize(m_screen);
386 if (status.code() == E_ACCESSDENIED) {
388 updateError(QPlatformSurfaceCapture::NoError, status.str());
389 qCWarning(qLcScreenCaptureDxgi) << status.str();
395 auto maybeBuf = m_duplication.getNextVideoFrame();
397 std::unique_ptr<QD3D11TextureVideoBuffer> buffer =
std::move(*maybeBuf);
399 const QSize bufSize = buffer->getSize();
400 if (bufSize != m_format.frameSize())
401 m_format.setFrameSize(bufSize);
403 frame = QVideoFramePrivate::createFrame(std::move(buffer), format());
405 const ComStatus &status = maybeBuf.error();
407 if (status.code() == DXGI_ERROR_WAIT_TIMEOUT) {
409 updateError(QPlatformSurfaceCapture::NoError, status.str());
410 }
else if (status.code() == DXGI_ERROR_ACCESS_LOST) {
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();
427 m_duplication = DxgiDuplication();
428 const ComStatus status = m_duplication.initialize(m_screen);
430 updateError(CaptureFailed, status.str());
438 const QScreen *m_screen =
nullptr;
439 QVideoFrameFormat m_format;
440 DxgiDuplication m_duplication;
443QFFmpegScreenCaptureDxgi::QFFmpegScreenCaptureDxgi() : QPlatformSurfaceCapture(ScreenSource{}) { }
445QFFmpegScreenCaptureDxgi::~QFFmpegScreenCaptureDxgi() =
default;
447QVideoFrameFormat QFFmpegScreenCaptureDxgi::frameFormat()
const
450 return m_grabber->format();
454bool QFFmpegScreenCaptureDxgi::setActiveInternal(
bool active)
456 if (
static_cast<
bool>(m_grabber) == active)
462 auto screen = source<ScreenSource>();
464 if (!checkScreenWithError(screen))
467 const auto format = getFrameFormat(screen);
469 updateError(NotFound,
"Unable to determine screen size or format"_L1 + format.error().str());
473 m_grabber.reset(
new Grabber(*
this, screen, *format));
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)
~QD3D11TextureVideoBuffer()
QVideoFrameFormat format() const override
Gets \l QVideoFrameFormat of the underlying video buffer.
Grabber(QFFmpegScreenCaptureDxgi &screenCapture, QScreen *screen, const QVideoFrameFormat &format)
QVideoFrame grabFrame() override
QVideoFrameFormat format()
void initializeGrabbingContext() override
virtual void initializeGrabbingContext()
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)