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<ID3D11Device> CreateD3D11Device()
{
winrt::com_ptr<ID3D11Device> 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
<packages>
<!-- C++ WinRT 支持 -->
<package id="Microsoft.Windows.CppWinRT" version="2.0.240405.15" />
<!-- 安全 C++ 工具库 (WIL) -->
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240803.1" />
<!-- Windows SDK -->
<package id="Microsoft.Windows.SDK.CPP" version="10.0.26100.1" />
<package id="Microsoft.Windows.SDK.CPP.x64" version="10.0.26100.1" />
</packages>
必需的链接库
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() 回调在工作线程
- 注意多线程访问共享资源时的同步