使用 DirectX 捕获屏幕

2021-12-17 00:00:00 winapi c++ directx

我知道如何使用 GDI 来捕获屏幕,但是它非常慢(它几乎不能捕获 10 fps)

I know how to use GDI to capture screen, however it is very slow (it barely captures 10 fps)

我了解到 DirectX 提供了最佳速度.但在我开始学习 DirectX 之前,我想测试一个示例,看看它是否真的那么快.

I have read that DirectX offers the best speed. But before I start learning DirectX I wanted to test a sample to see if it is really that fast.

我发现这个问题提供了一个示例代码来做到这一点:

I have found this question that offers a sample code to do that:

void dump_buffer()
{
   IDirect3DSurface9* pRenderTarget=NULL;
   IDirect3DSurface9* pDestTarget=NULL;
     const char file[] = "Pickture.bmp";
   // sanity checks.
   if (Device == NULL)
      return;

   // get the render target surface.
   HRESULT hr = Device->GetRenderTarget(0, &pRenderTarget);
   // get the current adapter display mode.
   //hr = pDirect3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddisplaymode);

   // create a destination surface.
   hr = Device->CreateOffscreenPlainSurface(DisplayMde.Width,
                         DisplayMde.Height,
                         DisplayMde.Format,
                         D3DPOOL_SYSTEMMEM,
                         &pDestTarget,
                         NULL);
   //copy the render target to the destination surface.
   hr = Device->GetRenderTargetData(pRenderTarget, pDestTarget);
   //save its contents to a bitmap file.
   hr = D3DXSaveSurfaceToFile(file,
                              D3DXIFF_BMP,
                              pDestTarget,
                              NULL,
                              NULL);

   // clean up.
   pRenderTarget->Release();
   pDestTarget->Release();
}

我已尝试包含所需的文件.但是,并非所有这些都可以包含在内(例如 #include ).

I have tried to include the required files. However not all of them can be included (for example #include <D3dx9tex.h>).

任何人都可以提供一个工作示例,其中包含所有必需的包含或指向我应该安装哪些库.

Can anyone provide a working example that has all of the required includes or point me to what libraries I should install.

我在 Windows 7 Ultimate (x64) 上使用 Visual C++ 2010 Express.

I am using Visual C++ 2010 Express on Windows 7 Ultimate (x64).

另外,这段代码并不完整,比如Device标识符是什么?!

Also, this code is not complete, for example what is the Device identifier?!

推荐答案

以下是一些使用 DirectX 9 捕获屏幕的示例代码.您不必安装任何 SDK(除了 Visual Studio 附带的标准文件,尽管我没有测试 VS 2010).

Here is some sample code to capture the screen with DirectX 9. You shouldn't have to install any SDK (except the standard files that come with Visual Studio, although I didn't tested VS 2010).

只需创建一个简单的 Win32 控制台应用程序,在 stdafx.h 文件中添加以下内容:

Just create a simple Win32 console application, add the following in the stdafx.h file:

  #include <Wincodec.h>             // we use WIC for saving images
  #include <d3d9.h>                 // DirectX 9 header
  #pragma comment(lib, "d3d9.lib")  // link to DirectX 9 library

这是示例主要实现

  int _tmain(int argc, _TCHAR* argv[])
  {
    HRESULT hr = Direct3D9TakeScreenshots(D3DADAPTER_DEFAULT, 10);
    return 0;
  }

这将捕获 10 倍屏幕,并将cap%i.png"图像保存在磁盘上.它还会显示为此花费的时间(保存图像不计入该时间,只计算屏幕截图).在我的(桌面 Windows 8 - Dell Precision M2800/i7-4810MQ-2.80GHz/Intel HD 4600,这是一台非常糟糕的机器......)机器上,它需要在大约 4 秒内捕获 100 个 1920x1080,所以大约 20/25 fps.

What this will do is capture 10 times the screen, and save "cap%i.png" images on the disk. It will also display the time taken for this (saving images is not counted in that time, only screen captures). On my (desktop windows 8 - Dell Precision M2800/i7-4810MQ-2.80GHz/Intel HD 4600 which is a pretty crappy machine...) machine, it takes 100 1920x1080 captures in ~4sec, so around 20/25 fps.

  HRESULT Direct3D9TakeScreenshots(UINT adapter, UINT count)
  {
    HRESULT hr = S_OK;
    IDirect3D9 *d3d = nullptr;
    IDirect3DDevice9 *device = nullptr;
    IDirect3DSurface9 *surface = nullptr;
    D3DPRESENT_PARAMETERS parameters = { 0 };
    D3DDISPLAYMODE mode;
    D3DLOCKED_RECT rc;
    UINT pitch;
    SYSTEMTIME st;
    LPBYTE *shots = nullptr;

    // init D3D and get screen size
    d3d = Direct3DCreate9(D3D_SDK_VERSION);
    HRCHECK(d3d->GetAdapterDisplayMode(adapter, &mode));

    parameters.Windowed = TRUE;
    parameters.BackBufferCount = 1;
    parameters.BackBufferHeight = mode.Height;
    parameters.BackBufferWidth = mode.Width;
    parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
    parameters.hDeviceWindow = NULL;

    // create device & capture surface
    HRCHECK(d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, NULL, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &parameters, &device));
    HRCHECK(device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr));

    // compute the required buffer size
    HRCHECK(surface->LockRect(&rc, NULL, 0));
    pitch = rc.Pitch;
    HRCHECK(surface->UnlockRect());

    // allocate screenshots buffers
    shots = new LPBYTE[count];
    for (UINT i = 0; i < count; i++)
    {
      shots[i] = new BYTE[pitch * mode.Height];
    }

    GetSystemTime(&st); // measure the time we spend doing <count> captures
    wprintf(L"%i:%i:%i.%i
", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    for (UINT i = 0; i < count; i++)
    {
      // get the data
      HRCHECK(device->GetFrontBufferData(0, surface));

      // copy it into our buffers
      HRCHECK(surface->LockRect(&rc, NULL, 0));
      CopyMemory(shots[i], rc.pBits, rc.Pitch * mode.Height);
      HRCHECK(surface->UnlockRect());
    }
    GetSystemTime(&st);
    wprintf(L"%i:%i:%i.%i
", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

    // save all screenshots
    for (UINT i = 0; i < count; i++)
    {
      WCHAR file[100];
      wsprintf(file, L"cap%i.png", i);
      HRCHECK(SavePixelsToFile32bppPBGRA(mode.Width, mode.Height, pitch, shots[i], file, GUID_ContainerFormatPng));
    }

  cleanup:
    if (shots != nullptr)
    {
      for (UINT i = 0; i < count; i++)
      {
        delete shots[i];
      }
      delete[] shots;
    }
    RELEASE(surface);
    RELEASE(device);
    RELEASE(d3d);
    return hr;
  }

请注意,此代码隐式链接到 WIC(包含一个成像库使用 Windows 很长一段时间)来保存图像文件(因此您不需要需要安装旧 DirectX SDK 的著名 D3DXSaveSurfaceToFile):

Note this code implicitely links to WIC (an imaging library included with Windows for quite a time now) to save the image files (so you don't need the famous D3DXSaveSurfaceToFile that require old DirectX SDKs to be installed):

  HRESULT SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, LPWSTR filePath, const GUID &format)
  {
    if (!filePath || !pixels)
      return E_INVALIDARG;

    HRESULT hr = S_OK;
    IWICImagingFactory *factory = nullptr;
    IWICBitmapEncoder *encoder = nullptr;
    IWICBitmapFrameEncode *frame = nullptr;
    IWICStream *stream = nullptr;
    GUID pf = GUID_WICPixelFormat32bppPBGRA;
    BOOL coInit = CoInitialize(nullptr);

    HRCHECK(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)));
    HRCHECK(factory->CreateStream(&stream));
    HRCHECK(stream->InitializeFromFilename(filePath, GENERIC_WRITE));
    HRCHECK(factory->CreateEncoder(format, nullptr, &encoder));
    HRCHECK(encoder->Initialize(stream, WICBitmapEncoderNoCache));
    HRCHECK(encoder->CreateNewFrame(&frame, nullptr)); // we don't use options here
    HRCHECK(frame->Initialize(nullptr)); // we dont' use any options here
    HRCHECK(frame->SetSize(width, height));
    HRCHECK(frame->SetPixelFormat(&pf));
    HRCHECK(frame->WritePixels(height, stride, stride * height, pixels));
    HRCHECK(frame->Commit());
    HRCHECK(encoder->Commit());

  cleanup:
    RELEASE(stream);
    RELEASE(frame);
    RELEASE(encoder);
    RELEASE(factory);
    if (coInit) CoUninitialize();
    return hr;
  }

还有一些我用过的宏:

  #define WIDEN2(x) L ## x
  #define WIDEN(x) WIDEN2(x)
  #define __WFILE__ WIDEN(__FILE__)
  #define HRCHECK(__expr) {hr=(__expr);if(FAILED(hr)){wprintf(L"FAILURE 0x%08X (%i)
	line: %u file: '%s'
	expr: '" WIDEN(#__expr) L"'
",hr, hr, __LINE__,__WFILE__);goto cleanup;}}
  #define RELEASE(__p) {if(__p!=nullptr){__p->Release();__p=nullptr;}}

注意:对于 Windows 8+ 客户端,应放弃所有这些(WIC 除外)以支持 桌面复制 API.

Note: for Windows 8+ clients, all these (except WIC) should be dropped in favor of the Desktop Duplication API.

相关文章