内存分析(二) AVFrame
2022/1/26 7:06:14
本文主要是介绍内存分析(二) 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
这篇关于内存分析(二) AVFrame的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01后台管理开发学习:新手入门指南
- 2024-11-01后台管理系统开发学习:新手入门教程
- 2024-11-01后台开发学习:从入门到实践的简单教程
- 2024-11-01后台综合解决方案学习:从入门到初级实战教程
- 2024-11-01接口模块封装学习入门教程
- 2024-11-01请求动作封装学习:新手入门教程
- 2024-11-01登录鉴权入门:新手必读指南
- 2024-11-01动态面包屑入门:轻松掌握导航设计技巧
- 2024-11-01动态权限入门:新手必读指南
- 2024-11-01动态主题处理入门:新手必读指南