WinRT屏幕共享应用:拆分捕获代码及解决边框、失真问题
Windows窗口捕获(WinRT)问题解决方案
1. 拆分单体代码为初始化、捕获、清理函数
将原有一次性执行的代码拆分为类成员函数,复用D3D设备、捕获会话等资源,避免每次捕获重复初始化:
类成员变量定义
class WindowCapturer { private: winrt::apartment_context m_apartment; winrt::com_ptr<ID3D11Device> m_d3dDevice; winrt::com_ptr<ID3D11DeviceContext> m_d3dContext; winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device; winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool; winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_session; winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_captureItem; HWND m_hwnd = nullptr; bool m_isCapturing = false; std::atomic<bool> m_frameArrived = false; winrt::com_ptr<ID3D11Texture2D> m_capturedTexture; winrt::event_token m_frameArrivedToken; };
初始化函数
HRESULT WindowCapturer::Init(HWND hwnd) { m_hwnd = hwnd; // 初始化COM(仅执行一次) winrt::init_apartment(winrt::apartment_type::multi_threaded); m_apartment = winrt::apartment_context(); // 创建D3D设备 HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, m_d3dDevice.put(), nullptr, nullptr ); if (FAILED(hr)) return hr; m_d3dDevice->GetImmediateContext(m_d3dContext.put()); // 转换为WinRT兼容的Direct3D设备 auto dxgiDevice = m_d3dDevice.as<IDXGIDevice>(); winrt::com_ptr<::IInspectable> inspectable; hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), inspectable.put()); if (FAILED(hr)) return hr; m_device = inspectable.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>(); // 创建捕获项 const auto activationFactory = winrt::get_activation_factory<winrt::Windows::Graphics::Capture::GraphicsCaptureItem>(); auto interopFactory = activationFactory.as<IGraphicsCaptureItemInterop>(); hr = interopFactory->CreateForWindow( hwnd, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), reinterpret_cast<void**>(winrt::put_abi(m_captureItem)) ); if (FAILED(hr)) return hr; // 获取窗口大小并创建帧池 RECT rect{}; DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT)); const auto size = winrt::Windows::Graphics::SizeInt32{ rect.right - rect.left, rect.bottom - rect.top }; m_framePool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::Create( m_device, winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, size ); // 注册帧到达回调 m_frameArrivedToken = m_framePool.FrameArrived([this](auto& pool, auto&) { if (m_frameArrived) return; auto frame = pool.TryGetNextFrame(); auto access = frame.Surface().as<IDirect3DDxgiInterfaceAccess>(); access->GetInterface(winrt::guid_of<ID3D11Texture2D>(), m_capturedTexture.put_void()); m_frameArrived = true; }); // 创建捕获会话 m_session = m_framePool.CreateCaptureSession(m_captureItem); return S_OK; }
捕获帧函数
MyImageContainer WindowCapturer::CaptureFrame() { if (!m_hwnd || !m_session) { return error "未初始化捕获器"; } m_frameArrived = false; m_session.StartCapture(); // 等待帧到达(可替换为更高效的同步机制,如事件) MSG msg; clock_t timer = clock(); while (!m_frameArrived) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) { DispatchMessage(&msg); } if (clock() - timer > 20000) { m_session.Close(); return error "捕获超时"; } } m_session.Close(); // 复制纹理到可CPU读取的 staging 纹理 D3D11_TEXTURE2D_DESC textureDesc{}; m_capturedTexture->GetDesc(&textureDesc); D3D11_TEXTURE2D_DESC stagingDesc = textureDesc; stagingDesc.Usage = D3D11_USAGE_STAGING; stagingDesc.BindFlags = 0; stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; stagingDesc.MiscFlags = 0; winrt::com_ptr<ID3D11Texture2D> stagingTexture; HRESULT hr = m_d3dDevice->CreateTexture2D(&stagingDesc, NULL, stagingTexture.put()); if (FAILED(hr)) { return error "创建 staging 纹理失败"; } m_d3dContext->CopyResource(stagingTexture.get(), m_capturedTexture.get()); // 映射纹理并读取数据 D3D11_MAPPED_SUBRESOURCE resource{}; hr = m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &resource); if (FAILED(hr) || !resource.pData) { return error "映射纹理失败"; } UINT rowPitch = textureDesc.Width * 4; MyImageContainer img; img.imageBuffer.resize(textureDesc.Height * rowPitch); BYTE* dst = img.imageBuffer.data(); BYTE* src = static_cast<BYTE*>(resource.pData); for (size_t h = 0; h < textureDesc.Height; ++h) { memcpy_s(dst, rowPitch, src, std::min<UINT>(rowPitch, resource.RowPitch)); src += resource.RowPitch; dst += rowPitch; } m_d3dContext->Unmap(stagingTexture.get(), 0); return img; }
清理函数
void WindowCapturer::Cleanup() { if (m_session) { m_session.Close(); } m_framePool.Close(); m_frameArrivedToken = {}; m_captureItem = nullptr; m_device = nullptr; m_d3dContext = nullptr; m_d3dDevice = nullptr; m_hwnd = nullptr; winrt::uninit_apartment(); }
2. Win10下去除捕获闪烁边框的替代方案
Win10不支持IsBorderRequired属性,可通过DWM窗口隐藏(Cloak)机制临时屏蔽系统绘制的捕获边框:
实现步骤
- 在启动捕获前,调用
DwmSetWindowAttribute将窗口设置为隐藏状态 - 启动捕获后立即取消隐藏,避免窗口长时间不可见
代码修改(在CaptureFrame函数中添加)
// 启动捕获前隐藏窗口 DWORD cloakValue = DWM_CLOAKED_INVISIBLE; DwmSetWindowAttribute(m_hwnd, DWMWA_CLOAK, &cloakValue, sizeof(cloakValue)); m_session.StartCapture(); // 立即取消隐藏 cloakValue = 0; DwmSetWindowAttribute(m_hwnd, DWMWA_CLOAK, &cloakValue, sizeof(cloakValue));
注意:需包含
dwmapi.h并链接dwmapi.lib;此方法仅屏蔽系统捕获边框,窗口本身仍正常显示。
3. 非全屏窗口捕获失真排查思路
步骤1:检查窗口大小获取逻辑
- 确认
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS)获取的是窗口实际像素尺寸,而非DPI缩放后的逻辑尺寸 - 对比
window.size.x/y与DWMWA_EXTENDED_FRAME_BOUNDS计算出的宽高是否一致,若不一致则以实际捕获的纹理尺寸为准(避免硬编码window.size)
步骤2:修复纹理复制逻辑
原代码中存在无效语句capturedTextureDesc.Height * 4 - lBmpRowPitch;,需删除或修正。确保行间距(RowPitch)处理正确:
- 直接使用捕获纹理的实际
RowPitch进行数据复制,避免强制截断导致的图像错位 - 检查
stagingDesc.Width/Height是否与捕获纹理的尺寸完全一致,禁止手动修改为window.size(除非window.size是经过DPI校正的实际像素值)
步骤3:排查DPI适配问题
- Win10高DPI环境下,需确保窗口已启用DPI感知(通过manifest或
SetProcessDpiAwarenessContext设置) - 若未启用DPI感知,
DWMWA_EXTENDED_FRAME_BOUNDS返回的尺寸可能是逻辑尺寸,需乘以DPI缩放系数转换为像素尺寸
步骤4:检查帧池尺寸适配
- 当窗口大小变化时,需调用
m_framePool.Recreate(m_device, pixelFormat, bufferCount, newSize)重新调整帧池尺寸,避免帧池尺寸与窗口实际尺寸不匹配导致的拉伸失真
内容的提问来源于stack exchange,提问作者bluetooth16
相关产品推荐
相关产品推荐

