.NET 窗口/屏幕录制

news/2024/11/17 3:42:21/文章来源:https://www.cnblogs.com/kybs0/p/18330811

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

常见的屏幕画面时时采集方案,主要有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:Win8版本以上,适用于高分辨率高帧率等高性能的需求,并且只支持屏幕录制、不支持窗口。

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

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

 

关键字:录屏、录制窗口、高性能屏幕捕获

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/780498.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET 屏幕录制

窗口/屏幕截图适用于截图、批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面、远程桌面这些场景是如何实现画面录制的。 常见的屏幕画面时时采集方案,主要有GDI、WGC、DXGI。 GDI GDI(Graphics Device Interface)就是使用user32下WindowsAPI来实现…

Kubernetes-POD的QoS

目录背景问题分析进一步排查问题原因Pod的QoS服务质量等级结论 背景 今天开发团队反馈,测试环境中部分业务功能无法正常使用。经过初步排查,发现某个业务Pod在一天内重启了10次,因此需要进一步调查原因。 问题分析 首先,我查看了Pod的日志,发现JVM并未抛出任何错误,服务却…

2024.8.9 鲜花

几张图?推歌:早安大森林 ![](https://baike.baidu.com/pic/%E6%97%A9%E5%AE%89%E5%A4%A7%E6%A3%AE%E6%9E%97/64160919/1/b17eca8065380cd791236bebf51cba345982b2b72b26?fr=lemma&fromModule=lemma_content-image#aid=1&pic=b17eca8065380cd791236bebf51cba345982b2…

赋值运算符和+号的用法(两个练习)

string strName = "kakaxi";string strVillage = "huoyingcun";int iAge = 20;string strEmail = "1287195315@qq.com";decimal deSalary = 2000m;Console.WriteLine("大家好,我叫{0},我今年{1}岁了, 我住在{2},我的邮箱是{3}, 我的工资有足…

中国四大软件外包公司

今天我们来聊聊国内的四大软件外包公司。这些公司不仅在国内市场中占据重要地位,还对全球软件外包行业产生了影响。部分数据来源网络排名,按照职位量、增长速度排名,排名仅供参考,去某家公司一定要多方位参考,比如企查查、脉脉等。要说软件外包这块大蛋糕,谁不想来一口呢…

六、决策树

决策1:如何选择在每个节点上分割什么特征?最大限度地提高纯度(或最小限度地减少不纯)。决策2:什么时候停止拆分?当一个节点是一个单一类时 当拆分一个节点会导致树超过最大的深度 当纯度分数的改进低于一个阈值(获得的信息增益很小小于阈值) 当一个节点中的例子数量低于…

性能测试面试题大曝光,让你如何迅速拿下 offer!

性能测试面试题精选1、 以前做过性能测试么?请结合例子具体说明性能测试的流程 面试考察点:性能测试的流程首选做性能测试的需求分析,明确性能测试的目标、范围、场景和性能指标(如响应时间、吞吐量、并发用户数等)。测试性能测试环境搭建:搭建与生产环境尽可能一致的测试…

[Java并发]ThreadLocal补充

ThreadLocal缺点及解决方案 每个Thread上都有一个threadLocals属性,它是一个ThreadLocalMap,里面存放着一个Entry数组,key是ThreadLocal类型的弱引用,value是对用的值。所有的操作都是基于这个ThreadLocalMap操作的。 但是它有一个局限性,就是不能在父子线程之间传递。 即…

NuminaMath 是如何荣膺首届 AIMO 进步奖的?

今年,Numina 和 Hugging Face 合作角逐 AI 数学奥林匹克 (AI Math Olympiad,AIMO) 的首届进步奖。此次比赛旨在对开放 LLM 进行微调,以使其能解决高中难度的国际数学奥林匹克训练题。我们很高兴向大家报告: 我们的模型 - NuminaMath 7B TIR - 在比赛中脱颖而出,成功解决了私…

【题解】ABC365(A~E)

前四题30min切,然后T5死磕70min+几发小唐错,距离比赛结束还有16s交最后一发,AC了。 目录A. Leap Year题目描述思路代码B. Second Best题目描述思路代码C. Transportation Expenses题目描述思路代码D. AtCoder Janken 3题目描述思路代码E. Xor Sigma Problem题目描述思路代码…

洛谷 P3870 开关之线段树板子

洛谷P3870题解传送锚点摸鱼环节 [TJOI2009] 开关 题目描述 现有 \(n\) 盏灯排成一排,从左到右依次编号为:\(1\),\(2\),……,\(n\)。然后依次执行 \(m\) 项操作。 操作分为两种:指定一个区间 \([a,b]\),然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开…

OI学习笔记(C++)

一些蒟蒻整理的OI笔记笔记完整版链接(洛谷)——暂无,正在维护 笔记完整版链接(博客) 参照 oi.wiki 整理的一些笔记: 学习笔记+模板(Adorable_hly) (自己结合网络和做题经验总结的,dalao勿喷) 第一大板块:DP 动态规划适用场景: 1. 最优化原理:若该问题所包含的子问…