WINUI——Magewell视频捕捉开发手记

背景

因需要融合视频,并加载患者CT中提取出的气管镜与病变,以便能实时查看气管镜是否在正确位置。

开发环境

硬件:Magewell的USB Capture HDMI Gen 2

IDE:VS2022

FrameWork: .Net6   WINUI

Package: MVVMToolKit     NLog

Magewell Demo源码浅析

看了下Magewell提供的Demo源码(官网下载MWCaptureSDK 3.3.1.1513安装后,在安装目录下即可看到),看了下,其仅提供了WinForm的示例。

以下为获取视频并播放的核心代码:

void audio_play()
{
    Console.WriteLine("audio_play in");
    while (m_capturing){
        CRingBuffer.st_frame_t frame = m_capture.m_audio_buffer.get_frame_to_render();
        if (frame.buffer_len == 0){
            Thread.Sleep(5);
            continue;
        }
        LibMWMedia.MWDSoundRendererPushFrame(m_dsound_render, frame.p_buffer, frame.frame_len);
    }
    m_capture.m_audio_buffer.stop_render();
    Console.WriteLine("audio_play out");
}

其中m_d3d_renderer的初始化如下:

 m_d3d_renderer初始化时调用了Handle参数:

 那么也就是说执行

 LibMWMedia.MWDSoundRendererPushFrame(m_dsound_render, frame.p_buffer, frame.frame_len);

就是将每帧的数据显示到Handle句柄所在的Form上。

而在执行 LibMWMedia.MWDSoundRendererPushFrame(m_dsound_render, frame.p_buffer, frame.frame_len)时,相应图像帧(或视频流)的数据就应该是frame.p_buffer()。

在WINUI中仅Window可以获取到句柄,但若用Window进行处理,就需要处理多个Window,这是不推荐的;最好还是以Page来显示会更合适一些。

通过测试,使用视频流来进行开发是可行的,其中一种方法即是将缓存下来的图像帧(YUY2)转化为图片(Bitmap,即YUY2转为BGRA),然后将图片贴到UI上即可,只要能保证大于24帧的帧率,就能达到流畅的视频效果。

下述为YUY2转化为BGRA类:

    /// <summary>
    /// YUY2转为BGRA格式
    /// </summary>
    internal class YUY2ToBGRA
    {
        internal static void ConvertYUY2ToBGRA(byte[] inputPtr, byte[] outputPtr)
        {
            // 假设数据是16位灰度(高字节在前)
            for (int i = 0, j = 0; i < inputPtr.Length; i += 4, j += 8)
            {
                // 读取YUY2数据(每4字节包含2个像素)
                byte y0 = inputPtr[i];
                byte u = inputPtr[i + 1];
                byte y1 = inputPtr[i + 2];
                byte v = inputPtr[i + 3];

                // 转换为RGB(简化版)
                YUVToRGB(y0, u, v, out byte r0, out byte g0, out byte b0);
                YUVToRGB(y1, u, v, out byte r1, out byte g1, out byte b1);

                // 写入BGRA格式
                outputPtr[j] = b0;     // B
                outputPtr[j + 1] = g0; // G
                outputPtr[j + 2] = r0; // R
                outputPtr[j + 3] = 255; // A

                outputPtr[j + 4] = b1;
                outputPtr[j + 5] = g1;
                outputPtr[j + 6] = r1;
                outputPtr[j + 7] = 255;
            }
        }

        // YUV到RGB转换
        private static void YUVToRGB(byte y, byte u, byte v, out byte r, out byte g, out byte b)
        {
            // 标准化YUV值
            double yD = (y - 16) / 219.0;
            double uD = (u - 128) / 224.0;
            double vD = (v - 128) / 224.0;

            // 转换矩阵(BT.601标准)
            r = (byte)(255 * Math.Clamp(yD + 1.402 * vD, 0, 1));
            g = (byte)(255 * Math.Clamp(yD - 0.344 * uD - 0.714 * vD, 0, 1));
            b = (byte)(255 * Math.Clamp(yD + 1.772 * uD, 0, 1));
        }
    }

根据Magewell给出的示例,增加下述视频捕获类:

    /// <summary>
    /// 视频捕获
    /// </summary>
    internal class VedioCapture
    {
        Boolean m_capturing = false;
        protected CMWCapture m_capture = null; 
        protected IntPtr m_d3d_renderer = IntPtr.Zero;
        private byte[] conversionBuffer; // 用于格式转换的缓冲区
        // 假设视频格式为YUY2 (1920x1080)
        private int Width = 1920;
        private int Height = 1080;
        //private const int BytesPerPixel = 2; // YUY2是2字节/像素

        /// <summary>
        /// 开始捕捉
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        internal Boolean Start_capture(Int32 index = 0)
        {
            if (m_capturing)
            {
                return true;
            }
            m_capture = CMWCapture.mw_capture_factory(index);
            if (null == m_capture)
            {
                return false;
            }
            if (!m_capture.set_device(index))
            {
                return false;
            }
            if (!m_capture.start_capture(true, false))
            {
                return false;
            }
            m_capturing = true;

            m_capture.get_mw_fourcc(out uint mw_fourcc);
            m_capture.get_mirror_and_reverse(out bool mirror, out bool reverse);
            m_capture.get_resolution(out Width, out Height);

            NlogHelper.Logger.Info($"获取分辨率,Width: {Width}, Height: {Height}");
            // 更新 UI
         
            Task.Run(() =>
            {
                _ = Video_play();
            });
            return true;
        }


        /// <summary>
        /// 视频播放
        /// </summary>
        /// <returns></returns>
        async Task Video_play()
        {
            NlogHelper.Logger.Info("video_play in");
            conversionBuffer = new byte[Width * Height * 4];
            while (m_capturing)
            {
                CRingBuffer.st_frame_t frame = m_capture.m_video_buffer.get_frame_to_render();
                if (frame.buffer_len == 0)
                {
                    continue;
                }
                UpdateFrame(frame.p_buffer);

                await Task.Delay(5);
            }

            NlogHelper.Logger.Info("video_play out");
        }

        /// <summary>
        /// 停止捕捉视频
        /// </summary>
        internal void Stop_capture()
        {
            if (!m_capturing)
            {
                return;
            }
            m_capturing = false;

            if (m_capture != null)
            {
                CRingBuffer.st_frame_t out_frame = m_capture.m_video_buffer.get_buffer_by_index(0);
                Array.Clear(out_frame.p_buffer, 0, out_frame.p_buffer.Length);
                m_capture.m_video_buffer.stop_render();
                m_capture.Dispose();
                m_capture = null;
            }
        }

        public void UpdateFrame(byte[] rawData)
        {
            if (rawData.Length != Width * Height * 2)
            {
                NlogHelper.Logger.Info($"rawData.Length {rawData.Length}");
                return;
            }

            // 1. 将YUY2转换为BGRA
            YUY2ToBGRA.ConvertYUY2ToBGRA(rawData, conversionBuffer);
            WeakReferenceMessenger.Default.Send(conversionBuffer, "VedioCaptureResult");
        }


    }

上述代码中,视频捕捉硬件的默认输出分辨率为1920*1080,若需要修改,可以在安装的Magwell上进行设置:

在VM中调用如下:

   private void StartCapture()
   {
       CMWCapture.Init();
       CMWCapture.RefreshDevices();
       int m_channel_count = CMWCapture.GetChannelCount();

       if (m_channel_count == 0)
       {
           ToUIMessage.SendMessage("GpuEncodeGui", "Can't find capture devices!");
       }
       else
       {
           VedioCapture = new();
           VedioCapture.Start_capture();
           InitSegment();
       }
   }

因为CMWCapture为非托管资源,在使用完成时需要手动将资源释放,故在VM中需要手动释放,示例如下:

[RelayCommand]
private void ReleaseResource()
{
    VedioCapture?.Stop_capture();
    WeakReferenceMessenger.Default.UnregisterAll(this);
}

VedioCapture为VM初始化时构造的VedioCapture实例。

WINUI中处理获视频流

UI需先引入 xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"

<win2d:CanvasSwapChainPanel
    x:Name="swapChainPanel"
    Width="1920"
    Height="1080"
    VerticalAlignment="Top" />

UI后台代码中接收视频捕获结果如下:

 WeakReferenceMessenger.Default.Register<byte[], string>(this, "VedioCaptureResult", (r, conversionBuffer) =>
 {
     UpdateFrame(conversionBuffer);
 });

上述涉及调用的UpdateFrame方法,以更新每一帧:

 private void UpdateFrame(byte[] conversionBuffer)
 {
     using (var drawingSession = swapChain.CreateDrawingSession(Colors.Black))
     using (var bitmap = CanvasBitmap.CreateFromBytes(
      canvasDevice,
      conversionBuffer,
      width,
      height,
      DirectXPixelFormat.B8G8R8A8UIntNormalized))
     {
         // 3. 绘制到交换链
         drawingSession.DrawImage(bitmap);
     }
     // 呈现到SwapChainPanel
     swapChain.Present();
 }

注意:上述使用的了MvvmToolkit中的Messenger,在离开所在Page时需要将Messenger全部释放,如  WeakReferenceMessenger.Default.UnregisterAll(this);  否则会导致Page不能被释放,会导致内存泄漏。

<think>嗯,用户现在想知道在WinUI3开发中推荐使用哪种编程语言。首先,我需要回顾一下WinUI3的基本信息。WinUI3是微软推出的原生UI框架,专门用于Windows应用程序开发,支持现代功能和Fluent Design。根据之前的引用,WinUI3Keng项目用的是C#和XAML[^1],而另一个项目将WinUI3与Rust结合,强调安全性和性能[^2]。另外,跨平台开发中提到C#在Xamarin/Maui中的使用[^3]。 用户的问题集中在推荐语言上,所以需要列出主要选项并比较优缺点。C#是官方支持的主要语言,与XAML结合紧密,生态完善,适合大多数情况。Rust作为系统级语言,适合对性能和安全要求高的场景,但可能生态不成熟。C++适合需要直接操作底层的情况,但复杂度高。JavaScript/TypeScript适用于Web背景的开发者,但可能有性能损失。 还要考虑用户的具体需求,比如是否需要跨平台,开发经验如何。如果用户主要开发Windows应用,C#是首选;如果需要极致性能或安全性,可以考虑Rust或C++。此外,跨平台的话,C#也有MAUI支持,但WinUI3本身是Windows专属。 最后,建议用户根据项目需求选择,并提供相关资源链接,比如官方的C#示例和Rust结合的项目参考。需要确保回答结构清晰,引用正确,符合用户的要求。</think>### WinUI3 开发推荐编程语言 WinUI3 是微软推出的**原生 UI 框架**,专为 Windows 应用设计,支持现代功能与 Fluent Design。根据开发需求和技术背景,推荐以下编程语言: #### 1. **C#(官方首选语言)** - **优势**: - **官方支持**:WinUI3 与 C# 深度集成,提供最完善的工具链(Visual Studio)和文档支持。 - **XAML 协作**:通过 XAML 实现 UI 与 C# 逻辑分离,开发效率高,适合复杂界面交互。 - **生态成熟**:依赖 .NET 生态,NuGet 包管理便捷,可调用大量现有库(如 ASP.NET Core、Entity Framework)。 - **适用场景**: - 企业级 Windows 桌面应用(如办公软件、数据管理工具)。 - 需要快速迭代或依赖 .NET 功能的项目。 - **示例代码**: ```csharp public sealed partial class MainWindow : Window { public MainWindow() { InitializeComponent(); myButton.Content = "点击"; } } ``` #### 2. **Rust(高性能与安全性)** - **优势**: - **内存安全**:通过所有权机制避免内存泄漏和空指针,适合对稳定性要求高的应用[^2]。 - **性能优势**:直接编译为机器码,适合计算密集型任务(如图形渲染、实时数据处理)。 - **跨语言调用**:可通过 FFI 与 C# 交互,结合 WinUI3 界面与 Rust 后端逻辑。 - **适用场景**: - 安全关键型应用(如金融、工业控制)。 - 需要高性能后端服务的复杂系统。 - **示例场景**: ```rust // 通过 COM 接口与 WinUI3 交互 #[com_interface] trait ICalculator { fn add(&self, a: i32, b: i32) -> i32; } ``` #### 3. **C++(系统级控制)** - **优势**: - **底层控制**:直接调用 Windows API,适合需要硬件交互或深度优化的场景。 - **高性能**:与 Rust 类似,适合图形引擎、游戏开发。 - **缺点**: - 开发复杂度高,需手动管理内存。 - WinUI3 的 C++ 文档较少,社区资源有限。 #### 4. **JavaScript/TypeScript(Web 技术栈)** - **优势**: - **跨平台潜力**:可通过 WebView2 嵌入 Web 内容,适合混合开发[^3]。 - **前端生态**:利用 React/Vue 等框架快速构建 UI。 - **缺点**: - 性能低于原生方案,不适合计算密集型任务。 --- ### 选择建议 | 需求 | 推荐语言 | 理由 | |---------------------|-----------|----------------------------------------------------------------------| | 快速开发、企业应用 | C# | 官方支持完善,工具链成熟 | | 高性能与内存安全 | Rust | 避免内存错误,适合长期维护的关键系统 | | 底层硬件交互 | C++ | 直接调用驱动或系统 API | | 混合 Web/桌面体验 | TypeScript| 结合 WebView2 实现跨平台 UI 复用 | --- ### 相关问题 1. WinUI3 如何与 Rust 通过 FFI 交互? 2. C#WinUI3 开发中的典型设计模式是什么?[^1] 3. WebView2 在 WinUI3 中的应用场景与限制?[^3] 如需进一步实践示例,可参考 [WinUI3Keng 项目](https://gitcode.com/gh_mirrors/wi/WinUI3Keng)中的 C# 实现或 Rust 集成方案[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值