DirectX屏幕捕获-桌面复制API-AcquireNextFrame的帧速率受限 [英] DirectX Screen Capture - Desktop Duplication API - limited frame rate of AcquireNextFrame

查看:1501
本文介绍了DirectX屏幕捕获-桌面复制API-AcquireNextFrame的帧速率受限的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Windows AcquireNextFrame 具有非常高的超时值(999毫秒).这样,我应该尽快从Windows获得每帧新帧,无论如何自然应该是60fps.我最终得到了看起来一切都很好的序列(第6-11帧),然后得到了看起来一切不好的序列(第12-14帧).如果我检查 AccumulatedFrames

I'm trying to use Windows Desktop Duplication API to capture the screen and save the raw output to a video. I'm using AcquireNextFrame with a very high timeout value (999ms). This way I should get every new frame from windows as soon as it at has one, which naturally should be at 60fps anyway. I end up getting sequences where everything looks good (frame 6-11), and then sequences where things look bad (frame 12-14). If I check AccumulatedFrames

lFrameInfo.AccumulatedFrames

该值通常为2或更高.根据我的理解,这意味着Windows会说嘿,别紧张,我还没有适合您的框架",因为对AcquireNextFrame的调用花费了很长时间.但是,一旦Windows最终确实给了我一个框架,它就是在说嘿,您实际上太慢了,最终错过了一个框架".如果我能以某种方式获得这些帧,我想我会得到60hz.

the value is often 2 or higher. From my understanding, this means windows is saying "hey hold up, I don't have a frame for you yet", because calls to AcquireNextFrame take so long. But once windows does finally give me a frame, it is saying "hey you were actually too slow and ended up missing a frame". If i could somehow get these frames I think I would be getting 60hz.

这可以通过记录进一步阐明:

This can be further clarified with logging:

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看起来不错,获取间隔大约为17ms.第12帧的获取时间为(300 + 17 = 317ms).帧12在308开始等待,但是直到336ms才得到任何东西. Windows直到(300 + 17 + 17〜= 336ms)之后才对我没有任何帮助.好的,可以确定Windows只是错过了一个帧,但是当我最终得到它时,我可以检查AccumulatedFrames,并且它的值为2(这意味着我错过了一个帧,因为在调用AcquireNextFrame之前等待了太长时间).以我的理解,仅当AcquireNextFrame立即返回时,AccumulatedFrames大于1才有意义.

Frame 6-11 look good, the acquires are roughly 17ms apart. Frame 12 should be acquired at (300+17=317ms). Frame 12 starts waiting at 308, but doesn't get anything until 336ms. Windows didn't have anything for me until the frame after (300+17+17~=336ms). Okay sure maybe windows just missed a frame, but when I finally get it, I can check AccumulatedFrames and its value was 2 (meaning I missed a frame because I waited too long before calling AcquireNextFrame). In my understanding, it only makes sense for AccumulatedFrames to be larger than 1 if AcquireNextFrame returns immediately.

此外,我可以在运行捕获软件时使用PresentMon.日志显示每帧MsBetweenDisplayChange,在16.666ms处相当稳定(有几个异常值,但比我的捕获软件所看到的要少得多).

Furthermore, I can use PresentMon while my capture software is running. The logs show MsBetweenDisplayChange for every frame, which is fairly steady at 16.666ms (with a couple outliers, but much less than my capture software is seeing).

这些人( 1

These people (1, 2) seem to have been able to get 60fps, so I'm wondering what I am doing incorrectly.

我的代码基于这个:

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;
}

**编辑-根据我的测量结果,您必须先调用AcquireNextFrame(),该帧才会实际出现约10毫秒,否则Windows将无法获取该帧并让您获得下一个帧.每当我的录制程序要花7毫秒以上的时间来回绕(获取帧i之后,直到在i + 1上调用AcquireNextFrame()),都会丢失帧i + 1.

**EDIT - According to my measurements, you must call AcquireNextFrame() before the frame will actually appear by about ~10ms, or windows will fail to acquire it and get you the next one. Every time my recording program takes more than 7 ms to wrap around (after acquiring frame i until calling AcquireNextFrame() on i+1), frame i+1 is missed.

***编辑-此处屏幕截图GPU View显示我在说什么.前6帧立即处理,然后第7帧耗时119ms. "capture_to_argb.exe"旁边的长矩形对应于我卡在AcquireNextFrame()中.如果您查看硬件队列,即使我被困在AcquireNextFrame()中,也可以看到它以60fps的速度清晰地呈现.至少这是我的解释(我不知道自己在做什么).

***EDIT - Heres a screenshot of GPU View showing what I'm talking about. The first 6 frames process in no time, then the 7th frame takes 119ms. The long rectangle beside "capture_to_argb.exe" corresponds to me being stuck inside AcquireNextFrame(). If you look up to the hardware queue, you can see it cleanly rendering at 60fps, even while I'm stuck in AcquireNextFrame(). At least this is my interpretation (I have no idea what I'm doing).

推荐答案

当前显示模式:3840 x 2160(32位)(60hz)"是指显示刷新率,即每秒钟可以传递多少帧来显示第二.但是,渲染新帧的速率通常要低得多.您可以使用 PresentMon 或类似的实用程序来检查此费率.当我不移动鼠标时,它会报告如下内容:

"Current Display Mode: 3840 x 2160 (32 bit) (60hz)" refers to display refresh rate, that is how many frames can be passed to display per second. However the rate at which new frames are rendered is typically much lower. You can inspect this rate using PresentMon or similar utilities. When I don't move the mouse it reports me something like this:

如您所见,什么都没发生,Windows每秒仅显示两次新帧,甚至更慢.但是,这通常非常适合视频编码,因为即使您以60 fps的速度录制视频,并且AcquireNextFrame报告没有可用的新帧,这也意味着当前帧与以前的帧完全相同.

As you can see when nothing happens Windows presents new frame only twice per second or even slower. However this is typically really good for video encoding because even if you are recording video at 60 fps and AcquireNextFrame reports that no new frame is available then it means that current frame is exactly the same as previous.

这篇关于DirectX屏幕捕获-桌面复制API-AcquireNextFrame的帧速率受限的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆