我们知道平时使用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即可,不用担心内存泄漏问题。
如有错误,欢迎大家指正。
参考: