媒体基础多个视频播放导致内存泄漏和未定义时间范围后崩溃

Posted

技术标签:

【中文标题】媒体基础多个视频播放导致内存泄漏和未定义时间范围后崩溃【英文标题】:Media foundation multiple videos playback results in memory-leak & crash after undefined timeframe 【发布时间】:2019-11-18 10:46:44 【问题描述】:

因此,我们使用由 c++ 媒体基础代码组成的堆栈来播放视频文件。一个重要的要求是能够以不断重复的顺序播放这些视频,因此每个视频插槽都会定期更改正在播放的视频。在我们当前的示例中,我们创建了 16 个 HWND 来将视频渲染到 16 个相应的播放器对象中。主应用程序依次循环所有这些并执行以下操作:

关闭最后一个播放器 释放对象 新玩家的 CoCreateinstance 使用(旧)HWND 初始化播放器 开始播放 媒体播放器称为“MediaPlayer2”,需要构建并注册为 COM (regsvr32)。主要应用程序可以在 TAPlayer2 项目中找到。它在注册表中搜索播放器的 CLSID 并将其实例化。作为当前的测试文件,我们使用一个 test.mp4,它必须驻留在磁盘上,如 C:\test.mp4

现在一切都很顺利。程序循环播放器,视频不断重新启动和播放。内存占用正常,一切顺利。在 20 分钟到 4 天之间的任何时间范围之后,所有突然的事情都会变得很奇怪。在这一点上,EVR 对“InitializeRenderer”的调用似乎变慢了,最终根本不再通过。如此一来,线程数和内存占用量也将开始急剧增加,并且在一定时间后,取决于现有 RAM,所有内存都将耗尽,我们的应用程序崩溃,通常在 GPU 驱动程序或 EVR DLL 附近的某个地方。

我很高兴尝试任何其他可以解决我的问题的代码示例:同时显示多个视频窗口,并像在播放列表中一样循环播放它们。需要在 Windows 10 上运行! 我已经做了很长一段时间了,而且很难坚持。我上传了上面提到的代码示例并将链接添加到这篇文章。这应该开箱即用。如果愿意,我还可以在线程中提供代码摘录。

感谢任何帮助,谢谢

托马斯

演示项目(VS2015)链接:https://filebin.net/l8gl79jrz6fd02vt

编辑:winmain.cpp 末尾的以下代码用于重启播放器:

do
    
    for (int i = 0; i < PLAYER_COUNT; i++)
    
      hr = g_pPlayer[i]->Shutdown();
      SafeRelease(&g_pPlayer[i]);
      hr = CoCreateInstance(CLSID_AvasysPlayer,  // CLSID of the coclass
        NULL,                         // no aggregation
        CLSCTX_INPROC_SERVER,         // the server is in-proc
        __uuidof(IAvasysPlayer),      // IID of the interface we want
        (void**)&g_pPlayer[i]);         // address of our interface pointer

      hr = g_pPlayer[i]->InitPlayer(hwndPlayers[i]);

      hr = g_pPlayer[i]->OpenUrl(L"C:\\test.mp4");
    

   while (true);

【问题讨论】:

尝试为您的问题创建一个Minimal, Reproducible Example。也许添加一些产生此错误的代码而不是整个项目,这将使我们更容易为您提供帮助 @JohnSmith 恐怕这是我能提供的最小的、可重现的例子。单独测试时,代码的所有单独部分都按预期工作。只有当结合起来用于实际任务(并发序列/播放列表)时才会出现问题。 重启播放器的代码在哪里? 我更新了我的初始帖子并添加了重新启动播放器的部分。 您的播放器实现中可能存在内存泄漏。我过去曾从事过类似的项目(在屏幕上混合视频输出),并通过使用单个 DirectX Presenter 来处理它。我使用 DirectX 11 VideoProcessBlt 将所有视频纹理(硬件 DX 11 解码)叠加到最终更大的输出 DX 11 纹理上。这对于渲染和录制到新的 (mp4/H264) 文件也很有用。每个视频输入都由单独的 IMFMediaSource 处理。 【参考方案1】:

一些 MediaFoundation 接口,例如

IMFMediaSource IMFMediaSession IMFMediaSink

在释放它们之前需要关闭。

此时,EVR 对“InitializeRenderer”的调用似乎变慢了,最终根本不再通过。 ... 通常位于 GPU 驱动程序中或 EVR DLL 附近。

在您的代码中进行精确搜索的好方法。

在您的文件 PlayerTopoBuilder.cpp 中,位于 CPlayerTopoBuilder::AddBranchToPartialTopology :

if (bVideo)

  if (false) 
    BREAK_ON_FAIL(hr = CreateMediaSinkActivate(pSD, hVideoWnd, &pSinkActivate));
    BREAK_ON_FAIL(hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode));
  
    else 
      //// try directly create renderer
      BREAK_ON_FAIL(hr = MFCreateVideoRenderer(__uuidof(IMFMediaSink), (void**)&pMediaSink));
      CComQIPtr<IMFVideoRenderer> pRenderer = pMediaSink;
      BREAK_ON_FAIL(hr = pRenderer->InitializeRenderer(nullptr, nullptr));

      CComQIPtr<IMFGetService> getService(pRenderer);
      BREAK_ON_FAIL(hr = getService->GetService(MR_VIDEO_RENDER_SERVICE, __uuidof(IMFVideoDisplayControl), (void**)&pVideoDisplayControl));
      BREAK_ON_FAIL(hr = pVideoDisplayControl->SetVideoWindow(hVideoWnd));

      BREAK_ON_FAIL(hr = pMediaSink->GetStreamSinkByIndex(0, &pStreamSink));
      BREAK_ON_FAIL(hr = AddOutputNode(pTopology, 0, &pOutputNode, pStreamSink));
    

您使用 MFCreateVideoRenderer 和 pMediaSink 创建一个 IMFMediaSink。 pMediaSink 因使用 CComPtr 而被释放,但从未关闭。

您必须在媒体接收器上保留一个引用,并在播放器关闭时关闭/释放它。

或者您可以对 MFCreateVideoRendererActivate 使用不同的方法。

IMFMediaSink::Shutdown

如果应用程序创建媒体接收器,它负责调用 Shutdown 以避免内存或资源泄漏。 然而,在大多数应用程序中,应用程序为媒体接收器创建一个激活对象,并且媒体会话使用该对象来创建媒体接收器。 在这种情况下,媒体会话(而不是应用程序)会关闭媒体接收器。 (有关详细信息,请参阅激活对象。)

我也建议你在 CPlayer::CloseSession 的末尾使用这种代码(在释放所有其他对象之后):

if(m_pSession != NULL)

    hr = m_pSession->Shutdown();

    ULONG ulMFObjects = m_pSession->Release();
    m_pSession = NULL;

    assert(ulMFObjects == 0);

关于MFCreateVideoRendererActivate的使用,可以看我的MFNodePlayer项目:

MFNodePlayer

编辑

我重写了你的程序,但我试图保留你的逻辑和原始源代码,比如 CComPtr/Mutex...

MFMultiVideo

告诉我这个程序是否有内存泄漏。

这将取决于您的回答,但随后我们可以讨论 MediaFoundation 的最佳实践。

另一个想法:

您的程序使用 1 到 16 个 IMFMediaSession。我认为,在一台好的计算机配置上,您只能使用一个 IMFMediasession(永远不要尝试聚合 16 个 MFSource)。

访问:

CustomVideoMixer

了解另一种方法。

我认为您使用 16 IMFMediasession 的方法不是现代计算机上的最佳方法。 VuVirt 谈论这个。

EDIT2

我已经使用工作队列更新了 MFMultiVideo。

我认为问题可能在于您为每个玩家调用 MFStartup/MFShutdown。 例如,只需在 winmain.cpp 中调用一次 MFStartup/MFShutdown,就像我的程序一样。

【讨论】:

在 PlayerTopoBuilder.cpp 中,在 CPlayerTopoBuilder::AddBranchToPartialTopology :如果您将 if(false) 更改为 if(true) 它应该已经通过 MFCreateVideoRendererActivate 创建媒体接收器,这是我最初的实现,但已经在内存泄漏/崩溃(4 天后!)。 ActivationObject 的这种用法对您来说似乎没问题吗?您能否建议此代码的关闭行为? (关闭/发布顺序) 好的,我们要做的不仅仅是代码审查。在提出任何建议之前,我先做一个简单的调试。 到目前为止有什么观察吗?通过最新的更改并使用 IMFActivate,我能够在 MediaSession 释放时每隔一分钟看到 一个 附加引用,并且代码在 4 天内不断泄漏内存。 调试代码后,我看不到内存泄漏。您项目的 MediaFoundation 源代码部分对我来说是正确的。 非常感谢您的努力!我用你的代码做了一堆测试,可以确认你的确切实现不会产生内存泄漏。但是,一旦我介绍了 MFWorkQueues 的概念,我绝对需要在这些视频上获得流畅的播放。我觉得我不知道 何时 准确使用 MFUnlockWorkQueue 我将在创建 Source 节点期间获取工作队列,到目前为止我无法找到正确的位置在关机期间。无论我把它放在哪里,我都会在发布时偶尔在 MediaSession 上附加引用。

以上是关于媒体基础多个视频播放导致内存泄漏和未定义时间范围后崩溃的主要内容,如果未能解决你的问题,请参考以下文章

如何在媒体基础上定制视频媒体/流接收器请求RGB32帧?

SoundPlayer 导致内存泄漏?

Swift Youtube 播放器内存泄漏

EasyNVR调16分屏播放ws-flv内存溢出导致浏览器崩溃问题的解决

不能使用释放或自动释放。出现黄色警告:内存泄漏和未使用的变量

MF Sink Writer 的内存泄漏