You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复用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,内存不会被释放。加上频繁创建新缓冲区的操作,会持续堆积未释放的内存。

具体修复步骤:

  1. 复用缓冲区,避免重复创建
    在类中添加一个IMFMediaBuffer* m_reusableDestBuf成员变量,预先创建一个足够容纳最大帧尺寸的缓冲区(可根据屏幕分辨率计算初始大小),每次仅更新缓冲区内容,而非重新创建:

    • 首次调用时创建缓冲区,后续调用中若当前帧尺寸超过缓冲区最大容量,才重新创建。
    • 这样能彻底避免频繁分配/释放内存带来的开销和泄漏风险。
  2. 确保旧缓冲区引用计数正确递减
    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;
}
  1. 类销毁时释放复用缓冲区
    在类的析构函数中调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:40:29