Windows Graphics Capture (WGC) 屏幕捕获简介

通行证任务

Windows Graphics Capture (WGC) 屏幕捕获简介

简介

Windows.Graphics.Capture 是微软在 Windows 10 1903 新增的一套官方"屏幕捕获(屏幕录制)API"。它是 WinRT API(Windows Runtime),可以在 C++、C#、Rust 等语言中使用。

相比传统的 GDI 截屏、DXGI Desktop Duplication,WGC 具有以下优势:

官方支持,兼容性好

性能优秀,GPU 加速

原生支持HDR

支持窗口和显示器捕获

WGC 完整使用流程

复制代码

┌─────────────────────────────────────────────────────────────┐

│ WGC 捕获流程 │

├─────────────────────────────────────────────────────────────┤

│ │

│ 1. 创建 D3D11 Device │

│ D3D11CreateDevice() + BGRA_SUPPORT │

│ ↓ │

│ 2. 获取捕获目标 │

│ GraphicsCaptureItem (窗口/显示器) │

│ ↓ │

│ 3. 创建 FramePool │

│ Direct3D11CaptureFramePool::Create() │

│ ↓ │

│ 4. 创建 CaptureSession │

│ framePool.CreateCaptureSession(item) │

│ ↓ │

│ 5. 注册帧回调 │

│ framePool.FrameArrived += OnFrameArrived │

│ ↓ │

│ 6. 开始捕获 │

│ session.StartCapture() │

│ ↓ │

│ 7. 处理帧数据 │

│ frame = framePool.TryGetNextFrame() │

│ ↓ │

│ 8. 停止捕获 │

│ session.Close() / framePool.Close() │

│ │

└─────────────────────────────────────────────────────────────┘

基础概念 Q&A

Win32 与 WinRT 的区别

Win32

WinRT

Windows 最传统、最基础的系统 API

现代 Windows Runtime API

使用 C/C++ 接口,C# 可通过 P/Invoke 调用

可使用 C++/WinRT、C# 调用

控制台程序、DLL 项目默认是 Win32

Win32 程序中可使用 C++/WinRT 调用 WinRT

具备系统级能力,权限最大

有沙箱机制,不能做强力系统操作

WPF 与 UWP 的区别

WPF(Windows Presentation Foundation):基于 .NET、XAML,用于构建桌面 UI 的 C# 框架

UWP(Universal Windows Platform):使用 WinRT 作为底层的一种 App 类型,主要使用 C# 开发

核心组件详解

CaptureSession - 捕获生命周期控制器

使用 WGC 的重要步骤之一就是创建 CaptureSession,其功能是控制捕获的生命周期,是捕获的遥控器。

主要功能:

开始捕获 StartCapture()

停止捕获 Close()

设置是否显示红框 IsCursorCaptureEnabled

指定捕获目标(窗口/桌面)

Direct3D11CaptureFramePool - 帧数据入口

Direct3D11CaptureFramePool 是 WGC 捕获帧的唯一入口,用户只能通过它来获取 WGC 捕获的帧。

注意:虽然名字中有 Direct3D11,但它是 WGC 的组件,不是 D3D11 的。之所以叫这个名字是因为输出帧是 D3D11 Texture。

核心特性:

这是一个 FIFO 队列

队列满时,新帧会被丢弃

保证系统捕获线程永远不会被用户代码阻塞

WGC 捕获数据流:

复制代码

系统捕获到新帧

FramePool 内部存储该帧(最多 N 帧)

触发 FrameArrived 回调

用户调用 TryGetNextFrame() 获取帧

FramePool 的两种创建方式

方法

特点

适用场景

Create()

需要 DispatcherQueue,FrameArrived 在当前线程触发

有 UI 消息循环的应用

CreateFreeThreaded()

不需要 DispatcherQueue,回调在 FramePool 内部线程触发

后台服务、无 UI 应用

CreateFreeThreaded 注意事项:

如果回调函数耗时,不会影响系统底层 GPU 生成帧

但会导致丢帧,影响捕获结果

GraphicsCaptureItem - 捕获目标句柄

GraphicsCaptureItem 表示你想让 Windows 进行屏幕捕获的目标对象,可以是:

一个窗口(HWND)

一个显示器(Monitor)

一个虚拟桌面区域(Windows 11)

一个 AppWindow(WinUI)

它不是纹理,不是帧,不是画面本身。它是一个 WinRT 对象(WinRT COM),用于告诉 WGC 你想捕获什么。

IDXGISwapChain3 - 交换链接口

交换链(SwapChain)是 Direct3D 应用用来:

存放渲染的帧(BackBuffer)

将 BackBuffer 显示到屏幕(Present)

管理帧之间的切换(swap / flip)

如果不需要将捕获的帧渲染出来,就不需要 IDXGISwapChain3。

窗口录制 vs 显示器录制

特性

窗口录制

显示器录制

捕获范围

单个窗口客户区

整个显示器

性能开销

较低

较高

DPI 感知

自动处理

需要处理

隐私保护

仅捕获窗口内容

捕获所有可见内容

适用场景

单应用录制、远程桌面

全屏录制、直播推流

窗口大小变化处理:

当捕获的窗口大小改变时,需要重建 FramePool:

cpp

复制代码

void OnItemClosed(GraphicsCaptureItem const&, IInspectable const&)

{

// 窗口关闭时的处理

}

// 监听窗口大小变化

item.Closed({ this, &OnItemClosed });

// 窗口大小变化时需要:

// 1. 关闭旧的 FramePool

// 2. 使用新的尺寸创建 FramePool

// 3. 重新创建 CaptureSession

帧率控制与丢帧处理

FramePool 缓冲区大小

cpp

复制代码

// 创建 FramePool 时指定缓冲区大小

auto framePool = Direct3D11CaptureFramePool::Create(

device,

DirectXPixelFormat::B8G8R8A8UIntNormalized,

2, // 缓冲区大小:建议 2-3 帧

item.Size()

);

缓冲区大小选择建议:

1 帧:最低延迟,但容易丢帧

2-3 帧:平衡延迟和稳定性,推荐

4+ 帧:更稳定,但延迟增加

丢帧原因分析

原因

解决方案

回调处理时间过长

优化帧处理逻辑,减少回调内工作量

缓冲区过小

适当增加 FramePool 缓冲区大小

GPU 负载过高

降低分辨率或帧率

内存不足

及时释放帧资源,避免内存累积

帧率限制方案

如果需要限制捕获帧率(如 30fps),可以在回调中控制:

cpp

复制代码

std::chrono::steady_clock::time_point lastFrameTime;

const auto frameInterval = std::chrono::milliseconds(33); // ~30fps

void OnFrameArrived(Direct3D11CaptureFramePool const& sender, IInspectable const&)

{

auto now = std::chrono::steady_clock::now();

if (now - lastFrameTime < frameInterval)

{

// 跳过此帧

auto frame = sender.TryGetNextFrame();

return; // 帧会自动释放

}

lastFrameTime = now;

// 正常处理帧

auto frame = sender.TryGetNextFrame();

ProcessFrame(frame);

}

高级特性

Dirty Region 机制

Dirty Region(脏矩形):每一帧窗口可能只有一部分变化,这些变化的区域就叫 Dirty Region。

通过 TryGetNextFrame() 拿到的永远是一张完整的 2D 纹理。开启 Dirty Region 后,系统会通过其他字段告诉你哪些地方发生了变化,你可以选择是否使用这个信息进行优化。

HDR 兼容性

在创建 FramePool 时指定格式:

cpp

复制代码

// SDR 格式(推荐,兼容性好)

DirectXPixelFormat::B8G8R8A8UIntNormalized

// HDR 格式

DirectXPixelFormat::R16G16B16A16Float

如果捕获 HDR 内容但指定 SDR 格式,WGC 会在内部进行格式转换,你拿到的帧永远是创建时指定的格式。

D3D11 Device 创建详解

D3D11CreateDevice 函数原型

cpp

复制代码

HRESULT D3D11CreateDevice(

IDXGIAdapter* pAdapter, // 显卡适配器,nullptr = 默认

D3D_DRIVER_TYPE DriverType, // 驱动类型

HMODULE Software, // 软件光栅化 DLL,通常 nullptr

UINT Flags, // 创建标志

const D3D_FEATURE_LEVEL* pFeatureLevels, // Feature Level 列表

UINT FeatureLevels, // Feature Level 数量

UINT SDKVersion, // SDK 版本,固定 D3D11_SDK_VERSION

ID3D11Device** ppDevice, // 输出设备

D3D_FEATURE_LEVEL* pFeatureLevel, // 输出实际 Feature Level

ID3D11DeviceContext** ppImmediateContext // 输出上下文

);

驱动类型

枚举值

说明

D3D_DRIVER_TYPE_HARDWARE

使用 GPU 硬件加速(正常使用)

D3D_DRIVER_TYPE_WARP

CPU 模拟 GPU,无 GPU 时保证可用

D3D_DRIVER_TYPE_REFERENCE

官方参考驱动,调试用,极慢

创建标志

Flag

作用

D3D11_CREATE_DEVICE_BGRA_SUPPORT

支持 BGRA 格式,WGC 必须开启

D3D11_CREATE_DEVICE_VIDEO_SUPPORT

启用视频处理能力(颜色转换、缩放等)

D3D11_CREATE_DEVICE_DEBUG

启用 D3D 调试层

推荐组合:

cpp

复制代码

auto flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT

| D3D11_CREATE_DEVICE_VIDEO_SUPPORT;

示例代码

cpp

复制代码

winrt::com_ptr CreateD3D11Device()

{

winrt::com_ptr device;

UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

// 优先使用硬件加速

HRESULT hr = D3D11CreateDevice(

nullptr,

D3D_DRIVER_TYPE_HARDWARE,

nullptr,

flags,

nullptr, 0,

D3D11_SDK_VERSION,

device.put(),

nullptr, nullptr

);

// 回退到 WARP

if (hr == DXGI_ERROR_UNSUPPORTED)

{

hr = D3D11CreateDevice(

nullptr,

D3D_DRIVER_TYPE_WARP,

nullptr, flags,

nullptr, 0,

D3D11_SDK_VERSION,

device.put(),

nullptr, nullptr

);

}

winrt::check_hresult(hr);

return device;

}

GraphicsCapturePicker

GraphicsCapturePicker 是 Windows 提供的系统 UI 选择器,让用户选择要捕获的窗口或显示器。

类似于文件选择器,但它是窗口/屏幕选择器。

注意事项:

需要 UI 线程和窗口句柄

不适合纯后台库使用

可考虑自行实现窗口枚举和选择 UI

项目依赖配置

NuGet 包(packages.config)

xml

复制代码

必需的链接库

复制代码

windowsapp.lib // WinRT API 支持

d3d11.lib // Direct3D 11

dxgi.lib // DXGI

实际踩坑经验

1. 链接错误:无法解析的外部符号 SetRestrictedErrorInfo

原因: 缺少 WinRT 链接库

解决方案: 在附加依赖库中添加 windowsapp.lib

2. 预编译头注意事项

如果项目使用了预编译头(pch.h),确保:

所有 .cpp 文件在最前面 include pch.h

或者项目设置中正确配置了预编译头选项

3. 窗口大小变化处理

捕获过程中窗口大小改变时:

旧的 FramePool 需要关闭

使用新尺寸重新创建 FramePool

重新创建 CaptureSession

4. 内存管理

每个帧用完后及时释放

使用 RAII 智能指针管理资源

避免在回调中分配大量内存

5. 线程安全

Create() 方式的回调在 UI 线程

CreateFreeThreaded() 回调在工作线程

注意多线程访问共享资源时的同步

参考资料

微软官方文档 - 屏幕捕获

官方示例 - ScreenCaptureforHWND

Win32CaptureSample

刘德华官方身高1.74米,但为什么很多人都说华仔增高鞋不离身?
千秋我为凰