使用ffmpeg时出现内存泄漏问题

6
我已经实现了一个类,它可以生成一个用于读取和排队帧的线程,主线程通过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和packet,但问题仍然存在。

由于缓冲区是包含实际图像数据的地方,需要在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循环中跳过的帧上释放资源。

2个回答

2
您从未释放buffer。请检查您的流程路径,因为现在它并没有真正意义。此外,请考虑ivan_onys的回答。
编辑:
正如我所写的,检查流程。您有三个分配: pFrame pFrameRGB 缓冲区
但是只有当此命令为true时,您才会释放它们:
if (av_read_frame(pFormatCtx, &packet) >= 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()应该可以工作吗? - se210
我已经尝试过显式保存缓冲指针并调用av_free(),但它仍然显示相同的行为。我还更新了设置新帧的顺序。看来在尝试几种不同的解决方案后,我忘记改回来了。 - se210
感谢您提供更新的答案。无条件释放内存是有道理的。我采纳了您的建议,但我仍然遇到了同样的问题。正如我之前提到的,avpicture_fill()将frame->data[0] = buffer。由于buffer包含实际的图像数据,需要在glTexSubImage2D()中使用,因此我不能在屏幕上显示它之前释放它(否则会导致段错误)。这就是为什么我在贴图新帧后调用av_free(curFrame.frame->data[0]);来释放上一帧的原因。 - se210
@se210 嗯,如果使用 std::vector<uint8_t*> 来保存 buffer 的分配指针,并在完成后遍历向量并释放所有指针,这样怎么样?我从未使用过 ffmpeg - Peter VARGA
我想现在我解决了这个问题。你提到检查缓冲区控制流的见解很有帮助。结果发现当数据包的流不是视频流(即音频流)时,我会泄漏内存。感谢你的帮助! - se210

1
我看到这里有多个问题:
  1. Attempt to free allocated memory, when it wasn't allocated.

    AVFrame* pFrame = av_frame_alloc();
    if (pFrame == nullptr)
    {
        av_frame_free(&pFrame);
        continue;
    }
    
  2. Not freeing allocated memory (pFrame from 1. isn't freed):

    AVFrame* pFrameRGB = av_frame_alloc();
    if (pFrameRGB == nullptr)
    {
        av_frame_free(&pFrameRGB);
        continue;
    }
    
在继续之前,您应该成功释放循环体中成功分配的所有内存。

我尝试了你的建议,但似乎并没有解决问题。内存似乎很快就会填满,所以我认为这可能与图像缓冲区的分配有关。 - se210

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接