1 FFmpeg内存管理原理
数据包管理过程中当数据转移到新的数据包时存在两种操作一种是数据包之间相互独立,当新创建一份数据包时,需要将原来的数据重新申请一个数据空间并且将数据拷贝到新的数据包中,具体过程如下图所示。这种数据包的管理优势是在于数据之间相互独立,不会存在数据干扰的问题,但是缺点也很明显就是消耗的内存大大增加,同时数据之间的拷贝也是耗时的。
另一种内存管理的方式是,只新增数据包对象,用于管理数据对象,对于数据本身采用同一个内存空间进行管理,当所有的内存引用为0时释放这片内存空间,具体如下图所示。这种方式的优势就是数据占用内存最小化,同时大大减少数据拷贝的耗时问题;缺点是增加了内存管理的难度。FFmpeg正是采用这种内存管理的方式进行数据包和数据帧的管理。
2 FFmpeg内存管理实现
2.1 AVPacket实现
核心API | 功能 |
---|---|
av_packet_alloc | 申请AVPacket |
av_packet_free | 释放AVPacket |
av_init_packet | 初始化AVPacket |
av_new_packet | 申请AVBufferRef和AVBuffer数据空间,引用计数设置为1 |
av_buffer_ref | 新申请AVBufferRef,AVBuffer引用计数加一 |
av_buffer_unref | 释放AVBufferRef,AVBuffer引用计数减一 |
AVPacket 内存模型如下图所示,AVBuffer存放具体数据,AVBufferRef用于管理AVBuffer,AVPacket 是实际对外数据包体。
引用计数加一和引用计数减一一个逆过程,具体如下图所示,加一主要是增加AVBufferRef和引用计数;减一主要是释放AVBufferRef和引用计数减少。当引用计数减到0时,需要释放AVBuffer数据区。
源码走读如下所示:
//1 申请AVPacket内存,并初始化
AVPacket *av_packet_alloc(void)
{
//1 分配AVPacket 内存
//2 将AVPacket解引用
av_packet_unref
{
//AVPacket 初始化
av_init_packet
}
}
//释放AVPacket内存,引用减1
void av_packet_free(AVPacket **pkt);
//初始化AVPacket
void av_init_packet(AVPacket *pkt);
//新申请AVPacket的数据空间
int av_new_packet(AVPacket *pkt, int size)
{
//分配AVBufferRef内存空间
packet_alloc
{
av_buffer_realloc
{
//分配AVBufferRef内存
av_buffer_create
{
//先分配AVBuffer,并且注册释放回调函数,refcount引用计数加一
//再分配AVBufferRef,并将指针与AVBuffer绑定
}
}
}
}
//引用计数加一
AVBufferRef *av_buffer_ref(AVBufferRef *buf)
{
//新分配一个AVBufferRef;将旧的AVBufferRef复制给它,同时引用计数加1
*ret = *buf;//结构体赋值的方式,浅拷贝
}
//引用计数减一
void av_buffer_unref(AVBufferRef **buf)
{
//AVBufferRef释放,同时AVBuffer计数减一,当计数为1时释放
buffer_replace
{
//利用回调释放数据
b->free
}
}
2.2 AVFrame实现
核心API | 功能 |
---|---|
av_frame_alloc | 申请AVFrame |
av_frame_free | 释放AVFrame |
av_frame_get_buffer | 申请AVBufferRef和AVFrame数据空间 |
av_frame_ref | 新申请AVBufferRef,AVFrame引用计数加一 |
av_frame_unref | 释放AVBufferRef,AVFrame引用计数减一 |
av_frame_move_ref | AVFrame转移引用计数 |
AVFrame实现原理与AVPacket 一致,都是利用AVBufferRef进行引用计数的管理,同时数据存储在AVBuffer中,只有保存一份,av_frame_ref负责将引用计数加一,av_frame_unref引用计数减一,当引用计数减到0后,进行数据释放。
3 FFmpeg内存接口实践
利用qt进行FFmpeg环境构建,首先是下载FFmpeg依赖库和头文件,这里使用的是win32的已经编译好的FFmpeg库,ffmpeg-4.2.1-win32-dev头文件和库。
首先构建一个空的c项目,同时创建好需要测试的avpacket文件,在依赖项.pro文件需要添加对FFmpeg的依赖,包括两部分一个是头文件路径,第二是库路径。具体如下所示。
win32{
INCLUDEPATH += $$PWD/ffmpeg-4.2.1-win32-dev/include
LIBS += $$PWD/ffmpeg-4.2.1-win32-dev/lib/avformat.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avcodec.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avdevice.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avfilter.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avutil.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/postproc.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/swresample.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/swscale.lib
}
注意ffmpeg-4.2.1-win32-dev文件放到项目的根路径如下所示。
接下来是测试FFmpeg是否可以正常使用,具体测试程序如下,获取FFmpeg的版本。
执行效果如下所示:
1 实现简单的内存分配和释放操作
需要注意的点:
- av_packet_alloc内部会进行初始化,所以不需要再调用av_init_packet初始化;
- av_packet_free内部会进行解引用,所以不需要再调用av_packet_unref;
- av_init_packet一定不能在av_new_packet之后使用会造成内存泄漏
- 对同一个AVPacket进行多次av_packet_ref而没有av_packet_unref会造成内存泄漏。
av_new_packet用于分配到:AVBufferRef引用管理,同时会分配数据部分AVBuffer,同时引用计数设置refcount为1.
经过av_packet_move_ref之后AVBufferRef整体被转移到pkt2中,同时会调用av_init_packet初始化pkt;
经过av_packet_clone之后会主动分配一个AVPacket因此不需要事先分配alloc一个;clone后pkt和pkt2数据区都是指向同一个AVFrame数据区。
经过av_packet_ref后,pkt2首先需要av_packet_alloc一个AVPacket,然后经过此函数后会分配AVBufferRef和将数据指向同一个AVFrame数据区。
AVFrame帧的操作与packet分配原理一致,使用方式也类似。主要包括几个步骤一个是av_frame_alloc分配一个AVFrame帧,然后稍微有点不同的是需要为帧进行初始化,然后来确认是视频帧还是音频帧。第二步是av_frame_get_buffer获取帧的数据区也就是AVBufferRef和AVBuffer这里有一个比较特殊的地方是这里预制了一个长度为8的AVBufferRef指针数组,主要是用于不同的数据存储格式不一样需要多个内存空间。最后是确保AVFrame是可写的,在进行数据操作。释放利用av_frame_free。
获取分配数据帧的需要设置相关的参数,帧的数据格式;如果是视频需要设置分辨率;如果是音频需要设置步长,通道数等参数;主要原因是分配数据时根据这些参数计算分配数据空间大小。