Net中使用快速高性能的DX截图

使用DX获取桌面图像主要有如下5步:

1 获取适配器;

2获取输出;

3创建设备;

4创建复制输出;

5创建图像纹理

使用的组件,Opencv使用mat 进行分辨率转换压缩

 using 指令集合

复制代码
using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using OpenCvSharp;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using Device = SharpDX.Direct3D11.Device;
using MapFlags = SharpDX.Direct3D11.MapFlags;
using Size = System.Drawing.Size;
using ResultCode = SharpDX.DXGI.ResultCode;
复制代码

详情(注意使用DXGI只能输出RGBA像素格式,以及注意创建适配和释放适配器,对性能的要求TimeOut很重要可根据具体情况调试设置)

1 创建

复制代码
/// <summary>
        /// 捕获图像大小,默认为捕获源大小,建议按原始比例设置
        /// </summary>
        public Size CaptureSize { get; set; }

        /// <summary>
        /// 输出数据像素格式,默认为BGRA32
        /// </summary>
        public PixelFormat PixelFormat { get; set; } = PixelFormat.Bgra32;

        /// <summary>
        /// 捕获超时时间
        /// </summary>
        public int TimeOut { get; set; } = 100;

        /// <summary>
        /// 捕获源类型
        /// </summary>
        public SourceType SourceType => SourceType.Screen;

        /// <summary>
        /// 捕获源大小
        /// </summary>
        public Size SourceSize { get; }

        // 设备接口表示一个虚拟适配器
        private readonly Device _mDevice;

        // 复制输出设备
        private readonly OutputDuplication _mDeskDupl;

        //图像纹理
        private readonly Texture2D _desktopImageTexture;

        //屏幕大小
        private Size _screenSize;

        private CancellationTokenSource _cancellationTokenSource;

        private bool _isManualCaptureStop;
        /// <summary>
        /// 复制指定监视器的输出
        /// </summary>
        /// <param name="whichMonitor">要复制的输出设备(即监视器)。以零开始,应于主监视器.</param>
        public DxgiCapture(int whichMonitor = 0) : this(0, whichMonitor) { }

        /// <summary>
        /// 在指定的显卡适配器上复制指定监视器的输出
        /// </summary>
        /// <remarks>1获取适配器;2获取输出;3创建设备;4创建复制输出; 5创建图像纹理</remarks>
        /// <param name="whichGraphicsCardAdapter">显卡适配器</param>
        /// <param name="whichOutputDevice">要复制的输出设备(即监视器)。以零开始,应于主监视器.</param>
        public DxgiCapture(int whichGraphicsCardAdapter, int whichOutputDevice)
        {
            var primaryAdapter = GetPrimaryAdapter(whichGraphicsCardAdapter);
            Output primaryOutput = GetOutput(primaryAdapter, whichOutputDevice);

            // 获取输出对象的描述信息
            var mOutputDesc = primaryOutput.Description;
            _screenSize = new Size
            {
                Width = Math.Abs(mOutputDesc.DesktopBounds.Right - mOutputDesc.DesktopBounds.Left),
                Height = Math.Abs(mOutputDesc.DesktopBounds.Bottom - mOutputDesc.DesktopBounds.Top)
            };
            SourceSize = _screenSize;
            CaptureSize = SourceSize;
            //创建Direct3D设备
            _mDevice = CreateDevice(primaryAdapter);
            _mDeskDupl = CreateOutputDuplication(primaryOutput, _mDevice);

            // 创建共享资源
            _desktopImageTexture = CreateTexture2D(_mDevice, _screenSize);
        }
        /// <summary>
        /// 根据显卡编号获取适配器
        /// </summary>
        /// <remarks>获取输出设备(显卡、显示器),这里是主显卡和主显示器</remarks>
        /// <param name="whichGraphicsCardAdapter"></param>
        /// <returns></returns>
        private Adapter1 GetPrimaryAdapter(int whichGraphicsCardAdapter)
        {
            try
            {
                var adapter = new Factory1().GetAdapter1(whichGraphicsCardAdapter);
                return adapter;
            }
            catch (SharpDXException)
            {
                throw new CaptureException("找不到指定的显卡适配器");
            }
        }

        /// <summary>
        /// 根据显示适配器和输出设备获取输出
        /// </summary>
        /// <param name="adapter">显示适配器</param>
        /// <param name="whichOutputDevice">输出设备(监视器)</param>
        /// <returns></returns>
        private Output GetOutput(Adapter1 adapter, int whichOutputDevice)
        {
            try
            {
                return adapter.GetOutput(whichOutputDevice);
            }
            catch (SharpDXException)
            {
                throw new CaptureException("找不到指定的输出设备");
            }
        }

        /// <summary>
        /// 根据显卡适配器(视频卡)创建设备
        /// </summary>
        /// <param name="adapter"></param>
        /// <returns></returns>
        private Device CreateDevice(Adapter1 adapter)
        {
            return new Device(adapter);
            //return new Device(DriverType.Hardware, DeviceCreationFlags.VideoSupport);
        }

        /// <summary>
        /// 根据输出和设备创建输出Duplication
        /// </summary>
        /// <param name="output">输出</param>
        /// <param name="mDevice">设备</param>
        /// <returns></returns>
        private OutputDuplication CreateOutputDuplication(Output output, Device mDevice)
        {
            try
            {
                var mDeskDupl = output.QueryInterface<Output1>().DuplicateOutput(mDevice);
                if (mDeskDupl == null)
                {
                    throw new CaptureException(" Creates a desktop duplication interface Error");
                }

                return mDeskDupl;
            }
            catch (SharpDXException ex)
            {
                if (ex.ResultCode.Code == ResultCode.NotCurrentlyAvailable.Result.Code)
                {
                    throw new CaptureException("使用桌面复制API运行的应用程序已达到最大数量,请关闭其中一个应用程序,然后重试");
                }
                throw new CaptureException(ex.ToString());
            }
        }

        /// <summary>
        /// 创建2D纹理
        /// </summary>
        /// <remarks>共享资源</remarks>
        /// <param name="mDevice"></param>
        /// <param name="screenSize"></param>
        /// <returns></returns>
        private Texture2D CreateTexture2D(Device mDevice, Size screenSize)
        {
            return new Texture2D(mDevice, new Texture2DDescription
            {
                CpuAccessFlags = CpuAccessFlags.Read,
                BindFlags = BindFlags.None,
                Format = Format.B8G8R8A8_UNorm,
                Width = screenSize.Width,
                Height = screenSize.Height,
                OptionFlags = ResourceOptionFlags.None,
                MipLevels = 1,
                ArraySize = 1,
                SampleDescription = { Count = 1, Quality = 0 },
                Usage = ResourceUsage.Staging
            });
        }
      
复制代码

2 获取

复制代码
 [HandleProcessCorruptedStateExceptions]
        private CaptureFrame CaptureFrames()
        {
            try
            {
                var width = _desktopImageTexture.Description.Width;
                var height = _desktopImageTexture.Description.Height;
                var data = PixelFormat switch
                {
                    PixelFormat.Bgra32 => new byte[CaptureSize.Width * CaptureSize.Height * 4],
                    PixelFormat.Bgr24 => new byte[CaptureSize.Width * CaptureSize.Height * 3],
                    _ => throw new ArgumentOutOfRangeException(nameof(PixelFormat), PixelFormat, null)
                };

                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);
                }

                if (PixelFormat == PixelFormat.Bgr24)
                {
                    using var inputRgbMat = new Mat();
                    Cv2.CvtColor(inputRgbaMat, inputRgbMat, ColorConversionCodes.BGRA2BGR);
                    Marshal.Copy(inputRgbMat.Data, data, 0, data.Length);
                }
                else
                {
                    Marshal.Copy(inputRgbaMat.Data, data, 0, data.Length);
                }

                var captureFrame = new CaptureFrame(CaptureSize, PixelFormat, data);
                _mDevice.ImmediateContext.UnmapSubresource(_desktopImageTexture, 0);
                ReleaseFrame(_mDeskDupl);
                return captureFrame;
            }
            catch (AccessViolationException)
            {
                return null;
            }
            catch (Exception)
            {
                return null;
            }
        }
复制代码

3 释放

复制代码
   /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            _mDevice?.Dispose();
            _mDeskDupl?.Dispose();
            _desktopImageTexture?.Dispose();
        }

/// <summary>
        /// 释放帧
        /// </summary>
        /// <param name="mDeskDupl"></param>
        private void ReleaseFrame(OutputDuplication mDeskDupl)
        {
            try
            {
                mDeskDupl.ReleaseFrame();
            }
            catch (SharpDXException ex)
            {
                if (ex.ResultCode.Failure)
                    throw new CaptureException("释放帧失败");
                throw;
            }
        }
复制代码

4 创建回调获取方式

复制代码
        /// <summary>
        /// 开始捕获
        /// </summary>
        public void StartCapture(bool startMonitor = true)
        {
            _isManualCaptureStop = false;
            _cancellationTokenSource = new CancellationTokenSource();
            if (!startMonitor) return;
            Task.Run(() =>
            {
                try
                {
                    while (!_cancellationTokenSource.IsCancellationRequested)
                    {
                        var captureFrame = CaptureFrames();
                        if (captureFrame != null)
                        {
                            FrameArrived?.Invoke(this, captureFrame);
                        }
                    }
                }
                catch (Exception)
                {
                    //ignore
                }
                
            }, _cancellationTokenSource.Token);
        }

        /// <summary>
        /// 停止捕获
        /// </summary>
        public void StopCapture()
        {
            _cancellationTokenSource?.Cancel();
            Dispose();
            _isManualCaptureStop = true;
        }

        /// <summary>
        /// 获取下一帧图像
        /// </summary>
        /// <param name="captureFrame"></param>
        /// <returns></returns>
        public bool TryGetNextFrame(out CaptureFrame captureFrame)
        {
            captureFrame = null;

            if (_isManualCaptureStop) return false;
            _cancellationTokenSource?.Cancel();
            
            captureFrame = CaptureFrames();
            return captureFrame != null;
        }

         /// <summary>
        /// 新帧到达事件
        /// </summary>
        public event EventHandler<CaptureFrame> FrameArrived;
复制代码

 

posted on   TanZhiWei  阅读(80)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示