ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

内存分析(二) AVFrame

2022-01-25 18:05:55  阅读:231  来源: 互联网

标签:分析 buffer data frame decode 内存 av AVFrame buf


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一样。

 
参考:https://blog.csdn.net/bixinwei22/article/details/103952925

标签:分析,buffer,data,frame,decode,内存,av,AVFrame,buf
来源: https://www.cnblogs.com/lidabo/p/15843881.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有