ffmpeg avcodec_send_packet/avcodec_receive_frame memory leak

5.6k Views Asked by At

I'm attempting to decode frames, but memory usage grows with every frame (more specifically, with every call to avcodec_send_packet) until finally the code crashes with a bad_alloc. Here's the basic decode loop:

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);
}

Memory usage/leakage seems to scale with the resolution of the video I'm decoding. For example, for a 3840x2160 resolution video, the memory usage in windows task manager consistently jumps up by about 8mb (1 byte per pixel??) for each received frame. Do I need to do something besides call av_frame_unref to release the memory?

(more) complete code below


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;
}

Update 1: (1/21/2019) Compiling on a different machine and running with different video files I am not seeing the memory usage growing without bound. I'll try to narrow down where the difference lies (compiler?, ffmpeg version?, or video encoding?)

Update 2: (1/21/2019) Ok, it looks like there is some interaction occurring between ffmpeg and Qt's QCamera. In my application, I'm using Qt to manage the webcam, but decided to use ffmpeg libraries to handle decoding/encoding since Qt doesn't have as comprehensive support for different codecs. If I have the camera turned on (through Qt), ffmpeg decoding memory consumption grows without bound. If the camera is off, ffmpeg behaves fine. I've tried this both with a physical camera (Logitech C920) and with a virtual camera using OBS-Virtualcam, with the same result. So far I'm baffled as to how the two systems are interacting...

2

There are 2 best solutions below

1
On

Try calling av_frame_free when you're done with the frame (outside your while loop)

And don't call av_frame_unref

See example here: https://ffmpeg.org/doxygen/4.0/decode__video_8c_source.html

3
On

I had same problem.

before use the av_frame_unref.

call av_freep(buffer->data[0]).

av_frame_unref was not release raw data in frame

example:

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

EDIT: I am sorry that English is immature. When you decode the video, you have the data for the image in the buffer. It will remain as a NULL pointer until you release it and reallocate it, which means that you will need to allocate memory again at reallocation.

After you have finished using the image data, you should release the buffer. Are you using it like that?

    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;

release:

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