我们知道,ffmpeg是用C语言开发的,C语言最棘手的一个问题就是对内存的管理。而对于作为专门进行媒体处理的ffmpeg来说,又需要大量地使用动态内存的分配和释放,因此,ffmpeg实现了一个智能缓冲来对动态内存进行管理。
ffmpeg的智能缓冲其原理与C++ 11的智能指针shared_ptr是相同的,即通过一个计数值纪录对当前缓冲区引用次数,当引用次数减少为0时,释放缓冲所占用的内存。其中,涉及到的关键数据结构有
libavutil/buffer.h
/**
* A reference to a data buffer.
*
* The size of this struct is not a part of the public ABI and it is not meant
* to be allocated directly.
*/
typedef struct AVBufferRef {
AVBuffer *buffer;
/**
* The data buffer. It is considered writable if and only if
* this is the only reference to the buffer, in which case
* av_buffer_is_writable() returns 1.
*/
uint8_t *data;
/**
* Size of data in bytes.
*/
int size;
} AVBufferRef;
AVBufferRef为智能buffer对外的数据结构,相当于C++里的智能指针对象,该结构体只是一个外壳,实际的计数值和内存指针保存在AVBuffer中。为了使用方便,AVBufferRef中也保存了一份buffer内存的指针和大小,但是它们只不过是AVBuffer 中的对应成员的拷贝而已。
struct AVBuffer {
uint8_t *data; /**< data described by this buffer */
int size; /**< size of data in bytes */
/**
* number of existing AVBufferRef instances referring to this buffer
*/
volatile int refcount;
/**
* a callback for freeing the data
*/
void (*free)(void *opaque, uint8_t *data);
/**
* an opaque pointer, to be used by the freeing callback
*/
void *opaque;
/**
* A combination of BUFFER_FLAG_*
*/
int flags;
};
AVBuffer 保存了buffer内存的指针和大小,以及对buffer对象的引用计数值。
下面是对buffer进行操作的接口实现:
libavutil/buffer.c
AVBufferRef *av_buffer_alloc(int size)
{
AVBufferRef *ret = NULL;
uint8_t *data = NULL;
data = av_malloc(size); //分配buffer内存
if (!data)
return NULL;
ret = av_buffer_create(data, size, av_buffer_default_free, NULL, 0); //创建buffer对象
if (!ret)
av_freep(&data);
return ret;
}
av_buffer_alloc创建buffer对象,并分配buffer所使用的内存。
AVBufferRef *av_buffer_create(uint8_t *data, int size,
void (*free)(void *opaque, uint8_t *data),
void *opaque, int flags)
{
AVBufferRef *ref = NULL;
AVBuffer *buf = NULL;
buf = av_mallocz(sizeof(*buf));//动态创建buffer对象
if (!buf)
return NULL;
buf->data = data;//buffer对象管理的实际内存
buf->size = size;//buffer对象管理的内存大小
buf->free = free ? free : av_buffer_default_free;//相当于析构函数,
//默认为直接释放data所指向的内存
buf->opaque = opaque;
buf->refcount = 1;//初始化引用计数为1
if (flags & AV_BUFFER_FLAG_READONLY)
buf->flags |= BUFFER_FLAG_READONLY;
ref = av_mallocz(sizeof(*ref));//动态创建AVBufferRef 对象
if (!ref) {
av_freep(&buf);
return NULL;
}
ref->buffer = buf;//AVBufferRef为AVBuffer的管理者
ref->data = data;//复制AVBuffer中的相关属性
ref->size = size;
return ref;
}
从上面的代码可以看出,在创建buffer对象时,首先会创建一个AVBuffer对象,其中保存着buffer内存的指针,并将其引用计数初始化为1,然后,创建一个AVBufferRef对象对AVBuffer对象进行管理。注意,对于同一个buffer对象,AVBuffer对象只会创建一次,但是每个对该buffer引用都需要对应一个单独AVBufferRef对象,这些对象指向同一个AVBuffer对象,并且随着引用的创建和销毁相应地更新AVBuffer对象中的引用计数值。
int av_buffer_realloc(AVBufferRef **pbuf, int size)
{
AVBufferRef *buf = *pbuf;
uint8_t *tmp;
if (!buf) {
/* allocate a new buffer with av_realloc(), so it will be reallocatable
* later */
uint8_t *data = av_realloc(NULL, size);
if (!data)
return AVERROR(ENOMEM);
buf = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
if (!buf) {
av_freep(&data);
return AVERROR(ENOMEM);
}
buf->buffer->flags |= BUFFER_FLAG_REALLOCATABLE;
*pbuf = buf;
return 0;
} else if (buf->size == size)
return 0;
if (!(buf->buffer->flags & BUFFER_FLAG_REALLOCATABLE) ||
!av_buffer_is_writable(buf)) {
/* cannot realloc, allocate a new reallocable buffer and copy data */
AVBufferRef *new = NULL;
av_buffer_realloc(&new, size);
if (!new)
return AVERROR(ENOMEM);
memcpy(new->data, buf->data, FFMIN(size, buf->size));
buffer_replace(pbuf, &new);
return 0;
}
tmp = av_realloc(buf->buffer->data, size);
if (!tmp)
return AVERROR(ENOMEM);
buf->buffer->data = buf->data = tmp;
buf->buffer->size = buf->size = size;
return 0;
}
在av_buffer_realloc中,如果pbuf指向一个空的AVBufferRef指针,则为其创建一个新的AVBufferRef对象,并分配buffer内存。如果pbuf已经指向了一个已存在的AVBufferRef对象,并且其buffer大小与输入的size不相等,则为其重新分配buffer内存。
AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{
AVBufferRef *ret = av_mallocz(sizeof(*ret));
if (!ret)
return NULL;
*ret = *buf;
avpriv_atomic_int_add_and_fetch(&buf->buffer->refcount, 1);
return ret;
}
当需要对一个buffer对象进行引用时,调用av_buffer_ref接口,其输入是被引用的AVBufferRef指针,返回一个新的AVBufferRef,它们指向相同的AVBuffer对象,并且将引用计数加1。
void av_buffer_unref(AVBufferRef **buf)
{
if (!buf || !*buf)
return;
buffer_replace(buf, NULL);
}
static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
AVBuffer *b;
b = (*dst)->buffer;
if (src) {
**dst = **src;
av_freep(src);
} else
av_freep(dst);
if (!avpriv_atomic_int_add_and_fetch(&b->refcount, -1)) {
b->free(b->opaque, b->data);
av_freep(&b);
}
}
当对一个buffer的引用结束时,需要调用av_buffer_unref接口。首先会将该引用的AVBufferRef对象释放,并且将引用计数减1,如果引用计数为0了,则调用buffer注册的析构函数(默认为直接释放buffer内存),并且释放AVBuffer所对应的内存。
下面以AVPacket为例看一下上面所讲的智能内存是如何使用的。
libavcodec/avpacket.c
int av_new_packet(AVPacket *pkt, int size)
{
AVBufferRef *buf = NULL;
int ret = packet_alloc(&buf, size);
if (ret < 0)
return ret;
av_init_packet(pkt);
pkt->buf = buf;
pkt->data = buf->data;
pkt->size = size;
return 0;
}
static int packet_alloc(AVBufferRef **buf, int size)
{
int ret;
if (size < 0 || size >= INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
return AVERROR(EINVAL);
//对于第一次创建,buf为空
ret = av_buffer_realloc(buf, size + AV_INPUT_BUFFER_PADDING_SIZE);
if (ret < 0)
return ret;
memset((*buf)->data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
return 0;
}
int av_packet_ref(AVPacket *dst, const AVPacket *src)
{
int ret;
//拷贝可选数据成员
ret = av_packet_copy_props(dst, src);
if (ret < 0)
return ret;
if (!src->buf) {//如果没有buffer对象,创建一个
ret = packet_alloc(&dst->buf, src->size);
if (ret < 0)
goto fail;
memcpy(dst->buf->data, src->data, src->size);
} else {//已有buffer对象,创建一个新的引用
dst->buf = av_buffer_ref(src->buf);
if (!dst->buf) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
dst->size = src->size;
dst->data = dst->buf->data;//packet的数据指针直接指向buffer的数据指针
return 0;
fail:
av_packet_free_side_data(dst);
return ret;
}
void av_packet_unref(AVPacket *pkt)
{
av_packet_free_side_data(pkt);//释放可选属性
av_buffer_unref(&pkt->buf);//销毁该引用
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
}
总结:通过智能buffer的引入,免去了手动分配和释放内存的痛苦,可以有效减少bug的发生。ffmpeg的智能buffer的实现可以作为C语言下智能指针实现的一个示例。