【问题标题】:ffmpeg avcodec_send_packet/avcodec_receive_frame memory leakffmpeg avcodec_send_packet/avcodec_receive_frame 内存泄漏
【发布时间】:2019-06-14 09:52:54
【问题描述】:

我正在尝试解码帧,但内存使用量随着每一帧(更具体地说,每次调用 avcodec_send_packet)而增加,直到最后代码因 bad_alloc 而崩溃。这是基本的解码循环:

int rfret = 0;
while((rfret = av_read_frame(inctx.get(), &packet)) >= 0){
    if (packet.stream_index == vstrm_idx) {

        //std::cout << "Sending Packet" << std::endl;
        int ret = avcodec_send_packet(ctx.get(), &packet);
        if (ret < 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            std::cout << "avcodec_send_packet: " << ret << std::endl;
            break;
        }

        while (ret  >= 0) {
            //std::cout << "Receiving Frame" << std::endl;
            ret = avcodec_receive_frame(ctx.get(), fr);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                //std::cout << "avcodec_receive_frame: " << ret << std::endl;
                av_frame_unref(fr);
                // av_frame_free(&fr);
                break;
            }

            std::cout << "frame: " << ctx->frame_number << std::endl;

            // eventually do something with the frame here...

            av_frame_unref(fr);
            // av_frame_free(&fr);
        }
    }
    else {
        //std::cout << "Not Video" << std::endl;
    }
    av_packet_unref(&packet);
}

内存使用/泄漏似乎与我正在解码的视频的分辨率成比例。例如,对于 3840x2160 分辨率的视频,Windows 任务管理器中的内存使用量对于每个接收到的帧都会持续增加大约 8mb(每像素 1 个字节??)。除了调用 av_frame_unref 释放内存,我还需要做些什么吗?

(更多)下面的完整代码


void AVFormatContextDeleter(AVFormatContext* ptr)
{
    if (ptr) {
        avformat_close_input(&ptr);
    }
}

void AVCodecContextDeleter(AVCodecContext* ptr)
{
    if (ptr) {
        avcodec_free_context(&ptr);
    }
}

typedef std::unique_ptr<AVFormatContext, void (*)(AVFormatContext *)> AVFormatContextPtr;
typedef std::unique_ptr<AVCodecContext, void (*)(AVCodecContext *)> AVCodecContextPtr;

AVCodecContextPtr createAvCodecContext(AVCodec *vcodec)
{
    AVCodecContextPtr ctx(avcodec_alloc_context3(vcodec), AVCodecContextDeleter);
    return ctx;
}

AVFormatContextPtr createFormatContext(const std::string& filename)
{
    AVFormatContext* inctxPtr = nullptr;
    int ret = avformat_open_input(&inctxPtr, filename.c_str(), nullptr, nullptr);
    //    int ret = avformat_open_input(&inctx, "D:/Videos/test.mp4", nullptr, nullptr);
    if (ret != 0) {
        inctxPtr = nullptr;
    }

    return AVFormatContextPtr(inctxPtr, AVFormatContextDeleter);
}

int testDecode()
{
    // open input file context
    AVFormatContextPtr inctx = createFormatContext("D:/Videos/Matt Chapman Hi Greg.MOV");

    if (!inctx) {
        // std::cerr << "fail to avforamt_open_input(\"" << infile << "\"): ret=" << ret;
        return 1;
    }

    // retrieve input stream information
    int ret = avformat_find_stream_info(inctx.get(), nullptr);
    if (ret < 0) {
        //std::cerr << "fail to avformat_find_stream_info: ret=" << ret;
        return 2;
    }

    // find primary video stream
    AVCodec* vcodec = nullptr;
    const int vstrm_idx = av_find_best_stream(inctx.get(), AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0);
    if (vstrm_idx < 0) {
        //std::cerr << "fail to av_find_best_stream: vstrm_idx=" << vstrm_idx;
        return 3;
    }

    AVCodecParameters* origin_par = inctx->streams[vstrm_idx]->codecpar;
    if (vcodec == nullptr) {  // is this even necessary?
        vcodec = avcodec_find_decoder(origin_par->codec_id);
        if (!vcodec) {
            // Can't find decoder
            return 4;
        }
    }

    AVCodecContextPtr ctx = createAvCodecContext(vcodec);
    if (!ctx) {
        return 5;
    }

    ret = avcodec_parameters_to_context(ctx.get(), origin_par);
    if (ret) {
        return 6;
    }

    ret = avcodec_open2(ctx.get(), vcodec, nullptr);
    if (ret < 0) {
        return 7;
    }

    //print input video stream informataion
    std::cout
            //<< "infile: " << infile << "\n"
            << "format: " << inctx->iformat->name << "\n"
            << "vcodec: " << vcodec->name << "\n"
            << "size:   " << origin_par->width << 'x' << origin_par->height << "\n"
            << "fps:    " << av_q2d(ctx->framerate) << " [fps]\n"
            << "length: " << av_rescale_q(inctx->duration, ctx->time_base, {1,1000}) / 1000. << " [sec]\n"
            << "pixfmt: " << av_get_pix_fmt_name(ctx->pix_fmt) << "\n"
            << "frame:  " << inctx->streams[vstrm_idx]->nb_frames << "\n"
            << std::flush;


    AVPacket packet;

    av_init_packet(&packet);
    packet.data = nullptr;
    packet.size = 0;

    AVFrame *fr = av_frame_alloc();
    if (!fr) {
        return 8;
    }

    int rfret = 0;
    while((rfret = av_read_frame(inctx.get(), &packet)) >= 0){
        if (packet.stream_index == vstrm_idx) {

            //std::cout << "Sending Packet" << std::endl;
            int ret = avcodec_send_packet(ctx.get(), &packet);
            if (ret < 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                std::cout << "avcodec_send_packet: " << ret << std::endl;
                break;
            }

            while (ret  >= 0) {
                //std::cout << "Receiving Frame" << std::endl;
                ret = avcodec_receive_frame(ctx.get(), fr);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    //std::cout << "avcodec_receive_frame: " << ret << std::endl;
                    av_frame_unref(fr);
                    // av_frame_free(&fr);
                    break;
                }

                std::cout << "frame: " << ctx->frame_number << std::endl;

                // do something with the frame here...

                av_frame_unref(fr);
                // av_frame_free(&fr);
            }
        }
        else {
            //std::cout << "Not Video" << std::endl;
        }
        av_packet_unref(&packet);
    }

    std::cout << "RFRET = " << rfret << std::endl;

    return 0;
}

更新 1:(2019 年 1 月 21 日)在不同的机器上编译并使用不同的视频文件运行我没有看到内存使用量无限制地增长。我将尝试缩小差异所在(编译器?、ffmpeg 版本?还是视频编码?)

更新 2:(2019 年 1 月 21 日)好的,看起来 ffmpeg 和 Qt 的 QCamera 之间发生了一些交互。在我的应用程序中,我使用 Qt 来管理网络摄像头,但决定使用 ffmpeg 库来处理解码/编码,因为 Qt 没有对不同编解码器的全面支持。如果我打开相机(通过 Qt),ffmpeg 解码内存消耗会无限增长。如果相机关闭,ffmpeg 表现良好。我已经尝试过使用物理相机(Logitech C920)和使用 OBS-Virtualcam 的虚拟相机,结果相同。到目前为止,我对这两个系统如何交互感到困惑......

【问题讨论】:

    标签: ffmpeg decode libavcodec qcamera


    【解决方案1】:

    我有同样的问题。

    在使用 av_frame_unref 之前。

    调用 av_freep(buffer->data[0])。

    av_frame_unref 未在帧中释放原始数据

    示例:

        av_freep(&pFrame->data[0]);
        av_frame_unref(pFrame);
        //av_free(pFrame);
    

    编辑: 我很抱歉英语不成熟。 解码视频时,缓冲区中有图像的数据。 在您释放它并重新分配它之前,它将保持为 NULL 指针,这意味着您需要在重新分配时再次分配内存。

    在您使用完图像数据后,您应该释放缓冲区。 你是这样用的吗?

        while (Framecheck = av_read_frame(pFormatCtx, &packet) == NULL ) {
    
            if (d_end == true)
                break;
            if (packet.stream_index == VSI) {
                if (bool res = avcodec_send_packet(pVideoCodecCtx, &packet)) {
                    printf("avcodec_send_packet failed %d %d %d\n", res, AVERROR(EINVAL), AVERROR(ENOMEM));
                }
                if (bool res = avcodec_receive_frame(pVideoCodecCtx, pVFrame) == 0) {
                    printf("avcodec_receive failed %d %d %d\n", res, AVERROR(EINVAL), AVERROR(ENOMEM));
                }
                if (pVFrame->data[0] == NULL && pVFrame->data[1] == NULL && pVFrame->data[2] == NULL)
                    continue;
                else {
                    YUV_frame = Con_yuv_RGB(pVFrame);
                    QFrame->push(YUV_frame);
                    PushCount++;
    
                }
            }
            Sleep(5);
        }
        if (Framecheck != true){
            av_packet_unref(&packet);
            d_end = true;
    
            return true;
    

    发布:

        if (FrameQueue->size()) {
        while (FrameQueue->size() > 0) {
            av_freep(&FrameQueue->front());
            //av_frame_unref(FrameQueue->front());
            av_free(FrameQueue->front());
            FrameQueue->pop();
        }
    }
    

    【讨论】:

    • 我在其他地方看到过这个建议,但在我的程序中它让事情变得更糟(阅读 2 或 3 帧后出现段错误)
    • 另外,你不需要使用队列。
    • 我在转换功能中使用 YUV 帧分配。
    【解决方案2】:

    当你完成框架时(在你的 while 循环之外)尝试调用 av_frame_free

    不要打电话给av_frame_unref

    请参见此处的示例: https://ffmpeg.org/doxygen/4.0/decode__video_8c_source.html

    【讨论】:

    • 行为似乎没有改变。我应该补充一点,如果我使用足够小的视频,或者如果我提前跳出循环,则内存恢复。问题在于,对于较大的视频(例如,30 秒的 4k 视频),在循环结束之前 会抛出 bad_alloc。
    猜你喜欢
    • 2018-01-03
    • 1970-01-01
    • 2015-03-11
    • 2020-06-12
    • 2011-05-11
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 2015-11-06
    相关资源
    最近更新 更多