如何通过Windows桌面复制API限制捕获帧率至60fps以内
针对Windows Desktop Duplication API的帧率限制方案
正确利用AcquireNextFrame()的超时参数
AcquireNextFrame()的wait参数不是直接设置固定帧间隔,而是等待新帧可用的最长超时时间。如果硬设为16ms(对应60fps理论间隔),当主显示器刷新率更高(比如200Hz)时,桌面合成器会更频繁生成帧,API会在新帧出现时立刻返回,导致捕获帧率跟着主显示器走。
要稳定限制在60fps,得结合时间戳动态计算等待时长,既保证间隔符合要求,又不会错过新帧:
- 用
QueryPerformanceCounter或GetTickCount64记录每次成功捕获帧的时间戳 - 计算60fps对应的目标间隔:
1000/60 ≈16.666ms - 每次捕获前,计算当前时间与上次捕获时间的差值,若差值小于目标间隔,把
wait参数设为剩余时间;若已超过,设为0立即尝试捕获
示例伪代码:
LARGE_INTEGER freq, lastCaptureTime, currentTime; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&lastCaptureTime); while (true) { // 计算当前到下次目标捕获的剩余时间 QueryPerformanceCounter(¤tTime); double elapsedMs = (currentTime.QuadPart - lastCaptureTime.QuadPart) * 1000.0 / freq.QuadPart; DWORD waitMs = max(0, (DWORD)(16.666 - elapsedMs)); // 尝试捕获帧 HRESULT hr = duplication->AcquireNextFrame(waitMs, &frameInfo, &desktopResource); if (hr == DXGI_ERROR_WAIT_TIMEOUT) { // 超时无新帧,直接进入下一轮循环 continue; } else if (SUCCEEDED(hr)) { // 处理捕获到的帧 ProcessCapturedFrame(desktopResource); // 释放资源 duplication->ReleaseFrame(); // 更新捕获时间戳 QueryPerformanceCounter(&lastCaptureTime); } }
解决多显示器帧率异常问题
外接60Hz显示器捕获帧率跑到200fps,是因为Desktop Duplication API的帧更新和桌面合成器同步——主显示器高刷新率会让合成器频繁生成帧,哪怕外接显示器画面没变化,API也会触发捕获。
额外优化方案:
- 检查
frameInfo.LastPresentTime,如果和上一帧的时间戳一致(说明画面无更新),直接释放帧不处理,减少无效捕获 - 若需求是匹配显示器原生刷新率,可通过
IDXGIOutput::GetDisplayModeList获取外接显示器的实际刷新率,用该值计算目标间隔,而非固定60fps
为什么直接睡眠会丢帧
在AcquireNextFrame()之间加Sleep(16)会丢帧,原因有二:一是Windows系统睡眠精度有限(默认约10-15ms),实际间隔会偏离目标;二是睡眠期间如果有新帧生成,API不会主动通知,等睡眠结束再调用时,已经错过了多帧更新,导致画面延迟或丢帧。而动态调整wait参数的方式,是让API在等待期间主动监听新帧,有新帧就立刻返回,同时保证两次有效捕获的间隔不低于目标帧率要求。
内容的提问来源于stack exchange,提问作者unstuckkk
相关产品推荐
相关产品推荐

