Windows上最快的屏幕捕获方法


159

我想为Windows平台编写一个截屏程序,但是不确定如何捕获屏幕。我知道的唯一方法是使用GDI,但我很好奇是否还有其他方法可以解决此问题,如果有的话,哪种方法开销最少?速度是重中之重。

该截屏节目将用于记录游戏画面,但是,如果这确实缩小了选项的范围,我仍然愿意接受不属于此范围的任何其他建议。毕竟,知识还不错。

编辑:我碰到了这篇文章:各种捕获屏幕的方法。它向我介绍了Windows Media API的实现方式和DirectX的实现方式。它在结论中提到禁用硬件加速可以大大提高捕获应用程序的性能。我很好奇为什么会这样。谁能为我填补缺失的空白?

编辑:我读过诸如Camtasia之类的截屏程序使用它们自己的捕获驱动程序。有人可以详细解释它的工作原理以及为什么它更快。我可能还需要有关实现类似内容的指导,但是我确信无论如何都存在现有文档。

另外,我现在知道FRAPS如何记录屏幕。它挂接底层图形API以从后缓冲区读取。据我了解,这比从前端缓冲区读取要快,因为您正在从系统RAM而不是视频RAM进行读取。您可以在这里阅读文章。


您是否考虑过使用重放系统,而不是以图形方式记录屏幕内容?
本杰明·林德利

2
您不必钩任何东西。您只需要编写输入事件,以使它们不直接控制游戏,而是调用其他函数。例如,如果玩家按下左键,您将不会简单地减少玩家的x位置。而是调用一个函数,例如MovePlayerLeft()。您还可以记录按键和其他输入的时间和持续时间。然后,当您处于播放模式时,您只需忽略输入,而是读取记录的数据。如果在数据中看到按左键,则调用MovePlayerLeft()
本杰明·林德利

1
@PigBen这将是一个录制游戏录像的通用应用程序。不是针对特定游戏的。就我所知,按左键可能意味着向右移动。此外,您还没有考虑不受用户影响的事件。``那渲染呢?
someguy 2011年

1
@someguy好吧,我想您正在做的工作更加激烈,我使用上述方法添加了一个例程,以节省30fps的游戏重播AVI的速度。我使用Windows API制作了多监视器屏幕录像机,以实现“劳动力优化”,但即使在目标4fps下也表现不佳。
AJG85 2011年

1
有一个用于Windows的开源镜像驱动器上的鸟哥的存放点这里ultravnc.svn.sourceforge.net/viewvc/ultravnc/...
搁浅

Answers:


62

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

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

谢谢。不久前,我听说过这种方法,据说比从前端缓冲区读取数据要快。老实说,您这样做是否正确?
someguy 2011年

前缓冲区的问题是访问权限之一,即试图复制当前正在渲染的平原“中断”该副本。它对我来说足够好用,并且会占用我的硬盘!
Brandrew

@bobobobo我不知道它将如何工作,但是我正在考虑使用类似Huffyuv的东西。编辑:或者让用户从可用的DirectShow过滤器中进行选择。
someguy 2011年

1
我只是无法设法使它工作... DirectX在GetRenderTargetData部分上吐出一些无效的调用。显然,创建设备的方式非常重要。
LightStriker

12
downvote,它仅适用于您自己的应用程序,因此不能用于记录通用程序
user3125280 2013年

32

编辑:我可以看到,这在您的第一个编辑链接下被列为“ GDI方式”。即使在该站点上提供性能咨询,这仍然是一种不错的方法,我认为您可以轻松达到30fps。

根据评论(我没有这样做的经验,我只是指的是这样做的人):

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如果要在兼容的设备上下文之间进行复制,则操作通常会非常快。

作为参考,Open Broadcaster Software在其“ dc_capture” 方法中实现了类似的功能,尽管与其使用DirectX 10+的而不是hDest使用CreateCompatibleDC它们创建目标上下文IDXGISurface1。如果没有对此的支持,他们会退回到CreateCompatibleDC

要改变它使用一个特定的应用程序,您需要更改的第一行到GetDC(game)这里game是游戏的窗口的句柄,然后设置权heightwidth游戏的窗口太多。

将像素放置在hDest / hbDesktop中之后,您仍然需要将其保存到文件中,但是如果您要进行屏幕捕获,那么我想您希望将一定数量的像素缓冲在内存中并保存到视频文件中以块为单位,因此我不会指向用于将静态映像保存到磁盘的代码。


3
msdn.microsoft.com/zh-cn/library/dd183370%28VS.85%29.aspx 摘录:如果源设备上下文和目标设备上下文的颜色格式不匹配,则BitBlt函数将源颜色格式转换为与目标对象匹配的颜色格式。

2
一些证据可以证明那是很好的。您是否在某处发布了性能比较,或者看到了准确的比较?

2
尝试对其进行分析。正如我在帖子中所说的,我指的是具有GDI经验的人。如果它泄漏内存,并且您知道如何解决,请编辑帖子以消除泄漏。

1
通过这种方法,我得到了大约5 fps。这是在具有集成图形的笔记本电脑上,因此我希望从带有真实图形卡的台式机上获得更好的效果,但是仍然很慢。
Timmmm 2013年

1
@Timmmm我添加了对OBS实现此方法的参考。希望这可以为您加快速度。

19

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

尊重与绩效相关的问题,

  • DirectX应该比GDI快,除非是从非常慢的Frontbuffer中读取。我的方法类似于FRAPS(从后缓冲区读取)。我从Direct3D接口中截取了一组方法。

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

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


16

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

    IDirect3DSurface9 * surface;

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

    //保存表面
    D3DXSaveSurfaceToFileA(“ filename.png”,D3DXIFF_PNG,surface,NULL,NULL);

    SAFE_RELEASE(surface);

为此,您应该使用

d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(因此,在拍摄屏幕快照之前,请确保不会损坏后缓冲)。


很有道理,谢谢。您知道这与之间有什么区别GetRenderTarget吗?
someguy 2011年

那只是获取当前的渲染目标(如果有人在您调用时渲染纹理,则可能是另一个屏幕外表面)。
bobobobo

13

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

  1. 为什么DX更快?在DX(或图形世界)中,应用了一种更成熟的方法,称为“双缓冲区渲染”,其中存在两个缓冲区,当将前缓冲区提供给硬件时,也可以渲染到另一个缓冲区,然后在第1帧之后完成渲染后,系统将交换到另一个缓冲区(将其锁定以呈现给硬件,然后释放先前的缓冲区),从而极大地改善了渲染效率。
  2. 为什么要更快地拒绝硬件加速?尽管使用双缓冲区渲染可以改善FPS,但是渲染时间仍然有限。现代图形硬件通常在渲染过程中涉及很多优化,例如抗锯齿处理,这非常耗费计算资源,如果您不需要高质量的图形,当然可以禁用此选项。这样可以节省您一些时间。

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


1
查看有关为什么重播系统不可行的讨论。该截屏程序不适用于任何特定游戏。
someguy 2012年

10

我编写了一个类,该类实现了用于屏幕捕获的GDI方法。我也想要额外的速度,因此,在发现DirectX方法(通过GetFrontBuffer)后,我尝试了一下,期望它更快。

我很沮丧地发现GDI的执行速度提高了约2.5倍。经过100次尝试捕获我的双监视器显示后,GDI实施平均每个屏幕捕获为0.65s,而DirectX方法平均为1.72s。因此,根据我的测试,GDI绝对比GetFrontBuffer快。

我无法让Brandrew的代码通过GetRenderTargetData测试DirectX。屏幕拷贝纯粹是黑色的。但是,它可以快速复制该空白屏幕!我将继续对此进行修改,并希望获得一个工作版本以查看实际结果。


感谢您的信息。我尚未测试Brandrew的代码,但我知道采用这种GetRenderTargetData方法是可行的。完成申请后,也许我会自己写答案。或者,一旦一切正常,您可以更新自己的。
someguy 2011年

每个屏幕捕获0.65?良好的GDI实施(将设备保持在周围等)应该可以在现代计算机上轻松地以1920x1200的速度执行30fps。
Christopher Oezbek'5

我认为GDI渲染的图像质量肯定比DX差
凌晨

我已经使用SlimDX在C#中执行了该测试,令人惊讶的是,发现了相同的结果。也许这与以下事实有关:使用SlimDX,必须为每次帧更新创建一个新的流和一个新的位图,而不是一次创建,倒退并覆盖相同的位置。
Cesar 2013年

只是澄清一下:实际上,“相同的结果”是指GDI更快-正如@Christopher所提到的,30fps +的确非常可行,并且仍然留有大量备用CPU。
Cesar 2013年

8

我已经掌握了几件事:尽管我不知道有OSS,但显然使用“镜像驱动程序”很快。

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

同样显然使用一些StretchRect的卷积比BitBlt更快

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

您提到的一种方式(将片段插入D3D dll中)可能是D3D应用程序的唯一方法,但不适用于Windows XP桌面捕获。因此,现在我只希望普通桌面窗口具有相当于速度的速度...有人吗?

(我认为使用Aero可能可以使用类似fraps的钩子,但是XP用户将很不走运)。

显然还改变了屏幕位深度和/或禁用了硬件加速。可能会帮助(和/或禁用航空)。

https://github.com/rdp/screen-capture-recorder-program包括一个相当快的基于BitBlt的捕获实用程序,以及作为其安​​装一部分的基准测试程序,可以让您对BitBlt速度进行基准测试以优化它们。

VirtualDub还具有一个“ opengl”屏幕捕获模块,据说该模块非常快速,可以执行更改检测之类的操作http://www.virtualdub.org/blog/pivot/entry.php?id=290


我想知道,使用您的“屏幕捕获记录器”或自己使用BitBlt会更快吗?您的项目中是否有一些优化?
blez 2015年

如果您自己使用Bitblt,则可能会避免额外的memcpy(memcpy通常不是最大的瓶颈,尽管它确实增加了一些时间-我在这里只是针对其基准测试实用程序提及它,但是如果您需要某些东西或DirectShow,那么它很好)
rogerdpack 2015年

8

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

旧答案(C#):
可以使用System.Drawing.Graphics.Copy,但是速度不是很快。

我写的一个示例项目正是这样做的: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捕获屏幕为视频?


2
啊,我忘了提一下我正在用C(可能是C ++)编程,并且不打算使用.NET。非常抱歉 :/。
someguy 2011年

我已经知道BitBlt(GDI)。不过,我将研究Direct3D。谢谢!
someguy 2011年

几周前,我一直在研究这个问题,但是还没有实现它。Direct3D是!!方式!比使用GDI +的C#内置方法更快。
Tedd Hansen

我已经更新了我的原始帖子。根据链接,DirectX在必须调用GetFrontBufferData()时很慢。录制游戏画面时有什么要考虑的吗?您能为我介绍一下这个吗?
someguy 2011年

1
GDI速度很慢,因此它不适用于问题域,DirectX或OpenGL是唯一明智的建议。

6

在Windows 8中,Microsoft引入了Windows桌面复制API。这是官方推荐的方法。屏幕广播的一个不错的功能是它可以检测窗口的移动,因此当窗口四处移动时,您可以传输块增量,而不是原始像素。此外,它还告诉您哪些矩形从一帧到下一帧已更改。

Microsoft示例代码非常复杂,但是API实际上很简单且易于使用。我整理了一个示例项目,它比官方示例要简单得多:

https://github.com/bmharper/WindowsDesktopDuplicationSample

文件:https//docs.microsoft.com/en-gb/windows/desktop/direct3ddxgi/desktop-dup-api

Microsoft官方示例代码:https : //code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a


Github项目在Windows 10上完美运行,并通过Web视频和Resident Evil 7进行了测试。最好的是CPU负载随图形升级速率而变化。
jw_

一旦启动GPU-Z,该程序就会停止抓取屏幕。
MarinoŠimić

5

您可以尝试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窗口
  • Winlogon
  • DirectShowOverlay

1
那是...很多低级的钩子。如果您具有管理员权限,则捕获速度惊人。
toster-cx

我发现winrobot非常强大且流畅。
ashishgupta_mca

我研究了WinRobot代码,却没有看到任何具有开创性的wrt屏幕截图:它使用的是相同的CreateCompatibleDC..BitBlt。除非在服务上下文中执行此操作时有些不可思议?
谢赫

@shekh:您的研究太肤浅了。该代码使用IDirectDrawSurface7-> BltFast()将屏幕从屏幕绘图表面复制到复制DD表面,然后使用Filemapping复制图像。这非常复杂,因为代码在无法轻松访问桌面的服务中运行。
Elmue

2

我自己使用DirectX来做,并认为它的速度与您希望的一样快。我没有快速的代码示例,但是我发现应该很有用。directx11版本应该相差不大,directx9可能略有不同,但这就是方法


2

我意识到以下建议并不能解决您的问题,但是我发现捕获快速变化的DirectX视图的最简单方法是将摄像机插入视频卡的S-video端口,并以电影。然后将视频从相机传输回计算机上的MPG,WMV,AVI等文件。


2

屏幕录像可以使用VLC APIC#中完成。我已经完成了一个示例程序来演示这一点。它使用LibVLCSharpVideoLAN.LibVLC.Windows库。使用此跨平台API,您可以实现与视频渲染相关的更多功能。

有关API文档,请参见:LibVLCSharp 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();
            }
        }
    }
}

1
可能与谁有关:请注意libvlc是根据GPL发布的。如果您不打算在GPL下发布代码,则不要使用libvlc。
塞巴斯蒂安·卡伯特

这不是很准确,LibVLC是LGPL-它在2011年获得了许可。VLC应用程序本身仍然是GPL:videolan.org/press/lgpl-libvlc.html
Andrew

0

DXGI桌面捕捉

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

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

应用程序可以做什么

  • 如果您有多个台式机显示器,则可以选择。
  • 调整捕获的桌面图像的大小。
  • 选择不同的缩放模式。
  • 您可以在输出图像中显示或隐藏鼠标图标。
  • 您可以旋转输出图片的图像,或将其保留为默认值。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.