如何在不冻结 GUI 的情况下使用视频帧不断更新 contentcontrol?

Posted

技术标签:

【中文标题】如何在不冻结 GUI 的情况下使用视频帧不断更新 contentcontrol?【英文标题】:How can I constantly update contentcontrol with video frames without freezing the GUI? 【发布时间】:2012-07-27 13:34:31 【问题描述】:

我有一个 VideoRenderer 类,它继承了一个 Image 控件,一个 MyVideo 类,它有一个 VideoRenderer 实例,以及 MainWindow 代码隐藏中的一个 RenderFrames 方法

我在下面附上了这些类的相关部分,以及 RenderFrames 方法和一些 MainWindow 代码隐藏构造函数。

VideoRenderer 接收外部视频并将视频帧生成为位图对象,存储在“位图”字段中。在每个完成的过程之后,我将位图的副本存储在“bitmapCopy”字段中。

MyVideo 控制 VideoRenderer 实例“sharedVideoRenderer”何时开始和停止接收和发送帧到 MainWindow RenderFrames 方法,使用 ThreadPool.QueueUserWorkItem 将 VideoRenderer 实例作为对象参数传递。

RenderFrames 循环直到 MyVideo 说通过更改它的任一布尔“我们正在渲染”属性来停止,它从 Bitmap -> BitmapImage -> Image.Source 进行转换,然后将 VideoContentControl.Content 设置为图像,使用MainWindow 调度程序。

一切正常,视频渲染,但 GUI 控制基本上被冻结,其他按钮和其他东西不起作用,因为将整个操作分派到 MainWindow 线程并不断循环占用线程。

我的问题是:我可以尝试哪些其他方法将位图帧从“sharedVideoRenderer”传输到 VideoContentControl,并使用新图像不断更新它,而不冻结 GUI?

任何帮助将不胜感激。

相关代码:

VideoRenderer.cs:

internal void DrawBitmap()

    lock (bitmapLock)
    
        bitmapCopy = (Bitmap)bitmap.Clone();
    

MyVideo.cs:

public static void RenderVideoPreview()

    sharedVideoRenderer.VideoObject = videoPreview;

    sharedVideoRenderer.Start();

    videoPreviewIsRendering = true;

    ThreadPool.QueueUserWorkItem((Application.Current.MainWindow as MainWindow).RenderFrames, sharedVideoRenderer);
    

MainWindow.xaml.cs:

Dispatcher mainWindowDispatcher;

public MainWindow()

    InitializeComponent();

    mainWindowDispatcher = this.Dispatcher;
...

public void RenderFrames(object videoRenderer)

    while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)
    
        mainWindowDispatcher.Invoke(new Action(() =>
        
            try
            
                System.Drawing.Bitmap bitmap;

                bitmap = (videoRenderer as VideoRenderer).BitmapCopy;

                using (MemoryStream memory = new MemoryStream())
                
                    BitmapImage bitmapImage = new BitmapImage();
                    ImageSource imageSource;
                    Image image;
                    image = new Image();

                    bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);

                    memory.Position = 0;

                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = memory;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();

                    imageSource = bitmapImage;

                    image.Source = imageSource;

                    VideoContentControl.Content = image;

                    memory.Close();
                
            
            catch (Exception)
            

            
        ));
    

    mainWindowDispatcher.Invoke(new Action(() =>
        
            VideoContentControl.ClearValue(ContentProperty);

            VideoContentControl.InvalidateVisual();
        ));

【问题讨论】:

我认为您需要利用 GPU 来确保这项工作可靠。 【参考方案1】:
    ThreadPool.QueueUserWorkItem((Application.Current.MainWindow as MainWindow).RenderFrames, sharedVideoRenderer);

////    

public void RenderFrames(object videoRenderer)

    while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)
    
        mainWindowDispatcher.Invoke(new Action(() =>

我猜这些台词造成了痛苦。

虽然 Renderframes 方法是在线程池线程上调用的,但它所做的主要工作是将控制权传递回 UI 线程,从而声明/阻止它。

您可以尝试找到更小的范围:

while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)


        try
        
            System.Drawing.Bitmap bitmap;

            bitmap = (videoRenderer as VideoRenderer).BitmapCopy;

            using (MemoryStream memory = new MemoryStream())
            
                BitmapImage bitmapImage = new BitmapImage();
                ImageSource imageSource;
                Image image;
                image = new Image();

                bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);

                memory.Position = 0;

                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memory;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();

                imageSource = bitmapImage;

                mainWindowDispatcher.Invoke(new Action(() =>
                
                    image.Source = imageSource;

                    VideoContentControl.Content = image;

                    memory.Close();
                
            
        
        catch (Exception)
        

        
    ));

我不太确定我将范围更改得太少或太多,因为我真的不知道什么属于 UI 线程,但这可能会给你一个开始,你可能想四处移动。

您可以/应该使用的另一个技巧是尽量减少您正在制作的 UI 元素的数量。在当前代码中,您每帧都创建一个 Image 元素。相反,您可以制作一个 WriteableBitmap,将 Image 的 ImageSource 指向它,然后简单地将图片数据传送到它。见:What is a fast way to generate and draw video in WPF?

【讨论】:

以上是关于如何在不冻结 GUI 的情况下使用视频帧不断更新 contentcontrol?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不冻结 gui 的情况下运行 QProcess 的同步链?

如何在不冻结 GUI 的情况下让 AudioQueue 播放?

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?

如何在不冻结 UI 的情况下使用 QProcess 循环的输出更新 UI?

如何在不冻结 UI 的情况下使用 QProcess 循环的输出更新 UI?

PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]