WPF MediaElement视频冻结

6
我正在使用WPF项目中的Image和MediaElement,从文件系统中显示图片和视频。我有几个计时器,它们负责将文件加载到Image/MediaElement控件中。一切都正常工作了4-5个小时,但是随后MediaElement视频文件会冻结,并且MediaEnded事件不会发生。我重新启动应用程序,它可以正常运行,但是几个小时后这个问题又出现了。
我的WPF XAML代码:
<Grid Name="MainGrid">
    <Image HorizontalAlignment="Center" VerticalAlignment="Center" Name="MainImage" Stretch="Fill" />
    <MediaElement MediaEnded="MediaEnded" MediaOpened="MediaOpened" LoadedBehavior="Manual" HorizontalAlignment="Center" Name="VideoControl" VerticalAlignment="Center"  
                   Stretch="Fill" UnloadedBehavior="Manual"/>
</Grid>

C# 代码:

public partial class ImageView
{
    private static readonly Logger Log = LogManager.GetCurrentClassLogger();
    private static String _advCheckGuid;
    private List<String> _FolderNames;
    private int _FolderIndex = 0;
    private MainWindow _MainWindow;
    private List<String> _PathList;
    private List<String> _CheckPathList; 
    private int _Index;
    private BitmapImage _BitmapImage;
    private volatile bool _Running = true;
    private Backend _Backend;
    private ApplicationDeployment _UpdateCheck;

    // Threads
    private Timer _ImageTimer;
    private Timer _UpdateTimer;
    private Timer _FolderClearTimer;
    private Timer _CheckApplicationUpdateTimer;
    private Thread _TerminationThread;


    public ImageView()
    {
        InitializeComponent();
        _PathList = new List<string>();
        _CheckPathList = new List<string>();
        _Index = 0;

    }

    private void ViewPageLoaded(Object sender, EventArgs e)
    {

        _FolderNames = new List<string> { Constants.AdsFolderFirst, 
                                          Constants.AdsFolderSecond };

        _Backend = new Backend();



        _MainWindow = (MainWindow)Window.GetWindow(this);


        _ImageTimer = new Timer(Constants.DefaultImageTimer);
        _ImageTimer.Elapsed += ChangeImageSource;
        _ImageTimer.Start();


    }


    private void ChangeImageSource(object sender, System.Timers.ElapsedEventArgs e)
    {
        Application.Current.Dispatcher.Invoke(
            DispatcherPriority.Normal, new Action(
                  delegate()
                  {
                      try
                      {
                          if (MainImage != null && MainImage.Source != null)
                          {
                              MainImage.Source = null;
                          }

                          if (VideoControl != null && VideoControl.Source != null)
                          {
                              VideoControl.Stop();
                              VideoControl.Source = null;
                          }

                          if (_Index >= _PathList.Count)
                          {
                              _Index = 0;
                          }

                          if (_PathList.ElementAt(_Index) != null)
                          {

                              Log.Info(String.Format("Start [ChangeImageSource]. Element: {0}, Index: {1}", _PathList.ElementAt(_Index), _Index));

                              try
                              {
                                  _ImageTimer.Stop();

                                  String[] checkExt = _PathList.ElementAt(_Index).Split('.');
                                  String ext = checkExt[checkExt.Length - 1];

                                  if (ext.Equals("jpg", StringComparison.CurrentCultureIgnoreCase) ||
                                      ext.Equals("jpeg", StringComparison.CurrentCultureIgnoreCase) ||
                                      ext.Equals("png", StringComparison.CurrentCultureIgnoreCase))
                                  {
                                      _ImageTimer.Interval = Constants.NormalImageTimer;
                                      ShowImage(_PathList.ElementAt(_Index));
                                  }

                                  else if (ext.Equals("mp4", StringComparison.CurrentCultureIgnoreCase) ||
                                           ext.Equals("3gp", StringComparison.CurrentCultureIgnoreCase))
                                  {
                                      _ImageTimer.Interval = Constants.VideoDefaultTimer;
                                      PlayQueue(_PathList.ElementAt(_Index));
                                  }

                                  _ImageTimer.Start();
                                  _Index++;
                              }
                              catch (Exception exception)
                              {
                                  Log.ErrorException(exception.Message, exception);
                              }
                          }
                      }
                      catch (Exception exception)
                      {
                          Log.ErrorException(exception.Message, exception);
                      }
                  }));
    }


    private void ShowImage(String fileName)
    {
        try
        {
            if (!String.IsNullOrEmpty(fileName))
            {

                _BitmapImage = LoadImage(fileName);
                MainImage.Source = _BitmapImage;

            }
        }
        catch (Exception e)
        {
            Log.ErrorException(e.Message, e);
        }
    }


    private void PlayQueue(String fileName)
    {

        try
        {
            if (!String.IsNullOrEmpty(fileName))
            {
                VideoControl.LoadedBehavior = MediaState.Play;
                VideoControl.Source = new Uri(fileName, UriKind.Absolute);
            }
        }
        catch (Exception e)
        {
            Log.ErrorException(e.Message, e);
        }

    }



    private void MediaEnded(object sender, EventArgs e)
    {
        try
        {
            if (MainImage != null && MainImage.Source != null)
            {
                MainImage.Source = null;
            }

            if (VideoControl != null && VideoControl.Source != null)
            {
                VideoControl.Stop();
                VideoControl.Source = null;
            }

            if (_Index >= _PathList.Count)
            {
                _Index = 0;
            }

            if (_PathList.ElementAt(_Index) != null)
            {

                Log.Info(String.Format("Start [MediaEnded oper]. Element: {0}, Index: {1}", _PathList.ElementAt(_Index), _Index));

                try
                {
                    _ImageTimer.Stop();

                    String[] checkExt = _PathList.ElementAt(_Index).Split('.');
                    String ext = checkExt[checkExt.Length - 1];

                    if (ext.Equals("jpg", StringComparison.CurrentCultureIgnoreCase) ||
                        ext.Equals("jpeg", StringComparison.CurrentCultureIgnoreCase) ||
                        ext.Equals("png", StringComparison.CurrentCultureIgnoreCase))
                    {
                        _ImageTimer.Interval = Constants.NormalImageTimer;
                        ShowImage(_PathList.ElementAt(_Index));
                    }

                    else if (ext.Equals("mp4", StringComparison.CurrentCultureIgnoreCase) ||
                             ext.Equals("3gp", StringComparison.CurrentCultureIgnoreCase))
                    {
                        _ImageTimer.Interval = Constants.VideoDefaultTimer;
                        PlayQueue(_PathList.ElementAt(_Index));
                    }

                    _ImageTimer.Start();
                    _Index++;
                }
                catch (Exception exception)
                {
                    Log.ErrorException(exception.Message, exception);
                }
            }
        }
        catch (Exception exception)
        {
            Log.ErrorException(exception.Message, exception);
        }

    }



    private void MediaOpened(object sender, EventArgs e)
    {

    }



    private BitmapImage LoadImage(string myImageFile)
    {
        BitmapImage myRetVal = null;

        if (!String.IsNullOrEmpty(myImageFile))
        {
            var image = new BitmapImage();
            try
            {
                using (FileStream stream = File.OpenRead(myImageFile))
                {
                    image.BeginInit();
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.StreamSource = stream;
                    image.EndInit();
                }
            }
            catch (Exception exception)
            {
                Log.ErrorException(exception.Message, exception);
            }

            myRetVal = image;
        }

        return myRetVal;
    }

1
我怀疑存在一些内存问题。但仅通过分析相关代码很难得出答案。您能否将您尝试播放的示例视频发送给我们?如果您能提供一个可以重现该问题的完整示例包,我们将不胜感激。 - pushpraj
2
@pushpraj 您可以通过简单调用 mediaElement.Play() 来复制,当您收到 MediaEnded 事件通知时,使用相同的 mediaElement 加载另一个视频并调用 Play()。设置一个循环,其中包含 2 个视频,当一个结束时,开始下一个,让它持续运行,直到其中一个视频冻结且 MediaEnded 没有被调用。如果只循环播放一个视频也会失败。通常需要 6 到 12 小时才会冻结,并且应用程序显示的内存使用量与启动时相同。 - Duncan Groenewald
1
我做了完全相同的事情。我在路径列表中设置了2个视频,并且自从我上次留言以来,它们一直在循环播放10秒钟。让我们看看问题是否可以再现。顺便说一下,我想提一下,与今天的实践相比,代码有点旧了。也许你可以利用DispatcherTimerTasks进行一些简化。 - pushpraj
1
我运行了相当长的时间(超过24小时),但我无法重现相同的问题。这与您机器上的视频编码或视频本身有关吗?我使用两个.avi文件进行了测试。尝试重新安装视频编解码器或更改视频的编码方式,看看问题是否仍然存在。如果您仍然遇到问题,那么也许您可以发送一个包含能够重现该问题的视频和代码样本的副本。 - pushpraj
1
一个不太优雅的解决方案是提供定期重启。你也可以尝试在正常视频之间插入短暂的空白视频进行轮询,一旦没有收到 MediaEnded 信号 - 执行重启。 - Sinatr
4个回答

6

我谷歌了一下,发现这与软件渲染有关,是WPF图形问题。可以通过在ViewPageLoaded方法中添加以下代码来解决该问题。

        try
        {
            var hwndSource = PresentationSource.FromVisual(this) as HwndSource;
            var hwndTarget = hwndSource.CompositionTarget;
            hwndTarget.RenderMode = RenderMode.SoftwareOnly;
        }
        catch (Exception ex)
        {
            Log.ErrorException(ex.Message, ex);
        }

它帮助我解决了问题。希望它也能帮到您。

这里找到了答案。感谢@detale提供的解决方案。


这并没有解决我的问题...也难怪,因为所引用的答案看起来像是尝试1、2、3..而没有深入到问题的根源,也没有解释为什么问题首先会出现。 - G.Y
1
请注意,RenderMode.SoftwareOnly会绕过计算机GPU和图形卡硬件。所有视频处理都由CPU完成。从这个解决方案中预计会有明显的CPU使用率增加。我还注意到在使用此方法时会出现视频帧丢失,导致视频有时会出现抖动。 - LT Dan
看起来我的问题已经解决了...哈利路亚。我正在渲染位于文件共享上的MP4文件。有些会卡住,有时只有声音,灰色图像,...有时视频是好的。??? - Mark
对我来说,当我将上述代码不仅集成到“Window_Loaded”方法中,而且还在按钮单击事件处理程序中调用“Media.Play()”方法时,这个解决方案开始工作。 - Arsen Khachaturyan

5

这是一个复杂的问题..我将尝试深入解释它。(是的,我有一个解决方案)

让我们从MediaElement应该具备什么功能?开始吧!真的!

它是一个通配符,意味着您放在它上面的任何东西-都需要播放:视频、图片、动态图像、音乐... 好的...
现在...每个类别都有多种格式(或标准)... Gif,Png... Wmv,Mp4...
因此,我们使用的每个文件都是由其他编辑器创建的
(其中有一个可以在内部实现播放的播放器...)

似乎大多数公司削减了开支 - 他们并不总是(通常是...)完全实现标准。所以我们得到的结果文件并不总是1:1的标准。

因此,对于一个播放器来说完美的文件格式可能被认为对另一个播放器来说太损坏了。

而商业/高级播放器被设计成容忍文件损坏和标准中的“变化”- MediaElement-嗯...比较简单,也许与您要播放的内容相比太简单了。

所以当它遇到这种问题时-是的...它可能会冻结并且不会报告-这是我完全可以责怪微软的原因-为什么?因为冻结是可以接受的缺陷,但忽略它并不通知使用MediaElement的程序它已经冻结或遇到了严重的演示错误是不可接受的(而且极其不负责任!)
但正如我所说,这是微软的问题,绝对不是您的错。

那么解决方案是什么呢?

您可以尝试对自己说“好吧-我只需获取另一个组件来播放视频或使用第三方插件...”,但是我的朋友,这样做并不能真正解决您的问题,因为您不知道要替换的东西是否也会遇到同样的问题。

所以你唯一剩下的选择就是创建自己的“自定义”标准-放心,我不是说你需要开发一个新标准-我只是说你需要创建一个标准策略,以确保你要投入到MediaElement中的内容不会冻结。

因此,如果您的应用程序将播放作为资源使用的视频-您可能希望使用AnyVideoConverter的最新版本将所有视频转换为mp4。对我来说效果很好,以前在wmv上冻结的视频转换为mp4后,MediaElement非常流畅地容忍了它们。不是MP4起了作用,而是转换本身-我相信ANV会创建任何您可能用于文件的标准的“现代化”视频文件。

但是,如果您的视频是动态上传到应用程序或类似情况-您将不得不确保在实际投入到MediaElement之前,通过您选择的“标准化器”传递任何视频应用程序即将运行的视频。

顺便提一下,浏览器偶尔也会遇到同样的问题。我只希望这些内容可以解决其他人遇到此问题的困扰。

2
您正在创建大量的BitmapImage实例,BitmapImage类中存在内存泄漏问题。BitmapImage会保留对源流的引用(可能是为了随时读取StreamSource属性),因此它会使MemoryStream对象保持活动状态。这导致了内存不足异常。请阅读这篇文章,他为流创建了一个很好的包装器,对我很有帮助。
他在包装类中创建了一个流实例,当您调用包装器的dispose方法时,该流将被处理掉,而BitmapImage.Source仅具有空的包装类,没有任何对原始流的引用。

1
不确定这是否是问题,因为视频会冻结并且永远不会返回MediaEnded事件。这与BitmapImage有什么关系? - Duncan Groenewald
1
我尚未编译您的代码,但如您所说,在4-5小时后会出现问题,因此必定是内存积累所致。而且,新实例的唯一来源是您的LoadImage方法。 - Shivam cv
1
我有同样的问题,但这不是我的代码... 我只有视频。 - Duncan Groenewald
1
没有代码,没有人能够提供任何解决方案,请发布您的代码。MediaElement没有这样的已知问题。 - Shivam cv
如果我有时间,我会创建一个示例应用程序并测试它,然后发布代码。希望有一个快速解决方法。我的应用程序有很多后台线程在做各种事情,包括暂停视频和使媒体元素隐藏/可见,所以我想这可能是一些组合的问题。话虽如此,只有当应用程序除了循环播放视频几个小时之外什么都没做时才会冻结 - 所以我认为这是MediaElement代码中的一些深层次问题。 - Duncan Groenewald
没有内存泄漏 - 我尝试了325kb的视频大小,它在32秒后冻结了(就在第三个循环之后),那里发生了更深层次的事情。 - G.Y

1
我建议注册MediaElement.MediaFailed事件。看看它是否会返回任何内容。
然而,正如其他人所提到的,这听起来像是一个内存问题。您可以使用WPF Performance Suite或者仅使用任务管理器来确认这一点。观察内存使用量的逐渐增加。
正如Shivam cv所提到的,可能是BitmapImage泄漏了。尝试将其从解决方案中注释掉,看看是否能解决该问题。

Duncan已经指出,内存使用量没有增加。这可能是其他问题,比如没有释放文件句柄。 - Austin Mullins
我已经注册了MediaFailed事件,但它没有被调用。在我的代码中,我根本没有做任何位图处理,只是循环遍历一个视频文件目录。应用程序中的其他所有功能都正常工作,如果我重新加载媒体文件,它也可以正常工作——问题在于我的代码依赖于MediaEnded事件来知道何时开始下一个事件,但这个事件从未被调用。我将创建一个示例应用程序,并将其发布为另一个问题——只是现在没有时间,所以希望有人能够快速解决... - Duncan Groenewald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接