我正在尝试使用Windows Desktop Duplication API捕获屏幕并将原始输出保存到视频中。我正在使用具有非常高超时值(999毫秒)的AcquireNextFrame。这样我就可以从窗口获得每个新帧,只要它有一个,无论如何自然应该是60fps。我最终获得了一切看起来都很好的序列(第6-11帧),然后是事物看起来很糟糕的序列(第12-14帧)。如果我查看AccumulatedFrames
lFrameInfo.AccumulatedFrames
值通常为2或更高。根据我的理解,这意味着Windows会说“嘿嘿,我还没有框架”,因为调用AcquireNextFrame花了这么长时间。但是,一旦Windows终于给了我一个框架,它就是说“嘿,你实际上太慢了,最后错过了一个框架”。如果我能以某种方式获得这些帧,我想我会得到60hz。
这可以通过记录进一步澄清:
I0608 10:40:16.964375 4196 window_capturer_dd.cc:438] 206 - Frame 6 start acquire
I0608 10:40:16.973867 4196 window_capturer_dd.cc:451] 216 - Frame 6 acquired
I0608 10:40:16.981364 4196 window_capturer_dd.cc:438] 223 - Frame 7 start acquire
I0608 10:40:16.990864 4196 window_capturer_dd.cc:451] 233 - Frame 7 acquired
I0608 10:40:16.998364 4196 window_capturer_dd.cc:438] 240 - Frame 8 start acquire
I0608 10:40:17.007876 4196 window_capturer_dd.cc:451] 250 - Frame 8 acquired
I0608 10:40:17.015393 4196 window_capturer_dd.cc:438] 257 - Frame 9 start acquire
I0608 10:40:17.023905 4196 window_capturer_dd.cc:451] 266 - Frame 9 acquired
I0608 10:40:17.032411 4196 window_capturer_dd.cc:438] 274 - Frame 10 start acquire
I0608 10:40:17.039912 4196 window_capturer_dd.cc:451] 282 - Frame 10 acquired
I0608 10:40:17.048925 4196 window_capturer_dd.cc:438] 291 - Frame 11 start acquire
I0608 10:40:17.058428 4196 window_capturer_dd.cc:451] 300 - Frame 11 acquired
I0608 10:40:17.065943 4196 window_capturer_dd.cc:438] 308 - Frame 12 start acquire
I0608 10:40:17.096945 4196 window_capturer_dd.cc:451] 336 - Frame 12 acquired
I0608 10:40:17.098947 4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 12
I0608 10:40:17.101444 4196 window_capturer_dd.cc:438] 343 - Frame 13 start acquire
I0608 10:40:17.128958 4196 window_capturer_dd.cc:451] 368 - Frame 13 acquired
I0608 10:40:17.130957 4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 13
I0608 10:40:17.135459 4196 window_capturer_dd.cc:438] 377 - Frame 14 start acquire
I0608 10:40:17.160959 4196 window_capturer_dd.cc:451] 399 - Frame 14 acquired
I0608 10:40:17.162958 4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 14
第6-11帧看起来不错,获得的距离大约相差17毫秒。帧12应在(300 + 17 = 317ms)处获得。第12帧开始在308等待,但直到336ms才得到任何东西。 Windows之前的帧没有任何东西(300 + 17 + 17~ = 336ms)。好吧肯定也许窗户错过了一个框架,但是当我最终得到它时,我可以检查AccumulatedFrames并且它的值是2(意思是我错过了一个框架,因为我在调用AcquireNextFrame之前等了太久)。根据我的理解,如果AcquireNextFrame立即返回,则AccumulatedFrames大于1才有意义。
此外,我可以在我的捕获软件运行时使用PresentMon。日志显示每帧的MsBetweenDisplayChange,相当稳定在16.666ms(有几个异常值,但比我的捕获软件看到的要少得多)。
这些人(1,2)似乎能够获得60fps,所以我想知道我做错了什么。
我的代码基于this:
int main() {
int FPS = 60;
int video_length_sec = 5;
int total_frames = FPS * video_length_sec;
for (int i = 0; i < total_frames; i++) {
if(!CaptureSingleFrame()){
i--;
}
}
}
ComPtr<ID3D11Device> lDevice;
ComPtr<ID3D11DeviceContext> lImmediateContext;
ComPtr<IDXGIOutputDuplication> lDeskDupl;
ComPtr<ID3D11Texture2D> lAcquiredDesktopImage;
ComPtr<ID3D11Texture2D> lGDIImage;
ComPtr<ID3D11Texture2D> lDestImage;
DXGI_OUTPUT_DESC lOutputDesc;
DXGI_OUTDUPL_DESC lOutputDuplDesc;
D3D11_TEXTURE2D_DESC desc;
// Driver types supported
D3D_DRIVER_TYPE gDriverTypes[] = {
D3D_DRIVER_TYPE_HARDWARE
};
UINT gNumDriverTypes = ARRAYSIZE(gDriverTypes);
// Feature levels supported
D3D_FEATURE_LEVEL gFeatureLevels[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_1
};
UINT gNumFeatureLevels = ARRAYSIZE(gFeatureLevels);
bool Init() {
int lresult(-1);
D3D_FEATURE_LEVEL lFeatureLevel;
HRESULT hr(E_FAIL);
// Create device
for (UINT DriverTypeIndex = 0; DriverTypeIndex < gNumDriverTypes; ++DriverTypeIndex)
{
hr = D3D11CreateDevice(
nullptr,
gDriverTypes[DriverTypeIndex],
nullptr,
0,
gFeatureLevels,
gNumFeatureLevels,
D3D11_SDK_VERSION,
&lDevice,
&lFeatureLevel,
&lImmediateContext);
if (SUCCEEDED(hr))
{
// Device creation success, no need to loop anymore
break;
}
lDevice.Reset();
lImmediateContext.Reset();
}
if (FAILED(hr))
return false;
if (lDevice == nullptr)
return false;
// Get DXGI device
ComPtr<IDXGIDevice> lDxgiDevice;
hr = lDevice.As(&lDxgiDevice);
if (FAILED(hr))
return false;
// Get DXGI adapter
ComPtr<IDXGIAdapter> lDxgiAdapter;
hr = lDxgiDevice->GetParent(
__uuidof(IDXGIAdapter), &lDxgiAdapter);
if (FAILED(hr))
return false;
lDxgiDevice.Reset();
UINT Output = 0;
// Get output
ComPtr<IDXGIOutput> lDxgiOutput;
hr = lDxgiAdapter->EnumOutputs(
Output,
&lDxgiOutput);
if (FAILED(hr))
return false;
lDxgiAdapter.Reset();
hr = lDxgiOutput->GetDesc(
&lOutputDesc);
if (FAILED(hr))
return false;
// QI for Output 1
ComPtr<IDXGIOutput1> lDxgiOutput1;
hr = lDxgiOutput.As(&lDxgiOutput1);
if (FAILED(hr))
return false;
lDxgiOutput.Reset();
// Create desktop duplication
hr = lDxgiOutput1->DuplicateOutput(
lDevice.Get(), //TODO what im i doing here
&lDeskDupl);
if (FAILED(hr))
return false;
lDxgiOutput1.Reset();
// Create GUI drawing texture
lDeskDupl->GetDesc(&lOutputDuplDesc);
desc.Width = lOutputDuplDesc.ModeDesc.Width;
desc.Height = lOutputDuplDesc.ModeDesc.Height;
desc.Format = lOutputDuplDesc.ModeDesc.Format;
desc.ArraySize = 1;
desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET;
desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.MipLevels = 1;
desc.CPUAccessFlags = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
hr = lDevice->CreateTexture2D(&desc, NULL, &lGDIImage);
if (FAILED(hr))
return false;
if (lGDIImage == nullptr)
return false;
// Create CPU access texture
desc.Width = lOutputDuplDesc.ModeDesc.Width;
desc.Height = lOutputDuplDesc.ModeDesc.Height;
desc.Format = lOutputDuplDesc.ModeDesc.Format;
std::cout << desc.Width << "x" << desc.Height << "\n\n\n";
desc.ArraySize = 1;
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.MipLevels = 1;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
desc.Usage = D3D11_USAGE_STAGING;
return true;
}
void WriteFrameToCaptureFile(ID3D11Texture2D* texture) {
D3D11_MAPPED_SUBRESOURCE* pRes = new D3D11_MAPPED_SUBRESOURCE;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
lImmediateContext->Map(texture, subresource, D3D11_MAP_READ_WRITE, 0, pRes);
void* d = pRes->pData;
char* data = reinterpret_cast<char*>(d);
// writes data to file
WriteFrameToCaptureFile(data, 0);
}
bool CaptureSingleFrame()
{
HRESULT hr(E_FAIL);
ComPtr<IDXGIResource> lDesktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO lFrameInfo;
ID3D11Texture2D* currTexture;
hr = lDeskDupl->AcquireNextFrame(
999,
&lFrameInfo,
&lDesktopResource);
if (FAILED(hr)) {
LOG(INFO) << "Failed to acquire new frame";
return false;
}
if (lFrameInfo.LastPresentTime.HighPart == 0) {
// not interested in just mouse updates, which can happen much faster than 60fps if you really shake the mouse
hr = lDeskDupl->ReleaseFrame();
return false;
}
int accum_frames = lFrameInfo.AccumulatedFrames;
if (accum_frames > 1 && current_frame != 1) {
// TOO MANY OF THESE is the problem
// especially after having to wait >17ms in AcquireNextFrame()
}
// QI for ID3D11Texture2D
hr = lDesktopResource.As(&lAcquiredDesktopImage);
// Copy image into a newly created CPU access texture
hr = lDevice->CreateTexture2D(&desc, NULL, &currTexture);
if (FAILED(hr))
return false;
if (currTexture == nullptr)
return false;
lImmediateContext->CopyResource(currTexture, lAcquiredDesktopImage.Get());
writer_thread->Schedule(
FROM_HERE, [this, currTexture]() {
WriteFrameToCaptureFile(currTexture);
});
pending_write_counts_++;
hr = lDeskDupl->ReleaseFrame();
return true;
}
**编辑 - 根据my measurements,您必须在帧实际出现约10ms之前调用AcquireNextFrame(),否则Windows将无法获取它并让您获得下一个。每次我的录制程序需要超过7毫秒来换行(在获取帧i之后直到在i + 1上调用AcquireNextFrame()),帧i + 1将被丢失。
***编辑 - Heres GPU视图的屏幕截图显示了我在说什么。前6帧立即处理,然后第7帧需要119ms。 “capture_to_argb.exe”旁边的长矩形对应于我被困在AcquireNextFrame()中。如果你查看硬件队列,你可以看到它以60fps干净地渲染,即使我陷入了AcquireNextFrame()。至少这是我的解释(我不知道我在做什么)。
答案 0 :(得分:2)
&#34;当前显示模式:3840 x 2160(32位)(60hz)&#34;是指显示刷新率,即每秒可以传递多少帧。但是,渲染新帧的速率通常要低得多。您可以使用PresentMon或类似实用程序检查此费率。当我不移动鼠标时,它会报告我这样的事情:
正如您所看到的,当没有任何反应时,Windows每秒只会呈现两次新帧,甚至更慢。然而,这通常非常适合视频编码,因为即使您以60 fps录制视频并且AcquireNextFrame
报告没有新帧可用,则表示当前帧与之前的帧完全相同。
答案 1 :(得分:1)
在AcquireNextFrame
的下一次调用之前执行阻止等待您缺少实际帧。桌面复制API逻辑建议您尝试立即获取下一帧,如果您期望一个合适的帧速率。您的休眠呼叫有效地放弃了可用的剩余执行超时,而没有在计划的时间间隔内获得新切片的硬性承诺。
您必须以最大帧速率进行轮询。不要睡觉(即使睡眠时间为零)并立即请求下一帧。您可以选择删除过早出现的帧。桌面复制API的设计方式是,获取额外的帧可能不会太昂贵,无法及早识别它们并停止处理。
如果您仍然希望在帧之间休眠,则可能需要阅读accuracy remark:
要提高睡眠间隔的准确性,请调用
timeGetDevCaps
函数以确定支持的最小计时器分辨率,并调用timeBeginPeriod函数将计时器分辨率设置为最小值。调用timeBeginPeriod时请小心,因为频繁的调用会显着影响系统时钟,系统电源使用和调度程序。如果你调用timeBeginPeriod,请在应用程序的早期调用它一次,并确保在应用程序的最后调用timeEndPeriod函数。
答案 2 :(得分:0)
正如其他人所提到的,60Hz刷新率仅表示显示可能更改的频率。它实际上并不意味着将频繁地改变它。 AcquireNextFrame仅在重复输出上显示的内容发生更改时才返回帧。
我的建议是......
这将以所需的速率产生一系列帧。如果显示屏没有更改,您将拥有上一帧的副本以用于维持帧速率。