如何使用IDXGIOutputDuplication实现多显示器屏幕捕获
基于IDXGIOutputDuplication实现多显示器屏幕捕获方案
完全可以不依赖GDI,仅通过Windows桌面复制API实现多显示器配置下的屏幕捕获,这也是目前Windows平台性能表现最好的屏幕捕获方案,相比传统GDI捕获CPU占用更低、延迟更小。
你当前单屏实现的核心逻辑是正确的——IDXGIOutputDuplication 本身设计上就是和单个适配器下的单个输出端(即单个显示器)强绑定的,不存在可以直接覆盖整个跨显示器虚拟桌面的Duplication实例,这是API层面的固有设计。
你提到的多线程方案的问题
你最初考虑的「为每个显示器创建独立线程、跑单独的API调用逻辑」是可运行的,但不是最优解,存在几个明显缺陷:
- 多线程频繁上下文切换会带来不必要的性能开销
- 若每个显示器都单独创建D3D设备,会造成GPU显存和驱动资源的浪费
- 多线程独立取帧很难对齐不同显示器的帧时间戳,拼接出来的全屏画面容易出现不同步的问题
更优雅、性能更优的实现路径
初始化阶段优化
同一个GPU适配器下的所有显示器,完全可以共用同一个ID3D11Device实例,只有当显示器挂在不同物理GPU(比如笔记本核显接主屏、独显接副屏的混合输出场景)时,才需要为对应适配器单独创建D3D设备,这一步可以减少一半以上的资源占用。
具体初始化流程:
- 枚举系统内所有
IDXGIAdapter,对每个适配器尝试创建ID3D11Device,创建成功的适配器保留,跳过重复设备创建 - 对每个保留的适配器,枚举其下挂的所有
IDXGIOutput,查询IDXGIOutput1接口后为每个输出创建对应的IDXGIOutputDuplication实例 - 将每个Duplication实例、对应的
DXGI_OUTPUT_DESC(存储显示器分辨率、虚拟桌面偏移坐标、旋转角度等信息)、预创建的Staging纹理、帧等待事件句柄存在统一的结构体中管理
注意不要像你当前代码一样每次捕获都新建Staging纹理,初始化阶段根据每个输出端的纹理格式提前创建好可复用的Staging纹理,能省掉大量GPU资源创建销毁的开销,捕获性能可提升20%以上。
捕获阶段优化
不需要为每个Duplication实例开独立线程,用单捕获线程配合事件等待即可实现高效的多屏帧获取:
- 对每个Duplication实例,调用
GetFrameLatencyWaitableObject拿到对应的帧可用等待事件句柄 - 把所有事件句柄传入
WaitForMultipleObjects,阻塞等待任意显示器有新帧到达,避免空转占满CPU - 被触发的事件对应显示器有新帧,调用
AcquireNextFrame拿到帧纹理,拷贝到预创建的Staging纹理后做后续处理 - 处理完帧数据后必须调用
ReleaseFrame释放帧锁,否则后续取帧会失败(你当前贴的代码漏了这一步) - 取帧时可以通过
DXGI_OUTDUPL_FRAME_INFO里的LastPresentTime做时间戳对齐,保证多屏画面的时间一致性
常见踩坑点
AcquireNextFrame的超时参数不要设为0,配合事件等待时设置100ms左右的超时即可,避免无效空转- 遇到
DXGI_ERROR_ACCESS_LOST错误一般是显示器热插拔、分辨率调整、显示模式切换导致的,只需要释放对应失效的Duplication实例,重新枚举输出创建新实例即可,不需要重启整个捕获流程 - 系统全局最多允许同时运行8个使用Desktop Duplication API的应用,自己的进程内不要对同一个输出端重复创建Duplication实例,否则会触发
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE错误 - 跨GPU适配器的显示器纹理不能直接拷贝,需要先拷贝到对应适配器的Staging纹理做CPU侧映射,再拷贝到目标设备,这类场景占比不高,单独做分支处理即可
内容的提问来源于stack exchange,提问作者bluetooth12
相关产品推荐
相关产品推荐

