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
{
winrt::com_ptr
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米,但为什么很多人都说华仔增高鞋不离身?千秋我为凰