Windows 下最快的屏幕捕捉方法

我想写一个视窗平台的屏幕播放程序,但不确定如何捕捉屏幕。我所知道的唯一方法是使用 GDI,但是我很好奇是否还有其他方法来实现这一点,如果有的话,哪种方法的开销最小?速度是第一位的。

截屏程序将用于记录游戏画面,但是,如果这确实缩小了选择范围,我仍然欢迎任何其他建议,超出了这个范围。毕竟知识也不是坏事。

编辑 : 我偶然发现了这篇文章: 捕捉屏幕的各种方法。它向我介绍了 WindowsMediaAPI 和 DirectX 的操作方法。它在结论中提到禁用硬件加速可以极大地提高捕获应用程序的性能。我很好奇为什么会这样。有人能帮我补充一下缺失的部分吗?

编辑 : 我读到像 Camtasia 这样的截屏程序使用它们自己的捕获驱动程序。谁能给我一个深入的解释,它是如何工作的,为什么它更快?我可能还需要关于实现这样的东西的指导,但是我肯定现在已经有文档了。

此外,我现在知道 FRAPS 如何记录屏幕。它钩住底层的图形 API 从后台缓冲区读取。据我所知,这比从前端缓冲区读取要快,因为读取是从系统 RAM 而不是视频 RAM。您可以阅读文章 给你

202245 次浏览

对于 C + + ,可以使用: http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
这可能不适用于所有类型的3D 应用程序/视频应用程序。那么 这个链接可能更有用,因为它描述了3种不同的方法,你可以使用。

古老的回答(C #) :
您可以使用 系统,绘图,图形,收到,但它不是很快。

我写的一个示例项目就是这样做的: http://blog.tedd.no/index.php/2010/08/16/c-image-analysis-auto-gaming-with-source/

我计划使用 Direct3D: http://spazzarama.com/2009/02/07/screencapture-with-direct3d/这样的更快的方法来更新这个示例

这里有一个捕捉视频的链接: 如何使用 C # . Net 将屏幕捕获为视频?

我自己做的 Directx 和认为它的快,因为你会希望它是。我没有一个快速的代码示例,但我发现 这个应该是有用的。Directx11版本应该差别不大,directx9可能差别更大一点,但这是正确的方法

这是我用来收集单帧的方法,但是如果您修改它,并保持两个目标始终处于打开状态,那么您可以使用文件名的静态计数器将其“流”到磁盘上。- 我不记得我在哪里找到这个,但它已经被修改,感谢任何人!

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

编辑: 我可以看到,这是列在您的第一个编辑链接作为“ GDI 方式”。这仍然是一个体面的方式去,即使在该网站的性能咨询,你可以轻松地达到30 fps 我认为。

来自 这个的评论(我没有这方面的经验,我只是引用了有这方面经验的人) :

HDC hdc = GetDC(NULL); // get the desktop device context
HDC hDest = CreateCompatibleDC(hdc); // create a device context to use yourself


// get the height and width of the screen
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);


// create a bitmap
HBITMAP hbDesktop = CreateCompatibleBitmap( hdc, width, height);


// use the previously created device context with the bitmap
SelectObject(hDest, hbDesktop);


// copy from the desktop device context to the bitmap device context
// call this once per 'frame'
BitBlt(hDest, 0,0, width, height, hdc, 0, 0, SRCCOPY);


// after the recording is done, release the desktop context you got..
ReleaseDC(NULL, hdc);


// ..delete the bitmap you were using to capture frames..
DeleteObject(hbDesktop);


// ..and delete the context you created
DeleteDC(hDest);

我不是说这是最快的,但是如果在兼容的设备上下文之间复制,BitBlt操作通常是非常快的。

作为参考,开放式广播软件在它们的 “直流捕获”方法中实现了类似的东西,尽管它们没有使用 CreateCompatibleDC创建目标上下文 hDest,而是使用了与 DirectX 10 + 一起工作的 IDXGISurface1。如果没有这方面的支持,他们就会退回到 CreateCompatibleDC

要改变它使用一个特定的应用程序,你需要改变第一行的 GetDC(game),其中 game是游戏的窗口处理,然后设置正确的 heightwidth的游戏的窗口也。

一旦你在 hDest/hbDesktop 中有了像素,你仍然需要将它保存到一个文件中,但是如果你正在进行屏幕捕捉,那么我认为你需要在内存中缓冲一定数量的像素,然后将它们分块保存到视频文件中,所以我不会指向将静态图像保存到磁盘的代码。

我编写了一个类,它实现了用于屏幕捕获的 GDI 方法。我也想要更快的速度,所以在发现了 DirectX 方法(通过 GetFrontBuffer)之后,我尝试了这个方法,希望它能更快。

我沮丧地发现 GDI 的性能比 GDI 快2.5倍。经过100次捕获我的双显示器显示的试验,GDI 实现平均每个屏幕捕获0.65秒,而 DirectX 方法平均每个屏幕捕获1.72秒。所以根据我的测试,GDI 绝对比 GetFrontBuffer 快。

我无法通过 GetRenderTargetData 让 Brandrew 的代码工作来测试 DirectX。屏幕上的拷贝是纯黑色的。然而,它可以复制那个空白屏幕超级快!我将继续对此进行修补,并希望得到一个可用的版本,以看到真正的结果。

我已经能够收集到一些东西: 显然使用“镜像驱动程序”是很快的,虽然我不知道一个 OSS 的。

为什么 RDP 与其他远程控制软件相比如此之快?

显然,使用 StretchRect 的一些卷积也比 BitBlt 快

Http://betterlogic.com/roger/2010/07/fast-screen-capture/comment-page-1/#comment-5193

你提到的那个(连接到 D3D dll 的 fraps)可能是 D3D 应用程序的唯一方法,但是不适用于 Windows XP 桌面捕获。所以,现在我只希望有一个 fraps 等效的速度为正常桌面窗口... 有人吗?

(我认为您可以使用 aero,像 fraps 一样的钩子,但 XP 用户将不会那么幸运)。

显然,改变屏幕位深度和/或禁用硬件访问。可能会有所帮助(和/或禁用 Aero)。

Https://github.com/rdp/screen-capture-recorder-program 包括一个相当快的基于 BitBlt 的捕获工具,以及一个基准测试器作为其安装的一部分,它可以让你基准 BitBlt 的速度来优化它们。

VirtualDub 还有一个“ opengl”屏幕捕捉模块,据说这个模块速度很快,可以完成像变化检测 http://www.virtualdub.org/blog/pivot/entry.php?id=290这样的任务

我使用 d3d9获取后台缓冲区,并使用 d3dx 库将其保存到 png 文件中:

IDirect3DSurface9 *surface ;


// GetBackBuffer
idirect3ddevice9->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface ) ;


// save the surface
D3DXSaveSurfaceToFileA( "filename.png", D3DXIFF_PNG, surface, NULL, NULL ) ;


SAFE_RELEASE( surface ) ;

要做到这一点,您应该创建您的交换缓冲区

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(所以在截屏之前,你要保证后台缓冲区不会被破坏)。

在我的印象中,GDI 方法和 DX 方法在本质上是不同的。 使用 GDI 绘图采用 FLUSH 方法,FLUSH 方法绘制帧然后清除它,并在同一缓冲区中重新绘制另一帧,这将导致在游戏中闪烁需要很高的帧速率。

  1. 为什么 DX 更快? 在 DX (或图形世界)中,采用了一种更为成熟的方法——双缓冲区渲染,即存在两个缓冲区,当前端缓冲区呈现给硬件时,也可以呈现给另一个缓冲区,然后在帧1完成渲染后,系统切换到另一个缓冲区(锁定后呈现给硬件,释放前端缓冲区) ,这样就大大提高了渲染效率。
  2. 为什么要更快地拒绝硬件加速? 虽然采用了双缓冲渲染,FPS 得到了改进,但是渲染时间仍然有限。现代图形硬件通常在渲染过程中涉及到大量的优化,比如抗锯齿,这是非常耗费计算的,如果你不需要高质量的图形,当然你可以禁用这个选项。这样可以节省你的时间。

我认为你真正需要的是一个重播系统,我完全同意人们所讨论的。

我知道下面的建议并不能回答你的问题,但是我发现最简单的方法来捕捉快速变化的 DirectX 视图,就是将一个 摄像机插入显卡的 S-video 端口,并将图像记录为电影。然后将视频从摄像机传回到计算机上的 MPG、 WMV、 AVI 等文件中。

我编写了一个视频捕捉软件,类似于用于 DirectX 应用程序的 FRAPS。源代码是可用的,我的文章解释了一般的技术。看看 http://blog.nektra.com/main/2013/07/23/instrumenting-direct3d-applications-to-capture-video-and-calculate-frames-per-second/

关于你提出的有关表现的问题,

  • DirectX 应该比 GDI 更快,除非你从缓冲区前端读取数据的速度非常慢。我的方法类似于 FRAPS (从后台缓冲区读取)。我从 Direct3D 接口拦截了一组方法。

  • 对于实时录像(应用程序影响最小) ,快速编解码器是必不可少的。FRAPS 使用自己的无损视频编解码器。Lagarith 和 HUFFYUV 是为实时应用而设计的通用无损视频编解码器。如果要输出视频文件,应该查看它们。

  • 另一种记录截屏的方法是编写一个镜像驱动程序。根据 Wikipedia: 当视频镜像处于活动状态时,每当系统在镜像区域内的某个位置绘制到主视频设备时,将在镜像视频设备上实时执行绘制操作的一个副本。查看 MSDN 上的镜像驱动程序: http://msdn.microsoft.com/en-us/library/windows/hardware/ff568315(v=vs.85).aspx

您可以尝试 c + + 开源项目 WinRobot@git,它是一个强大的屏幕捕获器

CComPtr<IWinRobotService> pService;
hr = pService.CoCreateInstance(__uuidof(ServiceHost) );


//get active console session
CComPtr<IUnknown> pUnk;
hr = pService->GetActiveConsoleSession(&pUnk);
CComQIPtr<IWinRobotSession> pSession = pUnk;


// capture screen
pUnk = 0;
hr = pSession->CreateScreenCapture(0,0,1280,800,&pUnk);


// get screen image data(with file mapping)
CComQIPtr<IScreenBufferStream> pBuffer = pUnk;

支援:

  • UAC 窗口
  • 温洛根
  • DirectShowOverlay

您需要 桌面复制 API(自 Windows8以来可用)。这是官方推荐的方法,也是 CPU 效率最高的方法。

它有一个很好的屏幕转播功能,就是它可以检测窗口移动,所以当窗口移动时,你可以传输块增量,而不是原始像素。此外,它告诉你哪些矩形已经改变,从一个框架到下一个。

Microsoft 示例代码相当复杂,但是 API 实际上很简单,也很容易使用。我整理了一个更简单的示例项目:

简化示例代码

WindowsDesktopDuplicationSample

微软参考资料

桌面复制 API

官方示例代码 (我上面的示例是该代码的精简版)

屏幕录制可以在 [他们]中使用 < em > 甚高效液相色谱仪 API 完成。我已经做了一个示例程序来演示这一点。它使用 < em > LibVLCSharp < em > VideoLAN.LibVLC.Windows 库。您可以使用这个跨平台 API 实现更多与视频呈现相关的特性。

有关 API 文档,请参见: API Github

using System;
using System.IO;
using System.Reflection;
using System.Threading;
using LibVLCSharp.Shared;


namespace ScreenRecorderNetApp
{
class Program
{
static void Main(string[] args)
{
Core.Initialize();


using (var libVlc = new LibVLC())
using (var mediaPlayer = new MediaPlayer(libVlc))
{
var media = new Media(libVlc, "screen://", FromType.FromLocation);
media.AddOption(":screen-fps=24");
media.AddOption(":sout=#transcode{vcodec=h264,vb=0,scale=0,acodec=mp4a,ab=128,channels=2,samplerate=44100}:file{dst=testvlc.mp4}");
media.AddOption(":sout-keep");


mediaPlayer.Play(media);
Thread.Sleep(10*1000);
mediaPlayer.Stop();
}
}
}
}

桌面捕获

用 DXGI 复制捕获桌面图像的项目。将捕获的图像以不同的图像格式保存到文件中(* 。* .* .如果)。

这个示例是用 C + + 编写的,您还需要一些 DirectX (D3D11,D2D1)方面的经验。

应用程序可以做什么

  • 如果您有多个桌面显示器,可以选择。
  • 调整捕获的桌面图像的大小。
  • 选择不同的缩放模式。
  • 可以在输出图像中显示或隐藏鼠标图标。
  • 您可以旋转输出图片的图像,或者将其保留为默认值。

这可能不是最快的方法,但是它是轻量级的并且易于使用。图像作为包含 RGB 颜色的整数数组返回。

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <Windows.h>
int* screenshot(int& width, int& height) {
HDC hdc = GetDC(NULL); // get the desktop device context
HDC cdc = CreateCompatibleDC(hdc); // create a device context to use yourself
height = (int)GetSystemMetrics(SM_CYVIRTUALSCREEN); // get the width and height of the screen
width  = 16*height/9; // only capture left monitor for dual screen setups, for both screens use (int)GetSystemMetrics(SM_CXVIRTUALSCREEN);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, width, height); // create a bitmap
SelectObject(cdc, hbitmap); // use the previously created device context with the bitmap
BITMAPINFOHEADER bmi = { 0 };
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biWidth = width;
bmi.biHeight = -height; // flip image upright
bmi.biCompression = BI_RGB;
bmi.biSizeImage = 3*width*height;
BitBlt(cdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY); // copy from desktop device context to bitmap device context
ReleaseDC(NULL, hdc);
int* image = new int[width*height];
GetDIBits(cdc, hbitmap, 0, height, image, (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
DeleteObject(hbitmap);
DeleteDC(cdc);
return image;
}

上面的代码组合了 这个答案这个答案

如何使用示例:

int main() {
int width=0, height=0;
int* image = screenshot(width, height);


// access pixel colors for position (x|y)
const int x=0, y=0;
const int color = image[x+y*width];
const int red   = (color>>16)&255;
const int green = (color>> 8)&255;
const int blue  =  color     &255;


delete[] image;
}

窗口,图形,捕捉

通过使用系统选择器 UI 控件,使应用程序能够以安全、易用的方式捕获环境、应用程序窗口和显示。

Https://blogs.windows.com/windowsdeveloper/2019/09/16/new-ways-to-do-screen-capture/