zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    UML期末复习题——2.7:UML Sequence Diagram
    UML期末复习题——2.6:Package Diagram
    UML期末复习题——2.5:System Sequence Diagram & Post-condition
    UML期末复习题——2.4:Domain Model
    UML期末复习题——2.3:UML State Diagram
    UML期末复习题——2.2:UML Activity Diagram.
    UML期末复习题——2.1:Use Case Diagram
    UML期末复习题
    《C++之那些年踩过的坑(附录一)》
    《C++之那些年踩过的坑(三)》
  • 原文地址:https://www.cnblogs.com/lidabo/p/15040688.html
Copyright © 2011-2022 走看看