摘要:FFmpeg中大多数数据存储比如AVFrame
,AVPacket
都是通过AVBufferRef
管理的,而承载数据的结构为AVBuffer
。本文主要通过FFmpeg源码来分析下FFmpeg中AVBuffer
相关的实现。
关键字:AVBuffer
、AVBufferPool
、AVBufferPool
1. AVBufferRef
1.1 AVBuffer
结构定义
AVBuffer
声明在libavutil/buffer_internal.h
文件中,而相关的操作函数定义在libavutil/buffer.c
中。先简单看下AVBuffer
的结构:
struct AVBuffer {
uint8_t *data; /**< data described by this buffer */
size_t size; /**< size of data in bytes */
atomic_uint refcount; //number of existing AVBufferRef instances referring to this **buffer**
void (*free)(void *opaque, uint8_t *data);//a callback for freeing the data
void *opaque;//an opaque pointer, to be used by the freeing callback
int flags;//A combination of AV_BUFFER_FLAG_*
int flags_internal;//A combination of BUFFER_FLAG_*
};
该结构比较简单,就是一个含有引用计数的数据类型:
data
:buffer中的数据指针;size
:数据的大小,即data
中数据的大小;refcount
:引用计数,无需多说,当引用计数为0时销毁对应的内存。该变量的操作是原子的,ffmpeg内部针对不同的编译期和平台实现了一套源自变量,具体就深入了,理解意思就行;free
:释放内存的函数指针,如果不指定的话会使用默认的函数指针av_buffer_default_free
释放内存;opaque
:user-defined的指针,用户可以通过该指针将数据传递给free
函数;flags
:目前只有一个值AV_BUFFER_FLAG_READONLY
;flags_internal
:目前只有一个值BUFFER_FLAG_REALLOCATABLE
;
1.2 AVBufferRef
结构定义
AVBufferRef
可以看做AVBuffer
的一个句柄,用来操作AVBuffer
:
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_t size;//Size of data in bytes.
} AVBufferRef;
AVBufferRef
结构比较简单,不详细描述,主要注意data
字段是指向其成员buffer.data
的。
1.3 操作函数
AVBufferRef *av_buffer_create(uint8_t *data, size_t size, void (*free)(void *opaque, uint8_t *data), void *opaque, int flags)
:该函数用来创建一个AVBufferRef
,具体就是申请内存函数根据参数初始化各个成员。需要注意的是返回的指针和其成员buffer
是在堆上的,以及AVBuferRef::data == AVBufferRef::buffer::data
;AVBufferRef *av_buffer_alloc(size_t size)
:通过av_buffer_create
创建对象,只不过参数都是默认值;AVBufferRef *av_buffer_allocz(size_t size)
:相比av_buffer_alloc
只是对内存进行了0初始化;AVBufferRef *av_buffer_ref(AVBufferRef *buf)
:FFmpeg中以_ref
结尾的API都是引用计数+1的含义,相反_unref
就是引用计数-1。但是需要注意两点:- 这里不是单纯的引用计数+1,而是
malloc
了一个AVBufferRef
作为返回值,然后浅拷贝输入参数; - 仅仅引用计数是原子的,类似
shared_ptr
,对象本身不线程安全;
- 这里不是单纯的引用计数+1,而是
void av_buffer_unref(AVBufferRef **buf)
:引用计数-1,释放内存,调用free
释放data
内存;int av_buffer_is_writable(const AVBufferRef *buf)
:当flags
设置了AV_BUFFER_FLAG_READONLY
时始终不可写,否则只有引用计数为1时才可写;int av_buffer_make_writable(AVBufferRef **pbuf)
:实现就是copy-on-write
,将pbuf
复制一份避免写共享的内存影响其他对象;int av_buffer_realloc(AVBufferRef **pbuf, size_t size)
:重新申请内存,如果传入的*pbuf
为空则create
一份。当输入的对象不可写或者不是BUFFER_FLAG_REALLOCATABLE
时会拷贝一份再realloc
;int av_buffer_replace(AVBufferRef **pdst, AVBufferRef *src)
:可以简单的理解就是*pds=*src
,当pdst
和src
指向同一个buffer
时,什么也不会做,实现类似C++中对象的拷贝构造函数;
2. AVBufferRef
2.1 结构定义
AVBufferPool
是一个单链表,用来管理其中的AVBuffer
。
typedef struct BufferPoolEntry {
uint8_t *data;
/*
* Backups of the original opaque/free of the AVBuffer corresponding to
* data. They will be used to free the buffer when the pool is freed.
*/
void *opaque;
void (*free)(void *opaque, uint8_t *data);
AVBufferPool *pool;
struct BufferPoolEntry *next;
} BufferPoolEntry;
从结构定义中可以看到BufferPollEntry
就是链表中的节点用来管理对应的AVBufferRef
。但是仔细看又发现其中并没有AVBuffer
的指针节点,而是保存了opaque
和free
函数指针,因为有这两个值我们就可以很顺利的释放对应的AVBuffer
,而pool中又保存了对应的allocate的函数指针能够创建对象。
data
:指向AVBuffer
的地址,因为没有保存AVBuffer
的地址所以需要一个指针来指向数据;opaque
:实现中BufferPoolEntry::opaque->AVBuffer::opaque->BufferPoolEntry
,这样能够保证通过AVBuffer
调用释放函数时找到管理自己的handle;free
:释放函数指针,实际上是固定的pool_release_buffer
;pool
:直接指向当前的内存池;next
:链表的节点指针;
struct AVBufferPool {
AVMutex mutex;
BufferPoolEntry *pool;
/*
* This is used to track when the pool is to be freed.
* The pointer to the pool itself held by the caller is considered to
* be one reference. Each buffer requested by the caller increases refcount
* by one, returning the buffer to the pool decreases it by one.
* refcount reaches zero when the buffer has been uninited AND all the
* buffers have been released, then it's safe to free the pool and all
* the buffers in it.
*/
atomic_uint refcount;
size_t size;
void *opaque;
AVBufferRef* (*alloc)(size_t size);
AVBufferRef* (*alloc2)(void *opaque, size_t size);
void (*pool_free)(void *opaque);
};
AVBufferPool
就是内存池的管理对象:
mutex
:线程安全用的锁;opaque
:pool_free
函数指针的第一个参数;alloc
:默认会被设置成av_buffer_alloc
;alloc2
:自定义的分配函数,申请AVBufferRef
时优先使用,没有指定则使用alloc
;pool_free
:释放内存池的回调;size
:单个对象的大小,即整个内存池管理的对象大小是相同的;refcount
:当前从内存池中分配但是并没有在内存池链表中的节点的引用计数之和。
2.2 接口实现
AVBufferPool *av_buffer_pool_init2(size_t size, void *opaque, AVBufferRef* (*alloc)(void *opaque, size_t size), void (*pool_free)(void *opaque))
:初始化pool的链表,根据参数设置相应的成员,alloc2
会设置输入的参数alloc
,而-alloc
会设置成av_buffer_alloc
;AVBufferPool *av_buffer_pool_init(size_t size, AVBufferRef* (*alloc)(size_t size))
:只会申请pool的内存设置相关参数,如果alloc为空则pool中的alloc
设置为av_buffer_alloc
;void av_buffer_pool_uninit(AVBufferPool **ppool)
:销毁pool,如果引用计数为1则销毁对象(不知道为什么命名没有类似_unref
,可能因为没有ref
吧);AVBufferRef *av_buffer_pool_get(AVBufferPool *pool)
:获取一个AVBufferRef
该内存是通过pool管理的。
2.3 内存管理
AVBufferPool
是一个以单链表形式实现的栈式内存池。其基本过程就是如果链表非空则出栈头结点,否则申请内存时就创建一个AVBUfferRef
返回给用户,用户释放时就会将节点入栈到头结点,并且申请和释放内存是线程安全的。AVBufferPool
就是一个空闲链表栈,通过指定对应的AVBufferRef
的释放函数为pool_release_buffer
来对内存进行管理。
对于一个刚初始化的内存池,连续申请两个Buffer就是下面这种状态:
连续申请3个buffer,再释放2个就是下面这种状态(红色为链表的连接线):