在8/10窗口技术中实现完全实时的屏幕捕获,无延迟

use*_*837 5 c# c++ winapi

我将使用C#或C ++ 创建一个非常快速且实时的远程服务,这没关系,但是目前我正在使用c#。

好的,我要寻找的是一种以最快的速度实时捕获Windows屏幕的方法。

我知道网上已经有办法了,但问题是延迟 ...

  • 我使用C#copyfromscreen,1920x1080的延迟为92ms [视频中的5帧]
  • 我将C#copyfromscreen与jpg编码器一起使用,对于1920x1080,延迟为36ms
  • 我将Unity3D screenCapture与jpg编码器一起使用,对于1920x1080,延迟为38ms
  • 我使用C#Windows Desktop Duplication API,对于1920x1080,延迟为19ms [视频中3帧]
  • 我将C#Windows Desktop Duplication API与jpg编码器一起使用,对于1920x1080 [视频中2帧],延迟为12ms
  • 我将C ++ Windows Desktop Duplication API与jpg编码器一起使用,对于1920x1080 [视频中2帧],延迟为9ms
  • 我将C#Windows DirectXCapture与jpg编码器一起使用,对于1920x1080 [视频中2帧],延迟为16ms

我以这种方式认为进度是正常的,直到我签出Windows 10/8任务栏实时预览缩略图,并且它是完全实时的,没有1ms的延迟,这意味着帧对帧!

我尝试将所有方法的1920 x 1080尺寸调整为“任务栏预览”尺寸,但未做任何更改!

注意:该程序无法通过Internet处理,因此可以在本地网络上运行。

我猜测延迟是由于位图处理或其他原因造成的,但我还不知道! [添加时间]这是我在服务器上处理图片的方式:

private static byte[] capture()
{
    Bitmap bmp = screenshot_DDAPI(); // Avarage Time : 29.6566ms
    MemoryStream ms = new MemoryStream();
    bmp.Save(ms, ImageFormat.Jpeg); // Avarage Time : 1.7101 ms
    return ms.ToArray(); // Avarage Time : 3.032 ms
}
Run Code Online (Sandbox Code Playgroud)

有人知道Windows使用哪种技术和方法来处理屏幕截图以显示任务栏实时缩略图吗?

注意:如果此问题与堆栈溢出无关或不在主题之列,请告诉我可以问我哪个堆栈类别?

谢谢

Soo*_*nts 5

桌面复制 API 是最快的。捕获延迟为零。

但是,在捕获之后,您将原始纹理数据下载到基于 CPU 的 jpeg 编码器的系统 RAM 中。这就是花时间。实时缩略图不需要这样做,它们在 GPU 中缩放窗口纹理并使用 GPU 进行渲染,当源数据已经在 VRAM 中时,两者都非常便宜。

如果您真的想最大限度地减少延迟,请寻找可以从B8G8R8A8_UNORMD3D11 纹理获取源数据的基于 GPU 的 JPEG 编码器。JPEG 占用的空间(因此带宽)比 RGBA 少得多,也就是说,您可能会更快地获得编码结果。


Ice*_*ind 2

最快的方法可能是使用 DirectX。每个 DirectX 应用程序都包含一个缓冲区或一个表面,用于保存与该应用程序相关的视频内存的内容。这称为应用程序的后台缓冲区。某些应用程序可能有多个后台缓冲区。还有另一个每个应用程序都可以默认访问的缓冲区 - 前缓冲区。这个前端缓冲区保存与桌面内容相关的视频内存,因此本质上就是屏幕图像。通过从应用程序访问前端缓冲区,您可以捕获当时屏幕的内容。

从应用程序访问前端缓冲区非常简单明了。该接口IDirect3DDevice9提供了GetFrontBufferData()采用IDirect3DSurface9对象指针并将前端缓冲区的内容复制到该表面上的方法。IDirect3DSurfce9可以使用 方法生成该对象IDirect3DDevice8::CreateOffscreenPlainSurface()。一旦屏幕被捕获到表面上,您可以使用该功能D3DXSaveSurfaceToFile()将表面以位图格式直接保存到磁盘。因此,捕获屏幕的代码如下所示:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;

    // g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}
Run Code Online (Sandbox Code Playgroud)

如果您想要实际位的缓冲区,而不是直接将其保存到磁盘,您可以使用如下代码:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;

// g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();
Run Code Online (Sandbox Code Playgroud)

确保在复制到 之前我们已经分配了足够的内存pBits。典型值为每像素BITSPERPIXEL位数32。但是,它可能会根据您当前的显示器设置而有所不同。

编辑

这是我编写的一个 C++ 程序来计时。该函数Direct3D9TakeScreenshots采用一个参数来确定拍摄屏幕截图的次数。我现在将其设置为 10,但您可以更改它。在我的机器上以发布模式运行,我得到以下结果:

18:33:23.189
18:33:23.579
Run Code Online (Sandbox Code Playgroud)

因此,390拍摄 10 个屏幕截图并将其复制到缓冲区只需几毫秒。每个屏幕截图和缓冲区副本平均大约需要 39 毫秒。

#include "pch.h"
#include <iostream>

#include <d3d9.h>                 // DirectX 9 header
#pragma comment(lib, "d3d9.lib")  // link to DirectX 9 library

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

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 = nullptr;

    // 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);


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

int main()
{
    Direct3D9TakeScreenshots(D3DADAPTER_DEFAULT, 10);
}
Run Code Online (Sandbox Code Playgroud)