Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ffmpeg memory leak in the avcodec_open2 method

I've developed an application which handles live video stream. The problem is that it should run as a service and over time I am noticing some memory increase. When I check the application with valgrind - it did not find any leak related issues. So I've check it with google profile tools. This is a result(substracting the one of the first dumps from the latest) after approximately 6 hour run:

   30.0  35.7%  35.7%     30.0  35.7% av_malloc
    28.9  34.4%  70.2%     28.9  34.4% av_reallocp
    24.5  29.2%  99.4%     24.5  29.2% x264_malloc

When I check the memory on the graph I see, that these allocations are related to avcodec_open2. The client code is:

`           g_EncoderMutex.lock();
            ffmpeg_encoder_start(OutFileName.c_str(), AV_CODEC_ID_H264, m_FPS, width, height);
            for (pts = 0; pts < VideoImages.size(); pts++) {                
                m_frame->pts = pts;
                ffmpeg_encoder_encode_frame(VideoImages[pts].RGBimage[0]);
            }
            ffmpeg_encoder_finish();
            g_EncoderMutex.unlock()

The ffmpeg_encoder_start method is:

 void VideoEncoder::ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height)
        {
            int ret;
            m_FPS=fps;
            AVOutputFormat * fmt = av_guess_format(filename, NULL, NULL);
            m_oc = NULL;
            avformat_alloc_output_context2(&m_oc, NULL, NULL, filename);

            m_stream = avformat_new_stream(m_oc, 0);
            AVCodec *codec=NULL;

            codec =  avcodec_find_encoder(codec_id);    
            if (!codec) 
            {
                fprintf(stderr, "Codec not found\n");
                return; //-1
            }

            m_c=m_stream->codec;

            avcodec_get_context_defaults3(m_c, codec);

            m_c->bit_rate = 400000;
            m_c->width = width;
            m_c->height = height;
            m_c->time_base.num = 1;
            m_c->time_base.den = m_FPS;
            m_c->gop_size = 10;
            m_c->max_b_frames = 1;
            m_c->pix_fmt = AV_PIX_FMT_YUV420P;
            if (codec_id == AV_CODEC_ID_H264)
                av_opt_set(m_c->priv_data, "preset", "ultrafast", 0);

            if (m_oc->oformat->flags & AVFMT_GLOBALHEADER) 
                m_c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            avcodec_open2( m_c, codec, NULL );

            m_stream->time_base=(AVRational){1, m_FPS};

            if (avio_open(&m_oc->pb, filename, AVIO_FLAG_WRITE) < 0)
            {
                printf( "Could not open '%s'\n", filename);
                exit(1);
            }            

            avformat_write_header(m_oc, NULL);
            m_frame = av_frame_alloc();
            if (!m_frame) {
                printf( "Could not allocate video frame\n");
                exit(1);
            }
            m_frame->format = m_c->pix_fmt;
            m_frame->width  = m_c->width;
            m_frame->height = m_c->height;
            ret = av_image_alloc(m_frame->data, m_frame->linesize, m_c->width, m_c->height, m_c->pix_fmt, 32);
            if (ret < 0) {
                printf("Could not allocate raw picture buffer\n");
                exit(1);
            }
        }

The ffmpeg_encoder_encode_frame is:

void VideoEncoder::ffmpeg_encoder_encode_frame(uint8_t *rgb) 
{
    int ret, got_output;
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb);
    av_init_packet(&m_pkt);
    m_pkt.data = NULL;
    m_pkt.size = 0;

    ret = avcodec_encode_video2(m_c, &m_pkt, m_frame, &got_output);
    if (ret < 0) {
        printf("Error encoding frame\n");
        exit(1);
    }
    if (got_output) 
    {

         av_packet_rescale_ts(&m_pkt,
                        (AVRational){1, m_FPS}, m_stream->time_base);
        m_pkt.stream_index = m_stream->index;
        int ret = av_interleaved_write_frame(m_oc, &m_pkt);

        av_packet_unref(&m_pkt);

    }

}

ffmpeg_encoder_finish code is:

void VideoEncoder::ffmpeg_encoder_finish(void) 
        {
            int got_output, ret;

            do {

                ret = avcodec_encode_video2(m_c, &m_pkt, NULL, &got_output);
                if (ret < 0) {
                    printf( "Error encoding frame\n");
                    exit(1);
                }
                if (got_output) {

                    av_packet_rescale_ts(&m_pkt,
                                (AVRational){1, m_FPS}, m_stream->time_base);
                    m_pkt.stream_index = m_stream->index;
                    int ret = av_interleaved_write_frame(m_oc, &m_pkt);

                    av_packet_unref(&m_pkt);
                }
            } while (got_output);

            av_write_trailer(m_oc);
            avio_closep(&m_oc->pb);

            avformat_free_context(m_oc);

            av_freep(&m_frame->data[0]);
            av_frame_free(&m_frame);

            av_packet_unref(&m_pkt);
            sws_freeContext(m_sws_context);
        }

This code runs multiple times in the loop. So my question is - what am I doing wrong? maybe ffmpeg is using some kind of internal buffering? If so, how to disable it? Because such an increase in memory usage is unacceptable at all.

like image 597
unresolved_external Avatar asked Jun 11 '18 12:06

unresolved_external


3 Answers

You didn't close encoder context. Add avcodec_close(m_c) to ffmpeg_encoder_finish().

See ffmpeg.org

User is required to call avcodec_close() and avformat_free_context() to clean up the allocation by avformat_new_stream().

Plus I don't see how m_c is allocated. Usually it is allocated with avcodec_alloc_context and must be deallocated with av_free (after closing of course).

like image 172
pure cuteness Avatar answered Oct 17 '22 18:10

pure cuteness


Don't use valgrind to check memory leaks for your own projects, use sanitizers, with these you can pin point the source of the leak. Check this out: Multi-Threaded Video Decoder Leaks Memory

Hope that helps.

like image 24
the kamilz Avatar answered Oct 17 '22 18:10

the kamilz


It's sufficient to call 'avcodec_free_context(m_c)', this procedure calls 'avcodec_close' and also de-allocates 'extradata'(if it's was allocated) and 'subtitle_header' (if it was allocated).

like image 1
Shevach Riabtsev Avatar answered Oct 17 '22 17:10

Shevach Riabtsev