C++ 使用 DirectX 捕获屏幕

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30021274/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 13:41:34  来源:igfitidea点击:

Capture screen using DirectX

c++winapidirectx

提问by Simon Mourier

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

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

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.

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

I have found this questionthat 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();
}

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

我试图包含所需的文件。但是,并非所有这些都可以包含在内(例如#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.

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

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

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



Edit:

编辑:

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

还有,这段代码不完整,比如Device标识符是什么?!

回答by Simon Mourier

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).

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

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

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

  #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

Here is the sample main implementation

这是示例主要实现

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

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.

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

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):

请注意,此代码隐式链接到WIC(Windows 中包含的图像库,现在已经有一段时间了)以保存图像文件(因此您不需要需要安装旧 DirectX SDK 的著名 D3DXSaveSurfaceToFile):

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

And some macros I used:

还有一些我使用的宏:

  #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)\n\tline: %u file: '%s'\n\texpr: '" WIDEN(#__expr) L"'\n",hr, hr, __LINE__,__WFILE__);goto cleanup;}}
  #define RELEASE(__p) {if(__p!=nullptr){__p->Release();__p=nullptr;}}

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

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

回答by Rudolfs Bundulis

You have not stated the requirements for the target Windows versions. If you do not need to support Windows 7, Windows 8 includes a nice new DXGI interface IDXGIOutputDuplicationthat allows to create a COM object that duplicates the output of a video adapter and provides CPU access to the video memory through IDXGIOutputDuplication::MapDesktopSurface. MSDN has quite a nice samplethat captures the desktop through this and draws it inside a form and works nice and smooth. So if Windows 7 is not a must have, I'd suggest you look at this.

您尚未说明目标 Windows 版本的要求。如果您不需要支持 Windows 7,Windows 8 包含一个不错的新 DXGI 接口IDXGIOutputDuplication,它允许创建一个 COM 对象来复制视频适配器的输出,并通过IDXGIOutputDuplication::MapDesktopSurface提供对视频内存的 CPU 访问。MSDN 有一个很好的示例,通过它捕获桌面并将其绘制在表单中,并且运行良好且流畅。所以如果 Windows 7 不是必须的,我建议你看看这个。

回答by MuertoExcobito

You can get the DirectX SDK from microsoft.com/en-ca/download/details.aspx?id=6812 (posted by @yms). This SDK is compatible with all versions Windows, including XP. Refer to its documentation on how to include/link with D3D9.

您可以从 microsoft.com/en-ca/download/details.aspx?id=6812(@yms 发布)获取 DirectX SDK。此 SDK 兼容所有版本的 Windows,包括 XP。请参阅其有关如何包含/链接 D3D9 的文档。

In your example Deviceis an IDirect3DDevice9. Every D3D9 application must create one of these. It's very easy to find example code on how to create one (eg. https://msdn.microsoft.com/en-us/library/windows/desktop/bb204867%28v=vs.85%29.aspx).

在您的示例中Device是一个IDirect3DDevice9. 每个 D3D9 应用程序都必须创建其中之一。很容易找到有关如何创建的示例代码(例如https://msdn.microsoft.com/en-us/library/windows/desktop/bb204867%28v=vs.85%29.aspx)。

In your example code, only the contents being rendered in DirectX are being captured, which I assume is not your intention. To capture the entire screen (which I'm assuming is the goal), instead of using IDirect3DDevice9::GetRenderTarget, you should use IDirect3DDevice9::GetFrontBufferData, as in this tutorial (http://dim-i.net/2008/01/29/taking-screenshots-with-directx-and-dev-cpp/). If you're looking for speed, you should not recreate the offscreen surface every frame, as in both your example and this tutorial. The memory pool should be D3DPOOL_SYSTEMMEMin this case, not D3DPOOL_SCRATCH. Depending on the size of the screen, the likely bottleneck will be writing the images to disk.

在您的示例代码中,仅捕获在 DirectX 中呈现的内容,我认为这不是您的意图。要捕获整个屏幕(我假设这是目标)IDirect3DDevice9::GetRenderTarget,您应该使用IDirect3DDevice9::GetFrontBufferData,而不是使用,如本教程中所示(http://dim-i.net/2008/01/29/taking-screenshots-with -directx-and-dev-cpp/)。如果您正在寻找速度,则不应像您的示例和本教程一样,每帧都重新创建屏幕外表面。D3DPOOL_SYSTEMMEM在这种情况下,内存池应该是,而不是D3DPOOL_SCRATCH. 根据屏幕的大小,可能的瓶颈是将图像写入磁盘。

Also note that the screen captured from this will be for the adapter used to create the IDirect3DDevice9. This is the first parameter to IDirect3D9::CreateDevice. This is only a concern if capturing multiple monitors is a possibility.

另请注意,由此捕获的屏幕将用于用于创建IDirect3DDevice9. 这是 的第一个参数IDirect3D9::CreateDevice。如果可以捕获多个监视器,这只是一个问题。