窗口/屏幕截图适用于截图、批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面、远程桌面这些场景是如何实现画面录制的。

常见的屏幕画面时时采集方案,主要有GDI、WGC、DXGI。

GDI

GDI(Graphics Device Interface)就是使用user32下WindowsAPI来实现,是 Windows 操作系统中最早、最基础的图形设备接口,满足所有windows平台。屏幕/窗口截图可以详见: .NET 窗口/屏幕截图 - 唐宋元明清2188 - 博客园 (cnblogs.com)

GDI性能不太好,尤其是针对高帧率及高分辨率需求,达到每秒20帧以上的截取,占用CPU就有点高了。另外GDI不能获取鼠标,需要在截取的图像中把鼠标画上去。

所以GDI使用很方便、不依赖GPU,对性能要求不高的截图场景建议直接使用这个方案。

WGC

Windows Graphics Capture ,是Win10引入的一种新截取屏幕以及截取窗口内容的机制 Screen capture - UWP applications | Microsoft Learn

WinRT提供接口访问,Csproj属性中添加:<UseWinRT>true</UseWinRT>

截图代码实现示例:

 1     public WgcCapture(IntPtr hWnd, CaptureType captureType)
2 {
3 if (!GraphicsCaptureSession.IsSupported())
4 {
5 throw new Exception("不支Windows Graphics Capture API");
6 }
7 var item = captureType == CaptureType.Screen ? CaptureUtils.CreateItemForMonitor(hWnd) : CaptureUtils.CreateItemForWindow(hWnd);
8 CaptureSize = new Size(item.Size.Width, item.Size.Height);
9
10 var d3dDevice = Direct3D11Utils.CreateDevice(false);
11 _device = Direct3D11Utils.CreateSharpDxDevice(d3dDevice);
12 _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(d3dDevice, pixelFormat: DirectXPixelFormat.B8G8R8A8UIntNormalized, numberOfBuffers: 1, item.Size);
13 _desktopImageTexture = CreateTexture2D(_device, item.Size);
14 _framePool.FrameArrived += OnFrameArrived;
15 item.Closed += (i, _) =>
16 {
17 _framePool.FrameArrived -= OnFrameArrived;
18 StopCapture();
19 ItemClosed?.Invoke(this, i);
20 };
21 _session = _framePool.CreateCaptureSession(item);
22 }
23 private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
24 {
25 try
26 {
27 using var frame = _framePool.TryGetNextFrame();
28 if (frame == null) return;
29 var data = CopyFrameToBytes(frame);
30 var captureFrame = new CaptureFrame(CaptureSize, data);
31 FrameArrived?.Invoke(this, captureFrame);
32 }
33 catch (Exception)
34 {
35 // ignored
36 }
37 }

Windows.GraphicsCapture API负责从屏幕实际抓取像素, GraphicsCaptureItem 类表示所捕获的窗口或显示, GraphicsCaptureSession 用于启动和停止捕获操作, Direct3D11CaptureFramePool 类维护要将屏幕内容复制到其中的帧的缓冲区。

WGC截图流程:
  1. 创建捕捉项:使用 CreateCaptureItemForMonitor 或 CreateCaptureItemForWindow 来创建捕捉项。
  2. 创建D3D11设备和上下文:调用 D3D11CreateDevice 创建 Direct3D 11 设备和设备上下文。这里虽然没有使用DXGI截图,但引用了DXGI的设备类型
  3. 转换为 Direct3D 设备:将 D3D11 设备转换为SharpDX Direct3D 设备对象。
  4. 创建帧池和会话:使用 Direct3D11CaptureFramePool 和 GraphicsCaptureSession。
  5. 开始捕捉:调用 StartCapture 开始会话,并注册帧到达事件。
  6. 处理帧:在帧到达事件中处理捕获的帧

我们这里是使用比较成熟的SharpDX来处理Direct3D,引用如下Nuget版本

<PackageReference Include="SharpDX" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.DXGI" Version="4.2.0" />

获取到截取的D3D对象帧,帧画面转数据流:

 1     private byte[] CopyFrameToBytes(Direct3D11CaptureFrame frame)
2 {
3 using var bitmap = Direct3D11Utils.CreateSharpDxTexture2D(frame.Surface);
4 _device.ImmediateContext.CopyResource(bitmap, _desktopImageTexture);
5 // 将Texture2D资源映射到CPU内存
6 var mappedResource = _device.ImmediateContext.MapSubresource(_desktopImageTexture, 0, MapMode.Read, MapFlags.None);
7 //Bgra32
8 var bytesPerPixel = 4;
9 var width = _desktopImageTexture.Description.Width;
10 var height = _desktopImageTexture.Description.Height;
11 using var inputRgbaMat = new Mat(height, width, MatType.CV_8UC4, mappedResource.DataPointer, mappedResource.RowPitch);
12
13 var data = new byte[CaptureSize.Width * CaptureSize.Height * bytesPerPixel];
14 if (CaptureSize.Width != width || CaptureSize.Height != height)
15 {
16 var size = new OpenCvSharp.Size(CaptureSize.Width, CaptureSize.Height);
17 Cv2.Resize(inputRgbaMat, inputRgbaMat, size, interpolation: InterpolationFlags.Linear);
18 }
19 var sourceSize = new Size(frame.ContentSize.Width, frame.ContentSize.Height);
20 if (CaptureSize == sourceSize)
21 {
22 var rowPitch = mappedResource.RowPitch;
23 for (var y = 0; y < height; y++)
24 {
25 var srcRow = inputRgbaMat.Data + y * rowPitch;
26 var destRowOffset = y * width * bytesPerPixel;
27 Marshal.Copy(srcRow, data, destRowOffset, width * bytesPerPixel);
28 }
29 }
30 else
31 {
32 Marshal.Copy(inputRgbaMat.Data, data, 0, data.Length);
33 }
34
35 _device.ImmediateContext.UnmapSubresource(_desktopImageTexture, 0);
36 return data;
37 }

将Surface对象转换为获取 SharpDX的Texture2D,映射到CPU以内存拷贝方式输出图像字节数据。

上面默认是输出三通道8位的Bgr24,如果是四通道Bgra32可以按如下从内存拷贝:

1 using var inputRgbMat = new Mat();
2 Cv2.CvtColor(inputRgbaMat, inputRgbMat, ColorConversionCodes.BGRA2BGR);
3 Marshal.Copy(inputRgbMat.Data, data, 0, data.Length);

拿到字节数据,就可以保存本地或者界面展示了 。

屏幕截图Demo显示:

 1     private void CaptureButton_OnClick(object sender, RoutedEventArgs e)
2 {
3 var monitorHandle = MonitorUtils.GetMonitors().First().MonitorHandle;
4 var wgcCapture = new WgcCapture(monitorHandle, CaptureType.Screen);
5 wgcCapture.FrameArrived += WgcCapture_FrameArrived;
6 wgcCapture.StartCapture();
7 }
8
9 private void WgcCapture_FrameArrived(object? sender, CaptureFrame e)
10 {
11 Application.Current.Dispatcher.Invoke(() =>
12 {
13 var stride = e.Size.Width * 4; // 4 bytes per pixel in BGRA format
14 var bitmap = BitmapSource.Create(e.Size.Width, e.Size.Height, 96, 96, PixelFormats.Bgra32, null, e.Data, stride);
15 bitmap.Freeze();
16 CaptureImage.Source = bitmap;
17 });
18 }

WGC利用了现代图形硬件和操作系统特性、能够提供高性能和低延迟的屏幕捕抓,适用于实时性比较高的场景如屏幕录制、视讯会议等应用。

更多的,可以参考官网屏幕捕获到视频 - UWP applications | Microsoft Learn。也可以浏览、运行我的Demo:kybs00/CaptureImageDemo (github.com)

DXGI

全名DirectX Graphics Infrastructure。从Win8开始,微软引入了一套新的接口Desktop Duplication API,而由于Desktop Duplication API是通过DXGI来提供桌面图像的,速度非常快。

DXGI使用GPU,所以cpu占用率很低,性能很高。DXGI官网文档:DXGI - Win32 apps | Microsoft Learn

因为DXGI也是使用DirectX,所以很多接口与WGC差不多。也就是通过D3D,各种QueryInterface,各种Enum,核心方法是AcquireNextFrame

它有个缺点,没办法捕获窗口内容。所以视讯会议共享窗口,是无法通过DXGI实现

我们看看Demo调用代码,

 1     private void CaptureButton_OnClick(object sender, RoutedEventArgs e)
2 {
3 var monitorDxgiCapture = new MonitorDxgiCapture();
4 monitorDxgiCapture.FrameArrived += WgcCapture_FrameArrived;
5 monitorDxgiCapture.StartCapture();
6 }
7
8 private void WgcCapture_FrameArrived(object? sender, CaptureFrame e)
9 {
10 Application.Current?.Dispatcher.Invoke(() =>
11 {
12 var stride = e.Size.Width * 4; // 4 bytes per pixel in BGRA format
13 var bitmap = BitmapSource.Create(e.Size.Width, e.Size.Height, 96, 96, PixelFormats.Bgra32, null, e.Data, stride);
14
15 bitmap.Freeze();
16 CaptureImage.Source = bitmap;
17 });
18 }

捕获画面帧数据:

 1     [HandleProcessCorruptedStateExceptions]
2 private CaptureFrame CaptureFrame()
3 {
4 try
5 {
6 var data = new byte[CaptureSize.Width * CaptureSize.Height * 4];
7 var result = _mDeskDupl.TryAcquireNextFrame(TimeOut, out _, out var desktopResource);
8 if (result.Failure) return null;
9
10 using var tempTexture = desktopResource?.QueryInterface<Texture2D>();
11 _mDevice.ImmediateContext.CopyResource(tempTexture, _desktopImageTexture); //拷贝图像纹理:GPU硬件加速的纹理复制
12 desktopResource?.Dispose();
13
14 var desktopSource = _mDevice.ImmediateContext.MapSubresource(_desktopImageTexture, 0, MapMode.Read, MapFlags.None);
15 using var inputRgbaMat = new Mat(_screenSize.Height, _screenSize.Width, MatType.CV_8UC4, desktopSource.DataPointer);
16 if (CaptureSize.Width != _screenSize.Width || CaptureSize.Height != _screenSize.Height)
17 {
18 var size = new OpenCvSharp.Size(CaptureSize.Width, CaptureSize.Height);
19 Cv2.Resize(inputRgbaMat, inputRgbaMat, size, interpolation: InterpolationFlags.Linear);
20 }
21 Marshal.Copy(inputRgbaMat.Data, data, 0, data.Length);
22
23 var captureFrame = new CaptureFrame(CaptureSize, data);
24 _mDevice.ImmediateContext.UnmapSubresource(_desktopImageTexture, 0);
25 //释放帧
26 _mDeskDupl.ReleaseFrame();
27 return captureFrame;
28 }
29 catch (AccessViolationException)
30 {
31 return null;
32 }
33 catch (Exception)
34 {
35 return null;
36 }
37 }

也是使用硬件加速将2D纹理资源拷贝,然后通过内存拷贝输出为字节数据。

1080P的本地录屏、显示,CPU、GPU使用情况如下:

1080P和WGC方案没有明显差别,延时也接近。但4K、8K分辨率下,DXGI方案更优,能够直接管理图形硬件和提供高性能渲染。它是与内核模式驱动程序和系统硬件进行通信的,借用下官网的架构图:

所以在需要极低延迟和高帧率的4K场景中,DXGI能提供必要的性能优化。

上面代码示例,详细Demo见github:kybs00/CaptureImageDemo (github.com)

总结下这三个方案

GDI:适用于所有 Windows 版本,但性能较低。

DXGI:适用于高性能需求,复杂度和学习成本较高,并且只适用于屏幕录制、不支持窗口。

WGC(Windows Graphics Capture):Win10 1803版本以上,高性能和低延迟,屏幕及窗口均支持。

录制主要是录屏、直播、远程桌面、视讯会议、传屏等场景。录制屏幕/窗口建议优先使用WGC,然后用DXGI兼容win8;如果仅录制屏幕且高分辨率、高帧率场景,建议优先DXGI

.NET 屏幕录制的更多相关文章

  1. ffmpeg 屏幕录制 so easy....

    linux Linux下使用FFmpeg进行屏幕录制相对比较方便,可以使用x11grab,使用如下的命令: ffmpeg -f x11grab -s 1600x900 -r 50 -vcodec li ...

  2. Mac与iPhone屏幕录制

    1. Mac电脑屏幕录制 1.1 文件->新建屏幕录制   1.2 点击红色按钮   1.3 截取需要录制的屏幕部分,点击开始录制   1.4 点击工具栏的停止按钮,停止录制   1.5 然后会 ...

  3. C# 与 Microsoft Expression Encoder实现屏幕录制

    在日常开发中,我们会经常遇到屏幕录制的需求.在C#中可以通过Expression Encoder的SDK实现这样的需求.首先需要下载Expression Encoder SDK,实现代码: priva ...

  4. LICEcap – 灵活好用,GIF 屏幕录制工具

    LICEcap – 灵活好用,GIF 屏幕录制工具 http://www.appinn.com/licecap/

  5. 团队交流合作简单解决方案:TeamViewer远程控制&会议演示 + HyperCam屏幕录制(免费)

    一. 教程摘要 做开发,团队合作是少不了的.而在合作中,有一部分是花在交流讨论上,其中包括初期的任务分配,成员的进度汇报,以及资源和心得分享等.该教程介绍了两个免费的软件,搭配起来,适合人数不超过25 ...

  6. LICEcap GIF 屏幕录制工具

    LICEcap 是一款屏幕录制工具,支持导出git动画图片格式,简单好用.大小只有几百KB   运行之后,可以随意调整大小,右下角有开始/停止按钮.      压缩包:http://files.cnb ...

  7. 在MAC系统上进行屏幕录制

    最近打算将一些软件操作过程进行屏幕录制进行视频分享,所以寻思着找一块能在MAC上使用的屏幕录制软件.google了一番,没想到MAC系统自带的QuickTime Player已经内置屏幕录像功能,而且 ...

  8. LICEcap 简洁易用的动画屏幕录制软件

    LICEcap 简洁易用的动画屏幕录制软件 LICEcap 捕捉屏幕的区域并保存为gif动画(便于网络发布)或lcf格式(见下). LICEcap 直观易用,功能灵活,支持 Windows 和 OSX ...

  9. 屏幕录制:SCR Screen Recorder Pro v0.14.3汉化破解版

    应用概览 <ignore_js_op> 软件名称:屏幕录制:SCR Screen Recorder Pro 软件版本:v0.14.3汉化破解版软件语言:中文软件大小:3.5M软件包名:co ...

  10. 几款屏幕录制软件 ActivePresente

    几款屏幕录制软件,最强大是  ActivePresenter ,免费版, 足以应对我们日常需求.列表如下 支持系统:W-Windows,L-Linux,M-Mac 软件 格式 W L M 免费 说明 ...

随机推荐

  1. JS的JSON.parse问题

    这个问题,已经有非常多人说过,而且由来已久. 大家都提供了不少的解决方法,但是都不够彻底. 一)现在是什么情况 1.使用SpringMvc+ModelAndView+jsp传递值 由于业务需要,通过m ...

  2. WAV音频文件按秒切片段

    wav音频文件按秒切片段 import wave def split_wav_by_seconds(input_file, output_file, start_second, end_second) ...

  3. Linux创建新用户时遇到的问题记录

    创建新用户命令: useradd -d "/home/guest" -m -s "/bin/bash" guest 报错: useradd: cannot op ...

  4. Power BI进阶秘籍,干货满满!如何将度量值转化为切片器(动态切换分析指标),实操指南来了!

    Power BI进阶秘籍,干货满满!如何将度量值转化为切片器(动态切换分析指标),实操指南来了!   想要在Power BI中让度量值也能像维度一样灵活筛选?没问题,这里就为你揭秘如何将度量值转化为切 ...

  5. VS图片

  6. 韦东山freeRTOS系列教程之【第九章】任务通知(Task Notifications)

    目录 系列教程总目录 概述 9.1 任务通知的特性 9.1.1 优势及限制 9.1.2 通知状态和通知值 9.2 任务通知的使用 9.2.1 两类函数 9.2.2 xTaskNotifyGive/ul ...

  7. JavaSE 常见时间日期

    java.util包提供了Date类来封装当前的⽇期和时间 构造函数 //当前时间 Date() //从1970年1⽉1⽇起的毫秒数作为参数 Date(long millisec) 常见方法 //返回 ...

  8. [oeasy]python0024_unix时间戳_epoch_localtime_asctime_PosixTime_unix纪年法

    输出时间回忆上次内容 通过搜索 我们学会 import 导入 time 了 完整写法为 asc_time = time.asctime( time.localtime( time.time())) 内 ...

  9. 题解:P8144 [JRKSJ R4] BBWWBB

    思路 分析题意可得,白方必定不会胜利,只能尽量让游戏无限进行下去.那么我们只考虑黑方能否胜利. 若想让戏能无限进行下去,必须满足以下条件. 白方先手. 若黑方先手必然可以吃掉一个白方,白方仅有一个棋子 ...

  10. CF1929B Sasha and the Drawing 题解

    CF1929B 题意 给定一个 \(n\times n\) 的正方形,已知正方形最多有 \(4\times n-2\) 条对角线,要求要有至少 \(k\) 条对角线经过至少一块黑色方格,求至少要将几条 ...