文章目录
- 一、FFmpeg 内存模型
- 1、AVPacket 队列 和 AVFrame 队列
- 2、解码操作涉及的函数
- 3、AVPacket 数据的 深拷贝 和 浅拷贝
- 4、AVPacket 数据的 浅拷贝 引用计数器机制
- 5、AVPacket 中 存储 音视频数据 细节
FFmpeg 4.0 版本源码地址 :
- GitHub : https://github.com/FFmpeg/FFmpeg/tree/release/4.0
- GitCode : https://gitcode.com/gh_mirrors/ff/FFmpeg/tree/release/4.0
一、FFmpeg 内存模型
1、AVPacket 队列 和 AVFrame 队列
FFmpeg 打开媒体文件后 , 调用 av_read_frame 函数 从 解复用器 中 获取到 音频包 / 视频包 AVPacket , 然后将读取到的数据包放入 AVPacket 队列中 ;
将 AVPacket 队列中的元素 通过 调用 avcodec_send_packet 和 avcodec_receive_frame 函数 , 获取到 AVFrame 数据帧 , 然后将 AVFrame 数据帧放入到 AVFrame 队列中 ,
如下图所示 :
这里涉及到两个队列 , AVPacket 队列 和 AVFrame 队列 , 这是 复用 / 解复用器 , 编码器 / 解码器 的 核心操作数据 ,
AVPacket 队列 的 入队操作 和 出队操作 :
- 入队 : 调用 av_read_frame 函数
- 出队 : 调用 avcodec_send_packet 函数
AVFrame 队列 的 入队操作 和 出队操作 :
- 入队 : 调用 avcodec_receive_frame 函数
- 出队 : 从 AVFrame 队列中取出数据进行播放 ;
2、解码操作涉及的函数
上述 AVPacket 队列 和 AVFrame 队列
首先 , 调用 avcodec_send_packet 函数
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *packet);
然后 , 调用 avcodec_receive_frame 函数
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
得到 AVFrame 数据后 , 可以直接用于播放 ;
3、AVPacket 数据的 深拷贝 和 浅拷贝
在上述 复用 / 解复用 和 编码 / 解码 操作中 , 大量使用到了 AVPacket 和 AVFrame 的数据拷贝操作 , 本章节以 AVPacket 为例进行解析 :
AVPacket 中 的 AVBufferRef *buf
就是 实际的 压缩编码后的 数据帧 , 其指向堆内存中的数据空间 , 音视频数据就存储在该数据空间中 ;
AVPacket 数据 拷贝 时 分为 深拷贝 和 浅拷贝 ,
- 浅拷贝 : 拷贝后的 AVPacket 的
AVBufferRef *buf
字段 和 原来的 AVPacket 的该字段 指向 堆内存 中的 同一个数据空间 ; - 深拷贝 : 拷贝后的 AVPacket 的
AVBufferRef *buf
字段 和 原来的 AVPacket 的该字段 指向 堆内存 中的 不同个数据空间 ; 拷贝前 , 先在堆内存申请内存空间 , 然后将 音视频数据 拷贝过去 , 这样拷贝前后的 AVPacket 各自持有一份不同的数据 ;
4、AVPacket 数据的 浅拷贝 引用计数器机制
在 FFmpeg 中 , 使用 的 是 浅拷贝 , 因为 深拷贝 在实际操作中 要消耗两倍的内存空间 ;
使用 浅拷贝 就意味着 多个 AVPacket 引用相同的数据 , 如果要将 AVPacket 进行释放 , 是否需要将其指向的 音视频 数据进行释放 ;
在实际的 音视频数据 中 , 定义了引用计数器 , AVPacket 每次进行浅拷贝 , 引用计数器自增 1 , AVPacket 每次进行释放时 , 引用计数器自减 1 , 当 自减后 引用计数器 值为 0 时 , 将 实际的 音视频数据 堆内存进行释放 ;
typedef struct AVBuffer {
/**
* 数据指针,指向实际的内存缓冲区。
*/
uint8_t *data;
/**
* 缓冲区的大小(以字节为单位)。
*/
int size;
/**
* 引用计数器,指示有多少个 `AVBufferRef` 引用了此缓冲区。
*/
int refcount;
} AVBuffer;
缓存空间 引用计数机制 :
- 初始状态 , 计数值为 0 ;
- 有新的 AVPacket 引用该 缓存空间 中的数据时 , 计数值 自增 1 ;
- 有 AVPacket 释放时 , 计数值 自减 1 , 当 自减 后 计数值为 0 时 , 释放 缓存空间 的内存 ;
5、AVPacket 中 存储 音视频数据 细节
在 FFmpeg 源码中 , AVPacket 通常定义在 libavcodec/avcodec.h 文件 中 , 其中 buf 字段 用于引用计数的缓冲区封装 , 方便数据的共享和管理 ;
typedef struct AVPacket {
/**
* 包含指向数据缓冲区的指针和引用计数信息。
*/
AVBufferRef *buf;
}
buf 是 AVBufferRef 类型的结构体指针
typedef struct AVBufferRef {
/**
* 指向实际缓冲区对象的指针。
* 该缓冲区对象包含了数据缓冲区和引用计数管理信息。
*/
AVBuffer *buffer;
/**
* 指向缓冲区数据的指针。
* 这通常是 `buffer` 中数据缓冲区的开始地址,但可以指向 `buffer->data` 中的任意偏移。
*/
uint8_t *data;
/**
* 缓冲区数据的大小(以字节为单位)。
*/
int size;
} AVBufferRef;
上述结构体中的 AVBuffer *buffer 是真实的 数据结构 , AVBuffer 结构体 的原型如下 , 在该原型中 , 定义了数据实际的内存缓冲区 , 数据大小 , 以及 引用计数 ;
typedef struct AVBuffer {
/**
* 数据指针,指向实际的内存缓冲区。
*/
uint8_t *data;
/**
* 缓冲区的大小(以字节为单位)。
*/
int size;
/**
* 引用计数器,指示有多少个 `AVBufferRef` 引用了此缓冲区。
*/
int refcount;
/**
* 用户自定义释放回调函数,在引用计数为 0 时调用。
*/
void (*free)(void *opaque, uint8_t *data);
/**
* 自定义数据,传递给 `free` 回调函数的上下文。
*/
void *opaque;
} AVBuffer;
参考下图进行理解 :
AVFrame 与 AVPacket 采用的是相同的 引用计数 机制 ;