- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
窗口/屏幕截图适用于截图、批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面、远程桌面这些场景是如何实现画面录制的.
常见的屏幕画面时时采集方案,主要有GDI、WGC、DXGI.
GDI(Graphics Device Interface)就是使用user32下WindowsAPI来实现,是 Windows 操作系统中最早、最基础的图形设备接口,满足所有windows平台。屏幕/窗口截图可以详见: .NET 窗口/屏幕截图 - 唐宋元明清2188 - 博客园 (cnblogs.com) 。
录制屏幕,可以基于GDI截图方案,使用定时器捕获屏幕数据.
GDI性能不太好,尤其是针对高帧率及高分辨率需求,达到每秒20帧以上的截取,占用CPU就有点高了。另外GDI不能获取鼠标,需要在截取的图像中把鼠标画上去.
所以GDI使用很方便、不依赖GPU,对性能要求不高的截图场景建议直接使用这个方案.
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 类维护要将屏幕内容复制到其中的帧的缓冲区.
我们这里是使用比较成熟的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) 。
全名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能提供必要的性能优化.
上面3个方案Demo示例,详细代码都在github仓库:kybs00/CaptureImageDemo (github.com) 。
总结下这三个方案, 。
GDI:适用于所有 Windows 版本,但性能较低.
WGC:Win10 1803版本以上,高性能和低延迟,屏幕及窗口均支持.
DXGI:Win8版本以上,适用于高分辨率高帧率等高性能的需求,并且只支持屏幕录制、不支持窗口.
录制主要是录屏、直播、远程桌面、视讯会议、传屏等场景。录制屏幕/窗口建议优先使用WGC,然后用DXGI兼容win8;如果仅录制屏幕且高分辨率、高帧率场景,建议优先DXGI 。
。
关键字:录屏、录制窗口、高性能屏幕捕获 。
最后此篇关于.NET窗口/屏幕录制的文章就讲到这里了,如果你想了解更多关于.NET窗口/屏幕录制的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
https://github.com/mattdiamond/Recorderjs/blob/master/recorder.js中的代码 我不明白 JavaScript 语法,比如 (functio
在 iOS 7 及更早版本中,如果我们想在应用程序中找到 topMostWindow,我们通常使用以下代码行 [[[UIApplication sharedApplication] windows]
我已经尝试解决这个问题很长一段时间了:我无法访问窗口的 url,因为它位于另一个域上..有一些解决方案吗? function login() { var cb = window.ope
是否可以将 FFMPEG 视频流传递到 C# 窗口?现在它在新窗口中作为新进程打开,我只是想将它传递给我自己的 SessionWindow。 此时我像这样执行ffplay: public void E
我有一个名为 x 的矩阵看起来像这样: pTime Close 1 1275087600 1.2268 2 1275264000 1.2264 3 1275264300 1.2
在编译时,发生搜索,grep搜索等,Emacs会在单独的窗口中创建一个新的缓冲区来显示结果,有没有自动跳转到那个窗口的方法?这很有用,因为我可以使用 n 和 p 而不是 M-g n 和 M-g p 移
我有一个启动 PowerShell 脚本的批处理文件。 批处理文件: START Powershell -executionpolicy RemoteSigned -noexit -file "MyS
我有一个基于菜单栏的应用程序,单击图标时会显示一个窗口。在 Mac OS X Lion 上一切正常,但由于某种原因,在 Snow Leopard 和早期版本的 Mac OS X 上会出现错误。任何时候
在 macOS 中,如何在 Xcode 和/或 Interface Builder 中创建带有“集成标题栏和工具栏”的窗口? 这是“宽标题栏”类型的窗口,已添加到 OS X 10.10 Yosemit
在浏览器 (Chrome) 中 JavaScript: var DataModler = { Data: { Something: 'value' }, Process: functi
我有 3 个 html 页面。第 1 页链接到第 2 页,第 2 页链接到第 3 页(为了简单起见)。 我希望页面 2 中的链接打开页面 3 并关闭页面 1(选项卡 1)。 据我了解,您无法使用 Ja
当点击“创建节点”按钮时,如何打开一个新的框架或窗口?我希望新框架包含一个文本字段和下拉菜单,以便用户可以选择一个选项。 Create node Search node
我有一个用户控件,用于编辑应用程序中的某些对象。 我最近遇到一个实例,我想弹出一个新的对话框(窗口)来托管此用户控件。 如何实例化新窗口并将需要设置的任何属性从窗口传递到用户控件? 感谢您的宝贵时间。
我有一个Observable,它发出许多对象,我想使用window或buffer操作对这些对象进行分组。但是,我不想指定count参数来确定窗口中应包含多少个对象,而是希望能够使用自定义条件。 例如,
我有以下代码,它打开一个新的 JavaFX 阶段(我们称之为窗口)。 openAlertBox.setOnAction(e -> { AlertBox alert = AlertBox
我要添加一个“在新窗口中打开”上下文菜单项,该菜单项将以新的UIScene打开我的应用程序文档之一。当然,我只想在实际上支持多个场景的设备上显示该菜单项。 目前,我只是在检查设备是否是使用旧设备的iP
我正在尝试创建一个 AIR 应用程序来记录应用程序的使用情况,使用 AIR 从系统获取信息的唯一简单方法是使用命令行工具和抓取 标准输出 . 我知道像 这样的工具顶部 和 ps 对于 OS X,但它们
所以我有这个简单的 turtle 螺旋制作器,我想知道是否有一种方法可以打印出由该程序创建的我的设计副本。 代码: import turtle x= float(input("Angle: ")) y
我正在编写一个 C# WPF 程序,它将文本消息发送到另一个程序的窗口。我有一个宏程序作为我的键盘驱动程序 (Logitech g15) 的一部分,它已经这样做了,尽管它不会将击键直接发送到进程,而是
我尝试使用以下代码通过 UDP 发送,但得到了奇怪的结果。 if((sendto(newSocket, sendBuf, totalLength, 0, (SOCKADDR *)&sendAd
我是一名优秀的程序员,十分优秀!