【问题标题】:Capture screen using DirectX使用 DirectX 捕获屏幕
【发布时间】:2017-05-22 14:11:56
【问题描述】:

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

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

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

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 <D3dx9tex.h>)。

谁能提供一个包含所有必需包含的工作示例或指出我应该安装哪些库。

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


编辑:

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

【问题讨论】:

  • @yms 我找不到适用于 Windows 7 的 DirectX SDK 的下载链接!另外,为什么我可以包含这个库:#include <D3D9.h>,我是否安装了旧版本的 DirectX SDK 或其他什么?
  • 我尝试了不同的屏幕捕获方法,包括 DirectX 一种,而 DirectX 一种比其他方法更快,它没有提供显着的加速(以涵盖额外的复杂性和花费的时间)。抱歉,我不记得任何细节或数字。
  • @Andy T 你能在 1 秒内截取多少张截图?
  • 我的要求很低,大概10fps,对于桌面捕捉来说已经足够了,但对于游戏/视频捕捉来说就不够了
  • 您想支持 Windows 7 作为目标平台吗?

标签: c++ winapi directx


【解决方案1】:

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

只需创建一个简单的 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

这里是示例主要实现

  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,这是一台非常糟糕的机器......)机器上,它需要 100 次 1920x1080 捕获在 ~4 秒内,所以大约 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;
  }

请注意,此代码隐式链接到 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;
  }

还有我使用的一些宏:

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

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

【讨论】:

  • 我尝试更改您的代码以捕获 jpeg。结果并不令人满意,因为图像会有垂直线重叠和处理缓慢。无论如何,请加快速度并提高图像质量?
  • @FeiHapLee - 再问一个问题
  • @Simon,已发布。谢谢。请参考stackoverflow.com/questions/33753912/…
  • 我可以在 DX11 游戏中使用它吗?
  • 这段代码很棒,但是当我切换到全屏 Directx 应用程序时,它会在我切换时拍摄屏幕截图,并一遍又一遍地连续显示相同的图像。有什么建议吗?
【解决方案2】:

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

【讨论】:

  • 对不起,我忘了提这个。实际上我什至想要 Windows XP 支持 :-)
  • @user4592590 这只是我的主观意见,但我怀疑您是否能够在 XP 上以任何其他方式执行此操作,而不是 GDI 很慢:(
【解决方案3】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多