Windows上最快的屏幕捕获方法

时间:2011-02-21 17:22:51

标签: c++ c windows

我想为Windows平台编写一个截屏程序,但不确定如何捕获屏幕。我所知道的唯一方法就是使用GDI,但我很好奇是否还有其他方法可以解决这个问题,如果有的话,会产生最少的开销?速度是首要任务。

屏幕录像节目将用于录制游戏镜头,但是,如果这确实缩小了选项范围,我仍然可以接受任何其他超出此范围的建议。毕竟知识还不错。

修改:我看到了这篇文章:Various methods for capturing the screen。它向我介绍了Windows Media API的实现方式以及DirectX的实现方式。它在结论中提到,禁用硬件加速可以极大地提高捕获应用程序的性能。我很好奇为什么会这样。任何人都可以为我填补遗失的空白吗?

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

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

15 个答案:

答案 0 :(得分:55)

这是我用来收集单帧的内容,但如果你修改它并保持两个目标一直打开,那么你可以使用文件名的静态计数器将它“流”到磁盘。 - 我不记得我在哪里发现了这个,但它已被修改,感谢无论谁!

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

答案 1 :(得分:27)

编辑:我可以看到它在您的第一个编辑链接下列为“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"方法中实现了类似的功能,但使用hDest创建目标上下文CreateCompatibleDC时,IDXGISurface1使用{{3}} },适用于DirectX 10+。如果没有对此的支持,则会回退到CreateCompatibleDC

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

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

答案 2 :(得分:17)

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

尊重与绩效相关的问题,

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

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

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

答案 3 :(得分:15)

我使用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 ) ;

要执行此操作,您应该使用

创建swapbuffer
d3dpps.SwapEffect = D3DSWAPEFFECT_COPY ; // for screenshots.

(所以你保证在拍摄截图之前不会破坏后备缓冲区。)

答案 4 :(得分:10)

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

  1. 为什么DX更快? 在DX(或图形世界)中,应用了一种称为双缓冲渲染的更成熟的方法,其中存在两个缓冲区,当前缓冲区存在于硬件时,您也可以渲染到另一个缓冲区,然后在帧1之后完成渲染后,系统交换到另一个缓冲区(将其锁定以呈现给硬件,并释放前一个缓冲区),这样就可以大大提高渲染效率。
  2. 为什么更快地降低硬件加速? 虽然使用双缓冲区渲染,但FPS得到了改进,但渲染时间仍然有限。现代图形硬件在渲染过程中通常需要进行大量优化,例如抗锯齿,这是非常耗费计算的,如果你不需要高质量的图形,当然你可以禁用这个选项。这样可以节省一些时间。
  3. 我认为你真正需要的是一个重播系统,我完全同意人们所讨论的内容。

答案 5 :(得分:9)

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

我很惊讶地发现GDI的速度提高了约2.5倍。在100次试验捕获我的双显示器显示后,GDI实现平均每屏捕获0.65秒,而DirectX方法平均为1.72秒。因此,根据我的测试,GDI肯定比GetFrontBuffer快。

我无法让Brandrew的代码通过GetRenderTargetData测试DirectX。屏幕副本纯粹是黑色的。但是,它可以超快地复制那个空白屏幕!我会继续修补它,并希望得到一个有效的版本,以便从中看到真实的结果。

答案 6 :(得分:8)

对于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?

答案 7 :(得分:8)

我已经能够收集到的一些东西:显然使用“镜像驱动程序”虽然我不知道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的那个)可能是D3D应用程序的唯一方法,但不适用于Windows XP桌面捕获。所以现在我只是希望普通的桌面窗口有一个相当于速度的fraps ...任何人?

(我认为使用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

答案 8 :(得分: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

答案 9 :(得分:3)

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

答案 10 :(得分:3)

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

答案 11 :(得分:2)

在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

答案 12 :(得分:2)

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

答案 13 :(得分:1)

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

答案 14 :(得分:0)

DXGI Desktop Capture

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

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

应用程序可以做什么

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