【问题标题】:Capture screen of any Windows app?捕获任何 Windows 应用程序的屏幕?
【发布时间】:2017-10-09 05:27:43
【问题描述】:

我正在尝试编写一个 Windows C++ 程序,该程序将尝试从屏幕上当前显示的内容中挑选出感兴趣的颜色。

我尝试了以下 GDI、Direct3D9 和 Direct3D11 DXGI 示例,它们所有似乎都用于捕获 Windows 桌面和/或我自己的应用程序的自己的输出。当我启动全屏 Direct3D 游戏时,我似乎最终会得到一些空白像素数据。

必须可以做到这一点,否则 OBS Studio、FRAPS 等将无法像它们那样透明地工作。

我知道我可以尝试对 OBS Studio 进行逆向工程,但有没有人有更简洁的 C++ 解决方案来捕获任意 Windows 应用程序的视频输出作为某种像素缓冲区?

编辑:我还应该提到,捕获常规桌面窗口似乎有效。全屏游戏给我带来了麻烦。

编辑:评论者请求了我的 GDI 代码。这是我的 GDI 和 D3D9 代码。如您所见,我根据发现的相互冲突的示例尝试了一些变体:

std::wstring GetScreenColor(COLORREF& colorRef)
{
    std::wstring retVal;

    //const int desktopWidth(GetDeviceCaps(desktopHdc, HORZRES));
    //const int desktopHeight(GetDeviceCaps(desktopHdc, VERTRES));
//        const int desktopWidth(GetSystemMetrics(SM_CXVIRTUALSCREEN));
//        const int desktopHeight(GetSystemMetrics(SM_CYVIRTUALSCREEN));
    const int desktopWidth(GetSystemMetrics(SM_CXSCREEN));
    const int desktopHeight(GetSystemMetrics(SM_CYSCREEN));
    HWND desktopHwnd(GetDesktopWindow());
//        HDC desktopHdc(GetDC(NULL));
    HDC desktopHdc(GetDC(desktopHwnd));
    HDC myHdc(CreateCompatibleDC(desktopHdc));
    const HBITMAP desktopBitmap(CreateCompatibleBitmap(desktopHdc, desktopWidth, desktopHeight));
    SelectObject(myHdc, desktopBitmap);
//        BitBlt(myHdc, 0, 0, desktopWidth, desktopHeight, desktopHdc, 0, 0, SRCCOPY);
    BitBlt(myHdc, 0, 0, desktopWidth, desktopHeight, desktopHdc, 0, 0, SRCCOPY | CAPTUREBLT);
    //SelectObject(myHdc, hOld);
    ReleaseDC(NULL, desktopHdc);

    BITMAPINFO bitmapInfo = { 0 };
    bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
    bitmapInfo.bmiHeader.biWidth = desktopWidth;
    bitmapInfo.bmiHeader.biHeight = -desktopHeight;
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitmapInfo.bmiHeader.biBitCount = 24;
    bitmapInfo.bmiHeader.biCompression = BI_RGB;
    bitmapInfo.bmiHeader.biSizeImage = 0;

    // TODO: use a persistent buffer?
    const unsigned long numPixels(desktopHeight * desktopWidth);
    ColorBGRS* rawPixels(new ColorBGRS[numPixels]);
    if (!GetDIBits(myHdc, desktopBitmap, 0, desktopHeight, rawPixels, &bitmapInfo, DIB_RGB_COLORS))
    {
        delete[] rawPixels;
        ReleaseDC(desktopHwnd, desktopHdc);
        DeleteDC(myHdc);
        DeleteObject(desktopBitmap);
        return L"GetDIBits() failed";
    }

    unsigned long redSum(0);
    unsigned long greenSum(0);
    unsigned long blueSum(0);

    for (unsigned long index(0); index < numPixels; ++index)
    {
        blueSum  += rawPixels[index].blue;
        greenSum += rawPixels[index].green;
        redSum   += rawPixels[index].red;
    }

    const unsigned long redAverage(redSum / numPixels);
    const unsigned long blueAverage(blueSum / numPixels);
    const unsigned long greenAverage(greenSum / numPixels);

    colorRef = RGB(redAverage, greenAverage, blueAverage);

    delete[] rawPixels;
    ReleaseDC(desktopHwnd, desktopHdc);
    DeleteDC(myHdc);
    DeleteObject(desktopBitmap);

    return std::wstring();
}

std::wstring GetScreenColor2(COLORREF& colorRef)
{
    IDirect3D9* d3d9(Direct3DCreate9(D3D_SDK_VERSION));
    if (!d3d9)
    {
        d3d9->Release();
        return L"Direct3DCreate9() failed";
    }

    D3DDISPLAYMODE d3dDisplayMode;
    if (FAILED(d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3dDisplayMode)))
    {
        return L"GetAdapterDisplayMode() failed";
    }

    D3DPRESENT_PARAMETERS d3dPresentParams;
    ZeroMemory(&d3dPresentParams, sizeof(D3DPRESENT_PARAMETERS));        
    d3dPresentParams.Windowed = TRUE;
    d3dPresentParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dPresentParams.BackBufferFormat = d3dDisplayMode.Format;
    d3dPresentParams.BackBufferCount = 1;
    d3dPresentParams.BackBufferHeight = d3dDisplayMode.Height;
    d3dPresentParams.BackBufferWidth = d3dDisplayMode.Width;
    d3dPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE;
    d3dPresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
    //d3dPresentParams.SwapEffect = D3DSWAPEFFECT_COPY;
    d3dPresentParams.hDeviceWindow = NULL; //hWnd;
    d3dPresentParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
    d3dPresentParams.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;

    IDirect3DDevice9* d3d9Device(0);
    if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dPresentParams.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dPresentParams, &d3d9Device)))
    {
        d3d9->Release();
        return L"CreateDevice() failed";
    }

    IDirect3DSurface9* d3d9Surface(0);
//        if (FAILED(d3d9Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &d3d9Surface))) return L"GetBackBuffer() failed";
    if (FAILED(d3d9Device->CreateOffscreenPlainSurface(d3dDisplayMode.Width, d3dDisplayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &d3d9Surface, NULL)))
//        if (FAILED(d3d9Device->CreateOffscreenPlainSurface(d3dDisplayMode.Width, d3dDisplayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &d3d9Surface, NULL)))
    {
        d3d9Device->Release();
        d3d9->Release();
        return L"CreateOffscreenPlainSurface() failed";
    }

    if (FAILED(d3d9Device->GetFrontBufferData(0, d3d9Surface)))
    {
        d3d9Surface->Release();
        d3d9Device->Release();
        d3d9->Release();
        return L"GetFrontBufferData() failed";
    }

    D3DLOCKED_RECT d3dLockedRect;
    if (FAILED(d3d9Surface->LockRect(&d3dLockedRect, 0, D3DLOCK_NO_DIRTY_UPDATE |
                                                        D3DLOCK_NOSYSLOCK |
                                                        D3DLOCK_READONLY)))
    {
        d3d9Surface->UnlockRect();
        d3d9Surface->Release();
        d3d9Device->Release();
        d3d9->Release();
        return L"LockRect() failed";
    }

    const unsigned long numPixels(d3dDisplayMode.Height * d3dDisplayMode.Width);
    BYTE* rawPixels((BYTE*)(d3dLockedRect.pBits));
    colorRef = RGB(*(rawPixels + 2), *(rawPixels + 1), *(rawPixels));

    d3d9Surface->UnlockRect();
    d3d9Surface->Release();
    d3d9Device->Release();
    d3d9->Release();

    return std::wstring();
}

【问题讨论】:

  • 您能否举例说明您是如何使用 GDI 进行尝试的?如果我没记错的话,好像你没有传递正确的窗口句柄,桌面是默认的~ null
  • 尤其是当涉及到诸如 DirectX 或 OpenGL 之类的东西时,它们往往有更直接的路由到视频硬件以获得更好的性能,这就是为什么在高级别捕获时会得到黑色数据的原因(像 GDI),因此捕获它们通常需要使用自定义的低级视频驱动程序,以便它们将输出发送到。

标签: c++ windows capture direct3d screen-capture


【解决方案1】:

从 Windows 8 开始,Desktop Duplication API 可以录制游戏等全屏应用程序。我最近为我的一个项目制作了this library,您也许可以用作参考。只有在您的情况下,您才需要从纹理中获取原始像素数据,而不是将它们写入视频或图像。

编辑:添加了一个在失去访问权时重新初始化的小例子。

{
    CComPtr<ID3D11Device> pDevice;
    CComPtr<IDXGIOutputDuplication> pDeskDupl;
    //(...)create devices and duplication interface etc here..
    InitializeDesktopDupl(pDevice, &pDeskDupl, &OutputDuplDesc);
    while(true) //capture loop gist.
    {
    IDXGIResource *pDesktopResource = nullptr;
    DXGI_OUTDUPL_FRAME_INFO FrameInfo;
    RtlZeroMemory(&FrameInfo, sizeof(FrameInfo));
    // Get new frame
    HRESULT hr = pDeskDupl->AcquireNextFrame(
                99,//timeout in ms
                &FrameInfo,
                &pDesktopResource);

            if (hr == DXGI_ERROR_ACCESS_LOST) {
                pDeskDupl->ReleaseFrame();
                pDeskDupl.Release();
                pDesktopResource->Release();

                hr = InitializeDesktopDupl(pDevice, &pDeskDupl, &OutputDuplDesc);
                if(FAILED(hr)){
                 //Check if everything is OK before continuing
                }
            }
     }
}
HRESULT InitializeDesktopDupl(ID3D11Device *pDevice, IDXGIOutputDuplication **ppDesktopDupl, DXGI_OUTDUPL_DESC *pOutputDuplDesc)
{
    *ppDesktopDupl = NULL;
    *pOutputDuplDesc;

    // Get DXGI device
    CComPtr<IDXGIDevice> pDxgiDevice;
    CComPtr<IDXGIOutputDuplication> pDeskDupl = NULL;
    DXGI_OUTDUPL_DESC OutputDuplDesc;

    HRESULT hr = pDevice->QueryInterface(IID_PPV_ARGS(&pDxgiDevice));

    if (FAILED(hr)) { return hr; }
    // Get DXGI adapter
    CComPtr<IDXGIAdapter> pDxgiAdapter;
    hr = pDxgiDevice->GetParent(
        __uuidof(IDXGIAdapter),
        reinterpret_cast<void**>(&pDxgiAdapter));
    pDxgiDevice.Release();
    if (FAILED(hr)) { return hr; }
    // Get output
    CComPtr<IDXGIOutput> pDxgiOutput;
    hr = pDxgiAdapter->EnumOutputs(
        m_DisplayOutput,
        &pDxgiOutput);
    if (FAILED(hr)) { return hr; }
    pDxgiAdapter.Release();

    CComPtr<IDXGIOutput1> pDxgiOutput1;

    hr = pDxgiOutput->QueryInterface(IID_PPV_ARGS(&pDxgiOutput1));
    if (FAILED(hr)) { return hr; }
    pDxgiOutput.Release();

    // Create desktop duplication
    hr = pDxgiOutput1->DuplicateOutput(
        pDevice,
        &pDeskDupl);
    if (FAILED(hr)) { return hr; }
    pDxgiOutput1.Release();

    // Create GUI drawing texture
    pDeskDupl->GetDesc(&OutputDuplDesc);

    pDxgiOutput1.Release();

    *ppDesktopDupl = pDeskDupl;
    (*ppDesktopDupl)->AddRef();
    *pOutputDuplDesc = OutputDuplDesc;
    return hr;
}

【讨论】:

  • 正如我在问题中所说,DXGI 不适用于捕获全屏游戏。也许我做错了什么……我想我得看看你的图书馆。
  • 啊,我一定错过了。检查 AcquireNextFrame 上的 DXGI_ERROR_ACCESS_LOST 结果并重新创建 DXGI 资源非常重要。每次应用程序全屏显示时都会发生这种情况。如果您已经这样做了,请记得在重新创建桌面复制界面时检查是否有任何错误。
猜你喜欢
  • 2013-03-30
  • 2018-11-03
  • 1970-01-01
  • 1970-01-01
  • 2019-01-14
  • 2019-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
相关资源
最近更新 更多