zoukankan      html  css  js  c++  java
  • 内存分析 AVFrame

    AVFrame结构体内有很多成员变量,我们肯定不可能都分析,只关心我们需要的,从实际应用场景出发,用到avframe只要有4个场景,1,init,2,decode,3 encode 4,free

    从decode说起,decode涉及的函数是avcodec_decode_video2(),这个函数代码较长,我就不粘了,其实我们关心的点很简单,

    它就做了2件事,先调用了av_frame_unref(picture); 然后decode, 填充picture

    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);
        av_buffer_unref(&frame->qp_table_buf);
     
        get_frame_defaults(frame);
    }
    这里确定了,我们需要关心的点是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_buffer_unref之前也讲过了。就是引用计数变为0,就释放data,否则只释放结构体自身内存。 注意,前提是buf[i] 不能是null.

    基本都和avpacket里面一样。

    AVFrame *av_frame_alloc(void)
    {
        AVFrame *frame = av_mallocz(sizeof(*frame));
     
        if (!frame)
            return NULL;
     
        frame->extended_data = NULL;
        get_frame_defaults(frame);
     
        return frame;
    }
     
    void av_frame_free(AVFrame **frame)
    {
        if (!frame || !*frame)
            return;
     
        av_frame_unref(*frame);
        av_freep(frame);
     
    }
    注意看av_frame_free(),多了一个av_freep(frame),这说明这个函数是带了释放avframe结构体自身内存的。(对比下avpacket的释放函数)

    再看初始化,buf默认是0,即初始化函数得到的是一个unref的frame 

    我们知道传给decode函数的frame是只需要初始化函数初始化就行。不需要设置宽高等。

    但是encode函数传入的frame需要额外设置宽高,格式,还要设置data,linesize.

    一般有两种设置方式。

    AVFrame *enc_frame_v = av_frame_alloc();
    enc_frame_v->width = 1280;
    enc_frame_v->height = 720;
    enc_frame_v->format = AV_PIX_FMT_YUV420P;
    /**
    *方式一
    av_frame_get_buffer(enc_frame_v,1); 
    */
     
    /**
    *方式二
    uint8_t *enc_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, 1280, 720));
    avpicture_fill((AVPicture *)enc_frame_v, enc_buffer, AV_PIX_FMT_YUV420P, 1280, 720);
    */
    设置方式的不同,直接关系到如何释放frame,确保内存不泄露。很重要。

    先看方式一,涉及到的函数是av_frame_get_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);
        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)
    {
        const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
        int ret, i;
     
        if (!desc)
            return AVERROR(EINVAL);
     
        if ((ret = av_image_check_size(frame->width, frame->height, 0, NULL)) < 0)
            return ret;
     
        if (!frame->linesize[0]) {
            for(i=1; i<=align; i+=i) {
                ret = av_image_fill_linesizes(frame->linesize, frame->format,
                                              FFALIGN(frame->width, i));
                if (ret < 0)
                    return ret;
                if (!(frame->linesize[0] & (align-1)))
                    break;
            }
     
            for (i = 0; i < 4 && frame->linesize[i]; i++)
                frame->linesize[i] = FFALIGN(frame->linesize[i], align);
        }
     
        for (i = 0; i < 4 && frame->linesize[i]; i++) {
            int h = FFALIGN(frame->height, 32);
            if (i == 1 || i == 2)
                h = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
     
            frame->buf[i] = av_buffer_alloc(frame->linesize[i] * h + 16 + 16/*STRIDE_ALIGN*/ - 1);
            if (!frame->buf[i])
                goto fail;
     
            frame->data[i] = frame->buf[i]->data;
        }
        if (desc->flags & AV_PIX_FMT_FLAG_PAL || desc->flags & AV_PIX_FMT_FLAG_PSEUDOPAL) {
            av_buffer_unref(&frame->buf[1]);
            frame->buf[1] = av_buffer_alloc(1024);
            if (!frame->buf[1])
                goto fail;
            frame->data[1] = frame->buf[1]->data;
        }
     
        frame->extended_data = frame->data;
     
        return 0;
    fail:
        av_frame_unref(frame);
        return AVERROR(ENOMEM);
    }
    从函数分析,可以看出,video使用av_frame_get_buffer,最后一个参数必须传1,只有传1,linesize才会被初始化,buf子元素才会被初始化,且frame->buf[i] 是分别的alloc的内存空间,说明这个av_frame_get_buffer函数得到的frame的data数组,内存不是连续的(假设pix_fmt=yuv420p)。我们还看到frame->data[i]=frame->buf[i]->data,这里也看出了frame的data和buf的data之间的联系。

    这说明什么呢,说明了通过方式一,得到的是ref的frame,最后av_frame_free可以完美释放frame和它的data。

    但是我们还需要考量一点,就是frame进入decode函数后,data的内存空间地址没有变。即decode会不会给它重新分配内存空间。

    场景1: 传入decode的frame是局部变量,初始化只有av_frame_alloc, 每次使用完,我们即调用av_frame_free, 从这个流程可以推断出,decode肯定是会给frame分配空间的。

    场景2:传入decode的frame的是全局变量,那么每次使用完,我们可以调用av_frame_free吗,肯定不行,因为前面提过,av_frame_free会把frame置为NULL, 那么问题来了,我们不释放,复用frame,而decode是会给frame的data重新分配空间吗。

    经过debug方式测验发现,是会重新分配的,即我们在decode函数调用前后分别打印frame->data[0]的地址。发现是不同的。

    从decode函数源码角度来解释也好解释,如果复用frame,每次进decode,之前讲过,会先走unref函数,这个函数直接就把buf数组的data释放了,所以重新分配内存空间肯定是地址会变的。也正是因为decode里面每次进来都先走unref函数,确保了frame使用全局的方式,不用担心每次重新分配内存空间,而内存泄漏。我们也不用关心复用时要去释放。

    avcodec_decode_audio4规则也是一样。

    再回到方式二,用avpicture_fill方式,我们自己分配内存空间,填充frame的data和linesize

    这种方式好处是保证了frame的data是内存连续的。但是avpicture_fill 并没有填充buf[i] ,

    也就是说av_frame_free并不能释放data. 

    该方式需要我们手动释放data

    uint8_t* p=frame->data[0];
    av_free(p);
    av_frame_free(&frame);
    av_free()要和上文的av_malloc()对应,如果上文用到的是malloc(),这里就用free()

    最后是encode函数。avcodec_encode_video2()函数传入的frame, 只是作为数据源,不会被encode函数修改data,所以进去是什么样,出来还是什么样。avcodec_encode_audio2也是一样。

    下面分析audio的decode,同样frame也是两种方式。

    AVFrame* enc_frame_a = av_frame_alloc();
    enc_frame_a->nb_samples = 1024;
    enc_frame_a->format = AV_SAMPLE_FMT_S16;
    enc_frame_a->channels = 2;
    enc_frame_a->channel_layout = av_get_default_channel_layout(2);
    enc_frame_a->sample_rate =44100;
    /**
    *方式一
    av_frame_get_buffer(enc_frame_a,1); 
    */
     
    /**
    *方式二
    int nPcmLen = av_samples_get_buffer_size(NULL, 2, 1024, AV_SAMPLE_FMT_S16, 1);
    uint8_t* adata = (uint8_t*)av_malloc(nPcmLen);
    avcodec_fill_audio_frame(enc_Aframe,2, S16, (const uint8_t*)adata, nPcmLen, 1);
    */
    av_frame_get_buffer此时调用的是get_audio_buffer(AVFrame *frame, int align) ,最后一个参数align标识是否使用内存对齐,

    和video不同,audio这里可以传0,也可以传1 ; 0是默认对齐,1表示不对齐。(对齐可以提高存取数据的性能,不对齐可以精准计算size)

    其他规则,和video一样。

    avcodec_fill_audio_frame,规则也和video一样。

  • 相关阅读:
    react中this.setState的理解
    expo:wraning remotedebugger is in a...cause apps to perform slowly
    expo:java.net.socketExcrption:No route to host
    redux的中间件
    js中this的指向
    微信小程序之模板/组件的使用
    js判断手机端
    微信小程序轮播图
    scrollbar样式设置
    CSS绝对定位元素居中的几种方法
  • 原文地址:https://www.cnblogs.com/lidabo/p/15040730.html
Copyright © 2011-2022 走看看