首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

DirectShow 記憶體洩漏問題:VMR9 + LAV Filter 播放視頻記憶體持續增長

     編輯:程式設計綜合問答

問題現象

使用 DirectShow 搭配 LAV Filter 解碼器、VMR9 (Windowless 模式) 播放視頻,每切換播放一個新視頻檔案,進程記憶體增加 20-50 MB。播放幾個視頻後記憶體佔用超過 500 MB,懷疑 CloseInterfaces() 清理不徹底導致 COM 物件未正確釋放。

程式碼分析與核心問題

1. RenderFileToVideoRenderer 內部建立的 Filter 未枚舉釋放

程式碼中調用 RenderFileToVideoRenderer(pGB, szFile, FALSE) 會自動在圖表中建立並連接 Source Filter、Splitter、Decoder、Audio Renderer 等 Filter。但 CloseInterfaces() 僅枚舉並移除 pGB 中的 Filter,**無法移除由 RenderFile 自動添加且未手動持有介面指標的 Filter**,導致這些 Filter 及其分配的記憶體(解碼緩衝區、表面等)洩漏。

2. VMR9 表面分配器未釋放

VMR9 Windowless 模式下,IVMRWindowlessControl9::SetVideoClippingWindow 會建立 Direct3D 表面。若未在關閉前顯式調用 IVMRFilterConfig9::SetRenderingMode(VMR9Mode_Renderless) 或釋放表面,顯存會洩漏。

3. 協助函數 AddFilterByCLSID 可能未正確 AddRef/Release

若 AddFilterByCLSID 內部未正確處理介面引用計數,會導致 Filter 物件洩漏。

正確的資源釋放流程

void CloseInterfaces(void) {
    HRESULT hr;

    // 1. 停止播放,等待狀態轉換完成
    if (pMC) {
        hr = pMC->Stop();
        // 可選:等待 EC_COMPLETE 事件確保完全停止
    }

    // 2. 禁用事件回調
    if (pME) {
        hr = pME->SetNotifyWindow((OAHWND)NULL, 0, 0);
    }

    // 3. 關鍵:先釋放 VMR9 相關介面,再枚舉移除 Filter
    SAFE_RELEASE(pWC);        // IVMRWindowlessControl9
    SAFE_RELEASE(pConfig);    // IVMRFilterConfig9 (若為成員變量)

    // 4. 枚舉並移除所有 Filter(包含 RenderFile 自動添加的)
    IEnumFilters *pEnum = NULL;
    hr = pGB->EnumFilters(&pEnum);
    if (SUCCEEDED(hr)) {
        IBaseFilter *pFilter = NULL;
        while (S_OK == pEnum->Next(1, &pFilter, NULL)) {
            pGB->RemoveFilter(pFilter);
            pFilter->Release();  // 移除後必須 Release
            pEnum->Reset();      // 重置枚舉器,避免死循環
        }
        pEnum->Release();
    }

    // 5. 釋放核心介面(順序不嚴格,但建議反初始化順序)
    SAFE_RELEASE(pME);   // IMediaEventEx
    SAFE_RELEASE(pMS);   // IMediaSeeking
    SAFE_RELEASE(pMP);   // IMediaPosition
    SAFE_RELEASE(pMC);   // IMediaControl
    SAFE_RELEASE(pBA);   // IBasicAudio
    SAFE_RELEASE(pGB);   // IGraphBuilder (最後釋放)

    // 6. 可選:CoUninitialize() 僅在進程退出或線程結束時調用,切勿每次播放調用
    // CoUninitialize(); 
}

根治方案:手動建構 Filter Graph(推薦)

完全避免使用 RenderFileToVideoRenderer,改為手動建立並連接 Filter:

  1. CoCreateInstance(CLSID_FilterGraph) 建立 GraphBuilder
  2. AddFilterByCLSID 依次添加:LAV Splitter Source → LAV Video Decoder → LAV Audio Decoder → VMR9 → DirectSound Renderer
  3. 使用 IPin 介面手動連接各 Filter 的輸出/輸入針腳
  4. 配置 VMR9 為 Windowless 模式,設置 IVMRWindowlessControl9
  5. 播放結束時,按反序釋放所有手動持有的 Filter 介面指標

此方案優勢:完全掌控 Filter 生命週期、無隱藏物件、記憶體洩漏徹底解決、支援動態切換解碼器/渲染器。

調試工具與驗證

  • GraphStudioNext:可視化檢查 Filter Graph 拓撲、介面引用計數
  • DebugView + DirectShow 基底類日誌:定義 DEBUG_LOG 宏輸出 AddRef/Release 呼叫
  • Windows Performance Analyzer (WPA):記憶體分配堆疊追踪,定位洩漏模組

按「手動建構 Filter Graph」重構後,記憶體曲線應呈現平穩鋸齒狀(播放時上升、停止後回落),不再單調遞增。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved