窗口/屏幕截图适用于截图、批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面、远程桌面这些场景是如何实现画面录制的。
常见的屏幕画面时时采集方案,主要有GDI、WGC、DXGI。
GDI
GDI(Graphics Device Interface)就是使用user32下WindowsAPI来实现,是 Windows 操纵系统中最早、最基础的图形设备接口,满足所有windows平台。屏幕/窗口截图可以详见: .NET 窗口/屏幕截图 - 唐宋元明清2188 - 博客园 (cnblogs.com)
录制屏幕,可以基于GDI截图方案,使用定时器捕获屏幕数据。
GDI性能不太好,尤其是针对高帧率及高分辨率需求,达到每秒20帧以上的截取,占用CPU就有点高了。另外GDI不能获取鼠标,必要在截取的图像中把鼠标画上去。
所以GDI使用很方便、不依靠GPU,对性能要求不高的截图场景建议直接使用这个方案。
WGC
Windows Graphics Capture ,是Win10引入的一种新截取屏幕以及截取窗口内容的机制 Screen capture - UWP applications | Microsoft Learn
WinRT提供接口访问,Csproj属性中添加:true
截图代码实现示例:- 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截图流程:
- 创建捕捉项:使用 CreateCaptureItemForMonitor 或 CreateCaptureItemForWindow 来创建捕捉项。
- 创建D3D11设备和上下文:调用 D3D11CreateDevice 创建 Direct3D 11 设备和设备上下文。这里固然没有使用DXGI截图,但引用了DXGI的设备类型
- 转换为 Direct3D 设备:将 D3D11 设备转换为SharpDX Direct3D 设备对象。
- 创建帧池和会话:使用 Direct3D11CaptureFramePool 和 GraphicsCaptureSession。
- 开始捕捉:调用 StartCapture 开始会话,并注册帧到达变乱。
- 处理帧:在帧到达变乱中处理捕获的帧
我们这里是使用比力成熟的SharpDX来处理Direct3D,引用如下Nuget版本
获取到截取的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能提供必要的性能优化。
上面3个方案Demo示例,具体代码都在github堆栈:kybs00/CaptureImageDemo (github.com)
总结下这三个方案,
GDI:适用于所有 Windows 版本,但性能较低。
WGC:Win10 1803版本以上,高性能和低延迟,屏幕及窗口均支持。
DXGI:Win8版本以上,适用于高分辨率高帧率等高性能的需求,并且只支持屏幕录制、不支持窗口。
录制主要是录屏、直播、远程桌面、视讯会议、传屏等场景。录制屏幕/窗口建议优先使用WGC,然后用DXGI兼容win8;假如仅录制屏幕且高分辨率、高帧率场景,建议优先DXGI
关键字:录屏、录制窗口、高性能屏幕捕获
出处:http://www.cnblogs.com/kybs0/本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |