【问题标题】:Fastest method of screen capturing on WindowsWindows 上最快的屏幕捕获方法
【发布时间】:2011-07-01 10:40:38
【问题描述】:

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

截屏程序将用于录制游戏画面,但如果这确实缩小了选项范围,我仍然愿意接受超出此范围的任何其他建议。毕竟,知识并不坏。

编辑:我看到了这篇文章:Various methods for capturing the screen。它向我介绍了 Windows Media API 的执行方式和 DirectX 的执行方式。它在结论中提到,禁用硬件加速可以显着提高捕获应用程序的性能。我很好奇这是为什么。有谁能帮我填一下空缺吗?

编辑:我读到 Camtasia 等截屏程序使用自己的捕获驱动程序。有人可以给我一个深入的解释它是如何工作的,为什么它更快?我可能还需要有关实施类似内容的指导,但我确信无论如何都有现有的文档。

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

【问题讨论】:

  • 您是否考虑过使用replay system而不是以图形方式记录屏幕内容?
  • 你不必挂任何东西。您只需编写输入事件,这样它们就不会直接控制游戏,而是调用其他函数。例如,如果玩家按下左键,您不会简单地减少玩家 x 的位置。相反,您调用一个函数,例如MovePlayerLeft()。您还可以记录按键和其他输入的时间和持续时间。然后,当您处于播放模式时,您只需忽略输入,而是读取记录的数据。如果在数据中看到左键按下,则调用 MovePlayerLeft()
  • @PigBen 这将是一个用于录制游戏画面的通用应用程序。这不是针对特定游戏的。据我所知,有人按下左键可能意味着向右移动。此外,您还没有考虑不受用户影响的事件。 '那么渲染呢?
  • @someguy 好吧,我猜你正在做一些更激烈的事情,我已经使用上述方法添加了一个例程,以在 30fps 左右的游戏中顺利保存重播 AVI。我使用 Windows API 制作了一个多显示器屏幕录像机,用于“劳动力优化”,但即使在目标 4fps 下也表现不佳。
  • UltraVNC 的存储库站点上有一个用于 windows 的开源镜像驱动程序 ultravnc.svn.sourceforge.net/viewvc/ultravnc/…

标签: c++ c windows


【解决方案1】:

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

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

【讨论】:

  • 谢谢。不久前我听说过这种方法,据说它比从前端缓冲区读取要快。你真的这样做了吗?它是否正常工作?
  • 前端缓冲区的问题是访问权限之一,即尝试复制当前正在呈现的纯文本会“中断”副本。它对我来说很好用,而且会吃掉我的硬盘!
  • @bobobobo 我不知道它会如何运作,但我正在考虑使用像 Huffyuv 这样的东西。编辑:或者让用户从可用的 directshow 过滤器中进行选择。
  • 我只是无法让它工作...... DirectX 在 GetRenderTargetData 部分吐出一些无效调用。显然,您创建设备的方式非常重要。
  • downvote,它仅适用于您自己的应用程序,因此不能用于录制通用程序
【解决方案2】:

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

来自this 评论(我没有这样做的经验,我只是在引用有经验的人):

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" 方法的一部分,尽管他们没有使用CreateCompatibleDC 创建目标上下文hDest,而是使用IDXGISurface1,它适用于DirectX 10+。如果没有对此的支持,他们会退回到CreateCompatibleDC

要将其更改为使用特定应用程序,您需要将第一行更改为GetDC(game),其中game是游戏窗口的句柄,然后设置游戏窗口的右侧heightwidth窗户也是。

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

【讨论】:

  • msdn.microsoft.com/en-us/library/dd183370%28VS.85%29.aspx 摘录:如果源设备上下文和目标设备上下文的颜色格式不匹配,BitBlt 函数会将源颜色格式转换为目标格式。
  • 有一些证据可以证明这一点会很好。您是否在某处发布过性能比较,或者看到过准确的比较?
  • 尝试分析它。正如我在帖子中所说,我指的是有 GDI 经验的人。如果它泄漏内存,并且您知道如何修复它,请编辑帖子以消除泄漏。
  • 我用这种方法得到了大约 5 fps。这是在带有集成显卡的笔记本电脑上进行的,因此我希望台式机带有真正的显卡会更好,但是仍然很慢。
  • @Timmmm 我添加了对 OBS 实现方式的参考。希望这可能会为您加快速度。
【解决方案3】:

我为 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

【讨论】:

    【解决方案4】:

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

    IDirect3DSurface9 *surface ; //GetBackBuffer idirect3ddevice9->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface) ; // 保存表面 D3DXSaveSurfaceToFileA("filename.png", D3DXIFF_PNG, 表面, NULL, NULL ) ; 安全释放(表面);

    为此,您应该使用

    创建您的交换缓冲区
    d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.
    

    (所以你保证在你截取屏幕截图之前后台缓冲区没有被破坏)。

    【讨论】:

    • 有道理,谢谢。你知道这和GetRenderTarget有什么区别吗?
    • 这只是获取当前的渲染目标(如果有人在您调用的那一刻渲染到纹理,则可能是另一个屏幕外表面)。
    【解决方案5】:

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

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

    我认为你真正需要的是回放系统,我完全同意人们讨论的内容。

    【讨论】:

    • 查看关于回放系统为何不可行的讨论。截屏程序不适用于任何特定游戏。
    【解决方案6】:

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

    我很沮丧地发现 GDI 的执行速度快了大约 2.5 倍。在 100 次尝试捕获我的双显示器显示后,GDI 实现平均每次屏幕捕获时间为 0.65 秒,而 DirectX 方法平均为 1.72 秒。所以根据我的测试,GDI 肯定比 GetFrontBuffer 快。

    我无法让 Brandrew 的代码通过 GetRenderTargetData 测试 DirectX。屏幕副本出来纯黑。但是,它可以超级快速地复制那个空白屏幕!我会继续修改它,并希望得到一个工作版本来看到它的真实结果。

    【讨论】:

    • 感谢您提供的信息。我还没有测试过 Brandrew 的代码,但我知道采用 GetRenderTargetData 方法是可行的。也许当我完成我的申请时,我会写下我自己的答案。或者,您可以在一切正常后更新您的。
    • 0.65 每个屏幕截图?!一个好的 GDI 实现(保持设备周围等)应该在现代计算机上轻松地在 1920x1200 下达到 30fps。
    • 我认为GDI渲染的图像质量肯定比DX差
    • 澄清一下:实际上,“相同的结果”是指 GDI 更快 - 正如@Christopher 所提到的,30fps+ 非常可行,并且仍然留下大量空闲 CPU。
    • 回想起来,我觉得我必须在我的基准测试中错误地在屏幕抓取之后包含一些后处理。但是,如果是这样,我确信 GDI 和 DX 都包含相同的后处理。所以我仍然觉得这是支持 GDI 作为更快方法的证据。
    【解决方案7】:

    对于 C++,您可以使用:http://www.pinvoke.net/default.aspx/gdi32/BitBlt.html
    然而,这可能不适用于所有类型的 3D 应用程序/视频应用程序。那么this link 可能会更有用,因为它描述了您可以使用的 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/

    这里是一个用于捕获视频的链接:How to capture screen to be video using C# .Net?

    【讨论】:

    • 啊,我忘了说我正在使用 C(可能是 C++)编程,并且不打算使用 .NET。非常抱歉:/.
    • 我已经知道 BitBlt (GDI)。不过,我会研究 Direct3D。谢谢!
    • 几周前我正在研究这个问题,但还没有开始实施它。 Direct3D 是!!方式!比使用 GDI+ 的 C# 内置方法更快。
    • 我已经更新了我原来的帖子。根据链接,DirectX 在必须调用 GetFrontBufferData() 时速度很慢。录制游戏画面时有什么需要考虑的吗?你能帮我解释一下这个吗?
    • GDI 很慢,所以不适合问题域,DirectX 或 OpenGL 将是唯一明智的建议。
    【解决方案8】:

    您需要 Desktop Duplication API(自 Windows 8 起可用)。这是官方推荐的方式,也是 CPU 效率最高的方式。

    它用于截屏的一个不错的功能是它检测窗口移动,因此您可以在窗口移动时传输块增量,而不是原始像素。此外,它还会告诉您哪些矩形发生了变化,从一帧到下一帧。

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

    简化示例代码

    WindowsDesktopDuplicationSample

    微软参考

    Desktop Duplication API

    Official example code(我上面的例子是这个的精简版)

    【讨论】:

    • Github 项目在 Windows 10 上完美运行,并通过网络视频和生化危机 7 进行了测试。最好的是 CPU 负载与图形升级率成比例。
    • 一旦你启动 GPU-Z,这个程序就会停止抓取屏幕。
    【解决方案9】:

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

    Why is RDP so fast compared to other remote control software?

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

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

    您提到的那个(连接到 D3D dll 的 Fraps)可能是 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 会更快吗?您的项目是否有一些优化?
    • 如果你自己使用 Bitblt,你可能会避免额外的 memcpy(memcpy 通常不是最大的瓶颈,虽然它确实增加了一些时间——我只是在这里提到它的基准实用程序,但如果你需要一些东西或直接展示然后它很好)
    【解决方案10】:

    你可以试试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

    【讨论】:

    • 那是……很多低级的钩子。如果您有管理员权限,捕获速度是惊人的。
    • 我发现winrobot非常强大和流畅。
    • 我研究了 WinRobot 代码,但没有看到任何突破性的屏幕截图:它使用相同的 CreateCompatibleDC..BitBlt。除非在服务上下文中执行此操作有什么魔力?
    • @shekh:你的研究太肤浅了。该代码使用 IDirectDrawSurface7->BltFast() 将屏幕从屏幕绘图表面复制到复制 DD 表面,然后使用 Filemapping 复制图像。这是相当复杂的,因为代码运行在您无法轻松访问桌面的服务中。
    【解决方案11】:

    屏幕录制可以在 C# 中使用 VLC API 完成。我已经做了一个示例程序来演示这一点。它使用 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();
                }
            }
        }
    }
    

    【讨论】:

    • 它可能关注的对象:请注意,libvlc 是在 GPL 下发布的。如果你不打算在 GPL 下发布你的代码,那么不要使用 libvlc。
    • 这不太准确,LibVLC 是 LGPL - 它在 2011 年被重新授权。VLC 应用程序本身仍然是 GPL:videolan.org/press/lgpl-libvlc.html
    【解决方案12】:

    这可能不是最快的方法,但它轻巧且易于使用。图像以包含 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;
    }
    

    上面的代码结合了this answerthis answer

    使用示例:

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

    【讨论】:

      【解决方案13】:

      我自己是用directx 来做的,我认为它的速度和你想要的一样快。我没有快速的代码示例,但我发现 this 应该很有用。 directx11 版本应该差别不大,directx9 可能会差一点,但这就是要走的路

      【讨论】:

        【解决方案14】:

        DXGI Desktop Capture

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

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

        应用程序可以做什么

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

        【讨论】:

          【解决方案15】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-11-02
            • 1970-01-01
            相关资源
            最近更新 更多