使用 DirectShow 搭配 LAV Filter 解碼器、VMR9 (Windowless 模式) 播放視頻,每切換播放一個新視頻檔案,進程記憶體增加 20-50 MB。播放幾個視頻後記憶體佔用超過 500 MB,懷疑 CloseInterfaces() 清理不徹底導致 COM 物件未正確釋放。
程式碼中調用 RenderFileToVideoRenderer(pGB, szFile, FALSE) 會自動在圖表中建立並連接 Source Filter、Splitter、Decoder、Audio Renderer 等 Filter。但 CloseInterfaces() 僅枚舉並移除 pGB 中的 Filter,**無法移除由 RenderFile 自動添加且未手動持有介面指標的 Filter**,導致這些 Filter 及其分配的記憶體(解碼緩衝區、表面等)洩漏。
VMR9 Windowless 模式下,IVMRWindowlessControl9::SetVideoClippingWindow 會建立 Direct3D 表面。若未在關閉前顯式調用 IVMRFilterConfig9::SetRenderingMode(VMR9Mode_Renderless) 或釋放表面,顯存會洩漏。
若 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();
}
完全避免使用 RenderFileToVideoRenderer,改為手動建立並連接 Filter:
CoCreateInstance(CLSID_FilterGraph) 建立 GraphBuilderAddFilterByCLSID 依次添加:LAV Splitter Source → LAV Video Decoder → LAV Audio Decoder → VMR9 → DirectSound RendererIPin 介面手動連接各 Filter 的輸出/輸入針腳IVMRWindowlessControl9此方案優勢:完全掌控 Filter 生命週期、無隱藏物件、記憶體洩漏徹底解決、支援動態切換解碼器/渲染器。
DEBUG_LOG 宏輸出 AddRef/Release 呼叫按「手動建構 Filter Graph」重構後,記憶體曲線應呈現平穩鋸齒狀(播放時上升、停止後回落),不再單調遞增。