【问题标题】:Memory leak when using ffmpeg使用 ffmpeg 时的内存泄漏
【发布时间】:2017-01-21 03:50:33
【问题描述】:

我已经实现了一个类,它产生一个线程来读取和排队帧,主线程通过 OpenGL 显示这些帧。在将图像数据绑定到 OpenGL 纹理后,我尝试释放分配的内存,但似乎有些内存没有正确释放。内存使用量不断增长,直到系统内存不足,最终由于内存分配失败,帧读取器线程无法抓取新帧。有人可以帮我解决我可能错过的事情吗?谢谢。

这是帧阅读器线程的代码:

void AVIReader::frameReaderThreadFunc()
{
    AVPacket packet;

    while (readFrames) {
        // Allocate necessary memory
        AVFrame* pFrame = av_frame_alloc();
        if (pFrame == nullptr)
        {
            continue;
        }

        AVFrame* pFrameRGB = av_frame_alloc();
        if (pFrameRGB == nullptr)
        {
            av_frame_free(&pFrame);
            continue;
        }

        // Determine required buffer size and allocate buffer
        int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width,
            pCodecCtx->height);
        uint8_t* buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));

        if (buffer == nullptr)
        {
            av_frame_free(&pFrame);
            av_frame_free(&pFrameRGB);
            continue;
        }

        // Assign appropriate parts of buffer to image planes in pFrameRGB
        // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
        // of AVPicture
        avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_RGB24,
            pCodecCtx->width, pCodecCtx->height);

        if (av_read_frame(pFormatCtx, &packet) >= 0) {
            // Is this a packet from the video stream?
            if (packet.stream_index == videoStream) {
                // Decode video frame
                int frameFinished;
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

                if (frameFinished) {
                    // Convert the image from its native format to RGB
                    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height,
                        pFrameRGB->data, pFrameRGB->linesize);

                    VideoFrame vf;
                    vf.frame = pFrameRGB;
                    vf.pts = av_frame_get_best_effort_timestamp(pFrame) * time_base;
                    frameQueue.enqueue(vf);

                    av_frame_unref(pFrame);
                    av_frame_free(&pFrame);
                }
            }
            //av_packet_unref(&packet);
            av_free_packet(&packet);
        }
    }
}

这是抓取排队帧并将其绑定到 OpenGL 纹理的代码。我明确地保存前一帧,直到我用下一帧将其切换出来。否则,似乎会导致段错误。

void AVIReader::GrabAVIFrame()
{
    if (curFrame.pts >= clock_pts)
    {
        return;
    }

    if (frameQueue.empty())
        return;

    // Get a packet from the queue
    VideoFrame videoFrame = frameQueue.top();
    while (!frameQueue.empty() && frameQueue.top().pts < clock_pts)
    {
        videoFrame = frameQueue.dequeue();
    }

    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, videoFrame.frame->data[0]);

    // release previous frame
    if (curFrame.frame)
    {
        av_free(curFrame.frame->data[0]);
    }
    av_frame_unref(curFrame.frame);

    // set current frame to new frame
    curFrame = videoFrame;
}

frameQueue 是一个线程安全的优先级队列,其中包含定义为的 VideoFrame:

class VideoFrame {
public:
    AVFrame* frame;
    double pts;
};

更新:将当前帧设置为新帧的顺序出现了一个愚蠢的错误。尝试了一些东西后我忘记将其切换回来。我也采纳了@ivan_onys 的建议,但这似乎并不能解决问题。


更新 2: 我采纳了@Al Bundy 的建议,无条件释放 pFrame 和数据包,但问题仍然存在。

由于缓冲区包含需要在 glTexSubImage2D() 中使用的实际图像数据,因此在屏幕上显示完成之前我无法释放它(否则会出现段错误)。 avpicture_fill() 分配 frame->data[0] = buffer,所以我想调用 av_free(curFrame.frame->data[0]);在纹理映射后的前一帧上,新帧应该释放分配的缓冲区。

这是更新的帧阅读器线程代码:

void AVIReader::frameReaderThreadFunc()
{
    AVPacket packet;

    while (readFrames) {
        // Allocate necessary memory
        AVFrame* pFrame = av_frame_alloc();
        if (pFrame == nullptr)
        {
            continue;
        }

        AVFrame* pFrameRGB = av_frame_alloc();
        if (pFrameRGB == nullptr)
        {
            av_frame_free(&pFrame);
            continue;
        }

        // Determine required buffer size and allocate buffer
        int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width,
            pCodecCtx->height);
        uint8_t* buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));

        if (buffer == nullptr)
        {
            av_frame_free(&pFrame);
            av_frame_free(&pFrameRGB);
            continue;
        }

        // Assign appropriate parts of buffer to image planes in pFrameRGB
        // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
        // of AVPicture
        avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_RGB24,
            pCodecCtx->width, pCodecCtx->height);

        if (av_read_frame(pFormatCtx, &packet) >= 0) {
            // Is this a packet from the video stream?
            if (packet.stream_index == videoStream) {
                // Decode video frame
                int frameFinished;
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

                if (frameFinished) {
                    // Convert the image from its native format to RGB
                    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height,
                        pFrameRGB->data, pFrameRGB->linesize);

                    VideoFrame vf;
                    vf.frame = pFrameRGB;
                    vf.pts = av_frame_get_best_effort_timestamp(pFrame) * time_base;
                    frameQueue.enqueue(vf);
                }
            }
        }
        av_frame_unref(pFrame);
        av_frame_free(&pFrame);
        av_packet_unref(&packet);
        av_free_packet(&packet);
    }
}

已解决:事实证明,当数据包来自非视频流(例如音频)时,就会发生泄漏。我还需要释放在 GrabAVIFrame() 的 while 循环中跳过的帧上的资源。

【问题讨论】:

    标签: c++ memory-leaks ffmpeg


    【解决方案1】:

    你永远不会释放buffer。检查您的流动路径,就像现在一样,它没有任何意义。另外,请考虑 ivan_onys 的回答。


    编辑:

    正如我所写,检查流程。你有三个分配:

    • pFrame
    • pFrameRGB
    • 缓冲区

    但是只有当这个命令是true时你才释放它们:

    if (av_read_frame(pFormatCtx, &amp;packet) &gt;= 0)if (frameFinished)buffer 永远不会被释放。

    这似乎是问题所在。我会在while 结束之前释放所有指针:

          if (av_read_frame(pFormatCtx, &packet) >= 0) {
          ...
             if (frameFinished) {
    //          av_frame_unref(pFrame);  --> remove this line
    //          av_frame_free(&pFrame);  --> remove this line
             }
          }
          // here is the body from while
          // release it here - unconditional
          av_packet_unref(&packet);
          av_free_packet(&packet);
          av_free(buffer);
       }    // while 
    }       // frameReaderThreadFunc()
    

    【讨论】:

    • 没有 avpicture_fill() 将缓冲区绑定到 frame->data[0],所以在 curFrame.frame->data[0] 上调用 av_free() 应该可以吗?
    • 我已经尝试显式保存缓冲区指针并对其调用 av_free() ,但它仍然显示相同的行为。我还更新了设置新框架的顺序。在尝试了几种不同的解决方案后,我似乎忘记将其改回。
    • 感谢您提供更新的答案。无条件释放内存是有道理的。我采纳了你的建议,但我仍然有同样的问题。正如我之前提到的,avpicture_fill() 分配 frame->data[0] = buffer。由于缓冲区包含需要在 glTexSubImage2D() 中使用的实际图像数据,因此在屏幕上显示完成之前我无法释放它(否则会出现段错误)。这就是为什么我调用 av_free(curFrame.frame->data[0]);在纹理映射新帧之后的前一帧。
    • @se210 嗯,让std::vector&lt;uint8_t*&gt; 持有为buffer 分配的指针并在完成后遍历向量并释放所有指针怎么样?我从未与ffmpeg合作过
    • 我想我现在解决了这个问题。您对检查缓冲区控制流的见解有所帮助。事实证明,当数据包的流不是视频流(即它是音频)时,我正在泄漏内存。感谢您的帮助!
    【解决方案2】:

    我在这里看到了多个问题:

    1. 尝试释放未分配的已分配内存。

      AVFrame* pFrame = av_frame_alloc();
      if (pFrame == nullptr)
      {
          av_frame_free(&pFrame);
          continue;
      }
      
    2. 不释放分配的内存(pFrame from 1. 不释放):

      AVFrame* pFrameRGB = av_frame_alloc();
      if (pFrameRGB == nullptr)
      {
          av_frame_free(&pFrameRGB);
          continue;
      }
      

    在继续之前,您应该释放在上述循环体中成功分配的所有内存。

    【讨论】:

    • 我尝试了您的建议,但似乎并没有解决问题。内存似乎很快就会被填满,所以我认为这与图像缓冲区的分配有关。
    猜你喜欢
    • 2019-06-14
    • 1970-01-01
    • 2015-03-11
    • 2011-05-11
    • 2012-04-13
    • 2020-06-12
    • 1970-01-01
    • 2021-07-06
    • 2013-10-11
    相关资源
    最近更新 更多