如何在不冻结 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?的主要内容,如果未能解决你的问题,请参考以下文章