AVFrame的数据填充方式

我们知道平时使用AVFrame这个数据结构时,首先需要调用av_frame_alloc()对其进行初始化,初始化后的数据里data数组和buf数组都是空的,也就是说初始化后不会填充一个默认图像数据(毕竟初始化时不需要知道图片的任何信息)。

 

我平时使用的AVframe填充数据的方式为av_image_fill_arrays,先自己申请一段内存空间tmpBuffer,然后填充到目标frame中,但是这种方式常常会由于疏忽释放tmpBuffer而导致内存泄漏。

//填充
int frameSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, width, height, 1);
uint8_t* tmpBuffer = (uint8_t*)av_malloc(frameSize * sizeof(uint8_t));

AVFrame* frame = av_frame_alloc();
frame->width = width;
frame->height = height;
frame->format = AV_PIX_FMT_RGB24;
ret = av_image_fill_arrays(frame->data, frame->linesize, tmpBuffer, AV_PIX_FMT_RGB24, width, height, 1);

//释放
av_free(tmpBuffer);
av_frame_free(&frame);

最近学会一种新的填充方式,用av_frame_get_buffer的方式,对于视频frame,只用在调用函数之前设置好图像的宽高和图像格式等信息,音频设置好nb_samples,channel_layout 和采样格式等信息,此函数将填充AVFrame.data和AVFrame.buf数组,并在必要时分配和填充AVFrame.extended_data和AVFrame.extended_buf。而且采用这种方式填充AVFrame的data后,在最后释放时调用av_frame_free可以直接释放掉AVFrame的所有内存,不会出现内存泄漏的问题。

//填充
AVFrame* frame = av_frame_alloc();
frame->width = width;
frame->height = height;
frame->format = AV_PIX_FMT_RGB24;
av_frame_get_buffer(frame, 1);

//释放
av_frame_free(&frame)

这里产生一个疑问,为什么使用了av_image_fill_arrays之后,在调用av_frame_free释放内存时,并不能释放填充进去的tmpBuffer,只能在后面通过手动释放,这与av_frame_get_buffer的方式有什么不一样。

av_frame_free
首先分析av_frame_free函数

void av_frame_free(AVFrame **frame)
{
    if (!frame || !*frame)
        return;

    av_frame_unref(*frame);
    av_freep(frame);
}

释放的过程较简单,av_frame_unref函数是对AVFrame结构体内部一些指针数据进行释放,av_freep这个函数是用来释放AVFrame结构体自身的内存的。

void av_frame_unref(AVFrame *frame)
{
    int i;

    if (!frame)
        return;

    wipe_side_data(frame);

    for (i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++)
        av_buffer_unref(&frame->buf[i]);
    for (i = 0; i < frame->nb_extended_buf; i++)
        av_buffer_unref(&frame->extended_buf[i]);
    av_freep(&frame->extended_buf);
    av_dict_free(&frame->metadata);
#if FF_API_FRAME_QP
FF_DISABLE_DEPRECATION_WARNINGS
    av_buffer_unref(&frame->qp_table_buf);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    av_buffer_unref(&frame->hw_frames_ctx);

    av_buffer_unref(&frame->opaque_ref);
    av_buffer_unref(&frame->private_ref);

    get_frame_defaults(frame);
}

这里的av_buffer_unref作用是将buf替换为NULL。代码中并没有直接释放data数组,而只对buf进行了操作。我们需要关注的是AVFrame中buf这个成员。

/**
     * AVBuffer references backing the data for this frame. If all elements of
     * this array are NULL, then this frame is not reference counted. This array
     * must be filled contiguously -- if buf[i] is non-NULL then buf[j] must
     * also be non-NULL for all j < i.
     *
     * There may be at most one AVBuffer per data plane, so for video this array
     * always contains all the references. For planar audio with more than
     * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in
     * this array. Then the extra AVBufferRef pointers are stored in the
     * extended_buf array.
     */
    AVBufferRef *buf[AV_NUM_DATA_POINTERS];

这个buf也是用来标记是否是ref的,注意这里buf是一个数组,数组名buf本身不为null,但是子元素值默认是null。av_frame_unref函数就是针对frame的buf数组逐个调用av_buffer_unref。

av_frame_get_buffer
在使用av_frame_get_buffer函数进行填充时,调用了get_video_buffer函数

int av_frame_get_buffer(AVFrame *frame, int align)
{
    if (frame->format < 0)
        return AVERROR(EINVAL);

    if (frame->width > 0 && frame->height > 0)
        return get_video_buffer(frame, align);        //line 343
    else if (frame->nb_samples > 0 && (frame->channel_layout || frame->channels > 0))
        return get_audio_buffer(frame, align);

    return AVERROR(EINVAL);
}

static int get_video_buffer(AVFrame *frame, int align)
{
    ...

    if (!frame->linesize[0]) {        //line 226
        ...

        for(i=1; i<=align; i+=i) {
            ret = av_image_fill_linesizes(frame->linesize, frame->format,
                                          FFALIGN(frame->width, i));
            ...
        }

        for (i = 0; i < 4 && frame->linesize[i]; i++)
            frame->linesize[i] = FFALIGN(frame->linesize[i], align);
    }

    ...

    frame->buf[0] = av_buffer_alloc(total_size);        //line 258
    ...

    if ((ret = av_image_fill_pointers(frame->data, frame->format, padded_height,
                                      frame->buf[0]->data, frame->linesize)) < 0)        //line 264
        goto fail;

    for (i = 1; i < 4; i++) {
        if (frame->data[i])
            frame->data[i] += i * plane_padding;
    }

    ...
}

int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,
                           uint8_t *ptr, const int linesizes[4])
{
    int i, ret;
    ptrdiff_t linesizes1[4];
    size_t sizes[4];

    memset(data     , 0, sizeof(data[0])*4);

    for (i = 0; i < 4; i++)
        linesizes1[i] = linesizes[i];

    ret = av_image_fill_plane_sizes(sizes, pix_fmt, height, linesizes1);
    if (ret < 0)
        return ret;

    ret = 0;
    for (i = 0; i < 4; i++) {
        if (sizes[i] > INT_MAX - ret)
            return AVERROR(EINVAL);
        ret += sizes[i];
    }

    data[0] = ptr;                //line 169
    for (i = 1; i < 4 && sizes[i]; i++)
        data[i] = data[i - 1] + sizes[i - 1];

    return ret;
}

在frame.c文件的343行,判断如果为视频frame,则调用get_video_buffer函数初始化frame。

在get_video_buffer的226行,先初始化linesize,258行对buf[0]分配空间,264行调用av_image_fill_pointers对frame中的data数组做初始化。

在av_image_fill_pointers中的169行将buf[0]->data的指针幅值给了AVFrame结构体的data,也就是说AVFrame中的buf和data是指向的同一块内存地址。

通过av_frame_get_buffer得到的AVFrame调用av_frame_free时,释放buf的同时,data也就被释放了。

av_image_fill_arrays
如果是通过调用av_image_fill_arrays来填充

int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
                         const uint8_t *src, enum AVPixelFormat pix_fmt,
                         int width, int height, int align)
{
    int ret, i;

    ret = av_image_check_size(width, height, 0, NULL);
    if (ret < 0)
        return ret;

    ret = av_image_fill_linesizes(dst_linesize, pix_fmt, width);
    if (ret < 0)
        return ret;

    for (i = 0; i < 4; i++)
        dst_linesize[i] = FFALIGN(dst_linesize[i], align);

    return av_image_fill_pointers(dst_data, pix_fmt, height, (uint8_t *)src, dst_linesize);
}

最后调用av_image_fill_pointers时只是把我们自己的图像内存填充到AVFrame的data,并没有对buf进行操作,最后释放内存时,data的内存并没有被释放掉。所以还需要自己手动释放。

总结:
在AVFrame结构体中,buf和data数组所指向的是同一块数据区域,在释放内存时调用av_frame_free时,只会释放buf。调用av_frame_get_buffer填充时,ffmpeg会将这两部分一起初始化,所以释放时只释放buf,data部分也会一起释放。调用av_image_fill_arrays填充时,只会更改AVFrame中的data部分,不会改变buf,所以释放时data不会随着buf释放,需要自己手动释放这部分空间。

结论:
av_frame_get_buffer可以理解为自动为AVFrame分配空间,而av_image_fill_arrays可以理解为手动填充。

在初始化一个AVFrame时,如果需要填充自己准备好的数据(如捕获到的屏幕图像数据),采用av_image_fill_arrays,但是在使用完后,一定要注意释放data

如果用一个AVFrame初始化是为了继承sws转换的数据,可以选择av_frame_get_buffer进行初始化,这样在释放时直接调用av_frame_free即可,不用担心内存泄漏问题。

如有错误,欢迎大家指正。

参考:

内存分析(二) AVFrame

https://blog.csdn.net/oooooome/article/details/111993911?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-8.pc_relevant_baidujshouduan&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-8.pc_relevant_baidujshouduan

上一篇:如何搭建16K高清的公网视频传输系统?


下一篇:ffplay源码分析(一):PacketQueue中AVPacket和AVFrame关系