2017-10-09 117 views
1

我正试图编写一个Windows C++程序,它会尝试从当前正在屏幕上显示的任何内容中挑选出感兴趣的颜色。捕获任何Windows应用程序的屏幕?

我已经试过了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(); 
} 
+0

你能举例说明你如何用GDI尝试过吗? 如果我没有记错,似乎你没有通过正确的窗口句柄,桌面是默认的null null – 2oppin

+0

特别是当像DirectX或OpenGL之类的东西被涉及时,他们往往有更直接的路线到视频硬件以获得更好的性能,这就是为什么如果你捕捉到高级别的数据(比如GDI),那么你会得到黑色数据,所以捕获它们通常需要使用自定义的低级视频驱动程序来发送它们的输出。 –

回答

0

有自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; 
} 
+0

尽管此链接可能会回答问题,但最好在此处包含答案的重要部分,并提供供参考的链接。如果链接页面更改,则仅链接答案可能会失效。 - [来自评论](/ review/low-quality-posts/18424137) – waka

+0

正如我在我的问题中所说的,DXGI不适合我捕捉全屏游戏。也许我做错了,但是我想我必须看看你的图书馆。 – HunterZ

+0

啊,我一定错过了。检查AcquireNextFrame上的DXGI_ERROR_ACCESS_LOST结果并重新创建DXGI资源非常重要。每次应用程序进入全屏时,都会发生这种情况。如果您已经这么做了,请记住在重新创建桌面复制界面时检查是否有任何错误。 – sskodje

相关问题