LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

.NET 窗口/屏幕录制

freeflydom
2024年8月10日 8:57 本文热度 831

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

常见的屏幕画面时时采集方案,主要有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属性中添加:<UseWinRT>true</UseWinRT>

截图代码实现示例:

public WgcCapture(IntPtr hWnd, CaptureType captureType)

    {

        if (!GraphicsCaptureSession.IsSupported())

        {

            throw new Exception("不支Windows Graphics Capture API");

        }

        var item = captureType == CaptureType.Screen ? CaptureUtils.CreateItemForMonitor(hWnd) : CaptureUtils.CreateItemForWindow(hWnd);

        CaptureSize = new Size(item.Size.Width, item.Size.Height);


        var d3dDevice = Direct3D11Utils.CreateDevice(false);

        _device = Direct3D11Utils.CreateSharpDxDevice(d3dDevice);

        _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(d3dDevice, pixelFormat: DirectXPixelFormat.B8G8R8A8UIntNormalized, numberOfBuffers: 1, item.Size);

        _desktopImageTexture = CreateTexture2D(_device, item.Size);

        _framePool.FrameArrived += OnFrameArrived;

        item.Closed += (i, _) =>

        {

            _framePool.FrameArrived -= OnFrameArrived;

            StopCapture();

            ItemClosed?.Invoke(this, i);

        };

        _session = _framePool.CreateCaptureSession(item);

    }

    private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)

    {

        try

        {

            using var frame = _framePool.TryGetNextFrame();

            if (frame == null) return;

            var data = CopyFrameToBytes(frame);

            var captureFrame = new CaptureFrame(CaptureSize, data);

            FrameArrived?.Invoke(this, captureFrame);

        }

        catch (Exception)

        {

            // ignored

        }

    }

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对象帧,帧画面转数据流:

private byte[] CopyFrameToBytes(Direct3D11CaptureFrame frame)

    {

        using var bitmap = Direct3D11Utils.CreateSharpDxTexture2D(frame.Surface);

        _device.ImmediateContext.CopyResource(bitmap, _desktopImageTexture);

        // 将Texture2D资源映射到CPU内存

        var mappedResource = _device.ImmediateContext.MapSubresource(_desktopImageTexture, 0, MapMode.Read, MapFlags.None);

        //Bgra32

        var bytesPerPixel = 4;

        var width = _desktopImageTexture.Description.Width;

        var height = _desktopImageTexture.Description.Height;

        using var inputRgbaMat = new Mat(height, width, MatType.CV_8UC4, mappedResource.DataPointer, mappedResource.RowPitch);


        var data = new byte[CaptureSize.Width * CaptureSize.Height * bytesPerPixel];

        if (CaptureSize.Width != width || CaptureSize.Height != height)

        {

            var size = new OpenCvSharp.Size(CaptureSize.Width, CaptureSize.Height);

            Cv2.Resize(inputRgbaMat, inputRgbaMat, size, interpolation: InterpolationFlags.Linear);

        }

        var sourceSize = new Size(frame.ContentSize.Width, frame.ContentSize.Height);

        if (CaptureSize == sourceSize)

        {

            var rowPitch = mappedResource.RowPitch;

            for (var y = 0; y < height; y++)

            {

                var srcRow = inputRgbaMat.Data + y * rowPitch;

                var destRowOffset = y * width * bytesPerPixel;

                Marshal.Copy(srcRow, data, destRowOffset, width * bytesPerPixel);

            }

        }

        else

        {

            Marshal.Copy(inputRgbaMat.Data, data, 0, data.Length);

        }


        _device.ImmediateContext.UnmapSubresource(_desktopImageTexture, 0);

        return data;

    }

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

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

using var inputRgbMat = new Mat();

Cv2.CvtColor(inputRgbaMat, inputRgbMat, ColorConversionCodes.BGRA2BGR);

Marshal.Copy(inputRgbMat.Data, data, 0, data.Length);

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

屏幕截图Demo显示:

private void CaptureButton_OnClick(object sender, RoutedEventArgs e)

    {

        var monitorHandle = MonitorUtils.GetMonitors().First().MonitorHandle;

        var wgcCapture = new WgcCapture(monitorHandle, CaptureType.Screen);

        wgcCapture.FrameArrived += WgcCapture_FrameArrived;

        wgcCapture.StartCapture();

    }


    private void WgcCapture_FrameArrived(object? sender, CaptureFrame e)

    {

        Application.Current.Dispatcher.Invoke(() =>

        {

            var stride = e.Size.Width * 4; // 4 bytes per pixel in BGRA format

            var bitmap = BitmapSource.Create(e.Size.Width, e.Size.Height, 96, 96, PixelFormats.Bgra32, null, e.Data, stride);

            bitmap.Freeze();

            CaptureImage.Source = bitmap;

        });

    }

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调用代码,

private void CaptureButton_OnClick(object sender, RoutedEventArgs e)

    {

        var monitorDxgiCapture = new MonitorDxgiCapture();

        monitorDxgiCapture.FrameArrived += WgcCapture_FrameArrived;

        monitorDxgiCapture.StartCapture();

    }


    private void WgcCapture_FrameArrived(object? sender, CaptureFrame e)

    {

        Application.Current?.Dispatcher.Invoke(() =>

        {

            var stride = e.Size.Width * 4; // 4 bytes per pixel in BGRA format

            var bitmap = BitmapSource.Create(e.Size.Width, e.Size.Height, 96, 96, PixelFormats.Bgra32, null, e.Data, stride);


            bitmap.Freeze();

            CaptureImage.Source = bitmap;

        });

    }

捕获画面帧数据:

[HandleProcessCorruptedStateExceptions]

    private CaptureFrame CaptureFrame()

    {

        try

        {

            var data = new byte[CaptureSize.Width * CaptureSize.Height * 4];

            var result = _mDeskDupl.TryAcquireNextFrame(TimeOut, out _, out var desktopResource);

            if (result.Failure) return null;


            using var tempTexture = desktopResource?.QueryInterface<Texture2D>();

            _mDevice.ImmediateContext.CopyResource(tempTexture, _desktopImageTexture); //拷贝图像纹理:GPU硬件加速的纹理复制

            desktopResource?.Dispose();


            var desktopSource = _mDevice.ImmediateContext.MapSubresource(_desktopImageTexture, 0, MapMode.Read, MapFlags.None);

            using var inputRgbaMat = new Mat(_screenSize.Height, _screenSize.Width, MatType.CV_8UC4, desktopSource.DataPointer);

            if (CaptureSize.Width != _screenSize.Width || CaptureSize.Height != _screenSize.Height)

            {

                var size = new OpenCvSharp.Size(CaptureSize.Width, CaptureSize.Height);

                Cv2.Resize(inputRgbaMat, inputRgbaMat, size, interpolation: InterpolationFlags.Linear);

            }

            Marshal.Copy(inputRgbaMat.Data, data, 0, data.Length);


            var captureFrame = new CaptureFrame(CaptureSize, data);

            _mDevice.ImmediateContext.UnmapSubresource(_desktopImageTexture, 0);

            //释放帧

            _mDeskDupl.ReleaseFrame();

            return captureFrame;

        }

        catch (AccessViolationException)

        {

            return null;

        }

        catch (Exception)

        {

            return null;

        }

    }

也是使用硬件加速将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





该文章在 2024/8/10 9:00:02 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved