内存分析(二) 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一样。

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

上一篇:1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains解决


下一篇:Mybaits-plus 开启SQL日志的两种方式