.NET 屏幕录制
窗口/屏幕截图适用于截图、批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面、远程桌面这些场景是如何实现画面录制的。
常见的屏幕画面时时采集方案,主要有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 类维护要将屏幕内容复制到其中的帧的缓冲区。
- 创建捕捉项:使用 CreateCaptureItemForMonitor 或 CreateCaptureItemForWindow 来创建捕捉项。
 - 创建D3D11设备和上下文:调用 D3D11CreateDevice 创建 Direct3D 11 设备和设备上下文。这里虽然没有使用DXGI截图,但引用了DXGI的设备类型
 - 转换为 Direct3D 设备:将 D3D11 设备转换为SharpDX Direct3D 设备对象。
 - 创建帧池和会话:使用 Direct3D11CaptureFramePool 和 GraphicsCaptureSession。
 - 开始捕捉:调用 StartCapture 开始会话,并注册帧到达事件。
 - 处理帧:在帧到达事件中处理捕获的帧
 
我们这里是使用比较成熟的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 屏幕录制的更多相关文章
- ffmpeg 屏幕录制 so easy....
		
linux Linux下使用FFmpeg进行屏幕录制相对比较方便,可以使用x11grab,使用如下的命令: ffmpeg -f x11grab -s 1600x900 -r 50 -vcodec li ...
 - Mac与iPhone屏幕录制
		
1. Mac电脑屏幕录制 1.1 文件->新建屏幕录制 1.2 点击红色按钮 1.3 截取需要录制的屏幕部分,点击开始录制 1.4 点击工具栏的停止按钮,停止录制 1.5 然后会 ...
 - C# 与 Microsoft Expression Encoder实现屏幕录制
		
在日常开发中,我们会经常遇到屏幕录制的需求.在C#中可以通过Expression Encoder的SDK实现这样的需求.首先需要下载Expression Encoder SDK,实现代码: priva ...
 - LICEcap – 灵活好用,GIF 屏幕录制工具
		
LICEcap – 灵活好用,GIF 屏幕录制工具 http://www.appinn.com/licecap/
 - 团队交流合作简单解决方案:TeamViewer远程控制&会议演示 + HyperCam屏幕录制(免费)
		
一. 教程摘要 做开发,团队合作是少不了的.而在合作中,有一部分是花在交流讨论上,其中包括初期的任务分配,成员的进度汇报,以及资源和心得分享等.该教程介绍了两个免费的软件,搭配起来,适合人数不超过25 ...
 - LICEcap GIF 屏幕录制工具
		
LICEcap 是一款屏幕录制工具,支持导出git动画图片格式,简单好用.大小只有几百KB 运行之后,可以随意调整大小,右下角有开始/停止按钮. 压缩包:http://files.cnb ...
 - 在MAC系统上进行屏幕录制
		
最近打算将一些软件操作过程进行屏幕录制进行视频分享,所以寻思着找一块能在MAC上使用的屏幕录制软件.google了一番,没想到MAC系统自带的QuickTime Player已经内置屏幕录像功能,而且 ...
 - LICEcap 简洁易用的动画屏幕录制软件
		
LICEcap 简洁易用的动画屏幕录制软件 LICEcap 捕捉屏幕的区域并保存为gif动画(便于网络发布)或lcf格式(见下). LICEcap 直观易用,功能灵活,支持 Windows 和 OSX ...
 - 屏幕录制:SCR Screen Recorder Pro v0.14.3汉化破解版
		
应用概览 <ignore_js_op> 软件名称:屏幕录制:SCR Screen Recorder Pro 软件版本:v0.14.3汉化破解版软件语言:中文软件大小:3.5M软件包名:co ...
 - 几款屏幕录制软件 ActivePresente
		
几款屏幕录制软件,最强大是 ActivePresenter ,免费版, 足以应对我们日常需求.列表如下 支持系统:W-Windows,L-Linux,M-Mac 软件 格式 W L M 免费 说明 ...
 
随机推荐
- Java基础:throw和throws的详解
			
总结来说,throw是用来抛出一个具体的异常实例,而throws是用来声明方法可能会抛出哪些类型的异常,是对调用者的一种通知和要求. 1. throw 作用: throw关键字用于在方法体内实际抛出一 ...
 - python正则表达式替换所有内容并同时保留找到的内容
			
除了一些专业的工具,例如ue,大部分编程语言的函数包都挺让人迷惑的,例如Java,js. 因为的确有许多功能是很常用的,但是他们又不提供,非得要程序员自己去实现,或者是利用三方的包. 到底是什么理由了 ...
 - 打开ftp服务器上的文件夹时发生错误,请检查是否有权限访问该文件夹
			
产生这种现象有很多方面的原因 如果你能用命令行的方式访问ftp服务器,但是不能用资源管理器访问,那么请看下去. 1.打开IE浏览器,点击设置 2.点击Internet选项,进入高级 3.取消勾选 大功 ...
 - /etc/shadow文件破解,密码破解,md5,SHA256,SHA512破解
			
环境 Kali系统 John the Ripper密码破解者 shadow文件解析 文件的格式为: {用户名}:{加密后的口令密码}:{口令最后修改时间距原点(1970-1-1)的天数}:{口令最小修 ...
 - Android自动化无障碍服务开源库-Assists v3.0.0
			
Assists v3.0.0 Android无障碍服务(AccessibilityService)开发框架,快速开发复杂自动化任务.远程协助.监听等 Android无障碍服务能做什么 利用Androi ...
 - 用Tensorboard在VScode Remote ssh中显示图像
			
可以用Tensorboard在vscode的ssh连接中显示plot出的图像. 具体方法如下: from torch.utils.tensorboard import SummaryWriter wr ...
 - UNR #7 Day2 T1 火星式选拔题解
			
放一个比赛链接 先考虑打完暴力后 \(k = 1\) 的特殊性质. 当队列容量为 \(1\) 时,队中的人 \(i\) 会被第一个满足 \(i \leq j\) 且 \(b_i \leq a_j\) ...
 - wireshark查看https通讯
			
前言 https在原有的http基础上增加了了一个TLS/SSL层,https的通讯过程是加密的,如果想用wireshark仔细分析TLS/SSL层,需要借助服务器证书公私钥或者用浏览器截取密钥: 接 ...
 - Java开发工具和历史版本
			
eclipse 的历史版本: 版本代号 平台版本 主要版本发行日期 SR1发行日期 SR2发行日期 SR3发行日期 代号名称 N/A 3.0 2004年6月21日 [2] N/A N/A N/A N ...
 - IDEA新手使用教程之使用技巧总结【详解】
			
IDEA是一款功能强悍.非常好用的Java开发工具,近几年编程开发人员对IDEA情有独钟. 一.IDEA的下载 IDEA下载地址:https://www.jetbrains.com/idea/down ...