如何复用IMFSample以避免额外内存占用?
我使用C++/CLI编写了一款屏幕录像程序,每秒捕获30帧视频并在PictureBox中显示。我的思路是将IMFSample复制到可复用的output sample中,再释放源sample以控制内存占用。
尽管我在调用sampleOut->AddBuffer(destBuf)前执行了sampleOut->RemoveAllBuffers(),但程序仍会额外占用内存,似乎RemoveAllBuffers并未真正释放前一视频帧的内存。请问如何在不释放并重建sampleOut的情况下释放内存?
HRESULT ScreenCapture::DuplicateSample(IMFSample* sample) { HRESULT hr = S_OK; if (!sample) { hr = E_ABORT; return hr; } DWORD sampleFlags = 0; LONGLONG llVideoTimeStamp = 0; LONGLONG llSampleDuration = 0; IMFMediaBuffer* srcBuf; IMFMediaBuffer* destBuf; hr = sample->GetSampleFlags(&sampleFlags); hr = sample->GetSampleTime(&llVideoTimeStamp); hr = sample->GetSampleDuration(&llSampleDuration); hr = sampleOut->SetSampleFlags(sampleFlags); hr = sampleOut->SetSampleTime(llVideoTimeStamp); hr = sampleOut->SetSampleDuration(llSampleDuration); srcBuf = nullptr; hr = sample->GetBufferByIndex(0, &srcBuf); byte* srcByteBuffer = nullptr; DWORD srcBuffCurrLen = 0; DWORD srcBuffMaxLen = 0; hr = srcBuf->Lock(&srcByteBuffer, &srcBuffMaxLen, &srcBuffCurrLen); destBuf = nullptr; hr = MFCreateMemoryBuffer(srcBuffCurrLen, &destBuf); byte* destByteBuffer = nullptr; hr = destBuf->Lock(&destByteBuffer, nullptr, nullptr); memcpy(destByteBuffer, srcByteBuffer, srcBuffCurrLen); hr = destBuf->Unlock(); hr = srcBuf->Unlock(); hr = destBuf->SetCurrentLength(srcBuffCurrLen); SafeRelease(&srcBuf); sampleOut->RemoveAllBuffers(); hr = sampleOut->AddBuffer(destBuf); //Memory issue here SafeRelease(&destBuf); return hr; }
另外,我也需要更好的方法来处理每一步的hr错误检测,打算先解决内存问题后再处理这部分。
内存泄漏的核心原因及修复
你的代码每次调用MFCreateMemoryBuffer创建新缓冲区,RemoveAllBuffers只是让sampleOut放弃旧缓冲区的引用,但如果旧缓冲区的引用计数未降到0,内存不会被释放。加上频繁创建新缓冲区的操作,会持续堆积未释放的内存。
具体修复步骤:
复用缓冲区,避免重复创建
在类中添加一个IMFMediaBuffer* m_reusableDestBuf成员变量,预先创建一个足够容纳最大帧尺寸的缓冲区(可根据屏幕分辨率计算初始大小),每次仅更新缓冲区内容,而非重新创建:- 首次调用时创建缓冲区,后续调用中若当前帧尺寸超过缓冲区最大容量,才重新创建。
- 这样能彻底避免频繁分配/释放内存带来的开销和泄漏风险。
确保旧缓冲区引用计数正确递减
RemoveAllBuffers会移除sampleOut对旧缓冲区的引用,但需确保没有其他地方持有这些缓冲区的引用。依赖COM的引用计数机制,所有不需要的引用都用SafeRelease释放,就能保证内存被正确回收。
修改后的核心代码示例:
// 类成员变量声明:IMFMediaBuffer* m_reusableDestBuf = nullptr; HRESULT ScreenCapture::DuplicateSample(IMFSample* sample) { HRESULT hr = S_OK; IMFMediaBuffer* srcBuf = nullptr; if (!sample) { hr = E_ABORT; return hr; } DWORD sampleFlags = 0; LONGLONG llVideoTimeStamp = 0; LONGLONG llSampleDuration = 0; hr = sample->GetSampleFlags(&sampleFlags); if (FAILED(hr)) goto done; hr = sample->GetSampleTime(&llVideoTimeStamp); if (FAILED(hr)) goto done; hr = sample->GetSampleDuration(&llSampleDuration); if (FAILED(hr)) goto done; hr = sampleOut->SetSampleFlags(sampleFlags); if (FAILED(hr)) goto done; hr = sampleOut->SetSampleTime(llVideoTimeStamp); if (FAILED(hr)) goto done; hr = sampleOut->SetSampleDuration(llSampleDuration); if (FAILED(hr)) goto done; hr = sample->GetBufferByIndex(0, &srcBuf); if (FAILED(hr)) goto done; byte* srcByteBuffer = nullptr; DWORD srcBuffCurrLen = 0; DWORD srcBuffMaxLen = 0; hr = srcBuf->Lock(&srcByteBuffer, &srcBuffMaxLen, &srcBuffCurrLen); if (FAILED(hr)) goto done; // 复用缓冲区:不存在或尺寸不足则重新创建 if (!m_reusableDestBuf) { hr = MFCreateMemoryBuffer(srcBuffMaxLen, &m_reusableDestBuf); if (FAILED(hr)) goto done; } else { DWORD destMaxLen = 0; hr = m_reusableDestBuf->GetMaxLength(&destMaxLen); if (FAILED(hr)) goto done; if (destMaxLen < srcBuffCurrLen) { SafeRelease(&m_reusableDestBuf); hr = MFCreateMemoryBuffer(srcBuffCurrLen, &m_reusableDestBuf); if (FAILED(hr)) goto done; } } byte* destByteBuffer = nullptr; hr = m_reusableDestBuf->Lock(&destByteBuffer, nullptr, nullptr); if (FAILED(hr)) goto done; memcpy(destByteBuffer, srcByteBuffer, srcBuffCurrLen); hr = m_reusableDestBuf->Unlock(); if (FAILED(hr)) goto done; hr = srcBuf->Unlock(); if (FAILED(hr)) goto done; hr = m_reusableDestBuf->SetCurrentLength(srcBuffCurrLen); if (FAILED(hr)) goto done; // 替换缓冲区 sampleOut->RemoveAllBuffers(); hr = sampleOut->AddBuffer(m_reusableDestBuf); if (FAILED(hr)) goto done; done: SafeRelease(&srcBuf); return hr; }
- 类销毁时释放复用缓冲区
在类的析构函数中调用SafeRelease(&m_reusableDestBuf),避免程序退出时残留内存泄漏。
HR错误检测优化
可以封装一个宏简化错误检测逻辑,减少冗余代码:
#define CHECK_HR(hr, err_msg) \ if (FAILED(hr)) { \ OutputDebugStringA(err_msg); \ goto done; \ }
使用时直接替换原有的失败判断:
hr = sample->GetSampleFlags(&sampleFlags); CHECK_HR(hr, "Failed to get sample flags");
内容的提问来源于stack exchange,提问作者Travelinman

