FFmpeg API 内存模型 —— AVPacket 和 AVFrame
1、引用计数原理
AVPacket 结构体
AVPacket
主要用于存储压缩的音视频数据,解复用后/解码前、编码后/复用前
- 包含缓冲区信息、显示事件戳、解码时间戳等信息
typedef struct AVPacket {
AVBufferRef *buf;
int64_t pts;
int64_t dts;
uint8_t *data;
int size;
} AVPacket;
typedef struct AVBufferRef {
AVBuffer *buffer;
uint8_t *data;
size_t size;
} AVBufferRef;
内存的的申请和释放
const char *str = "Hello, World!";
auto size = static_cast<int>(std::strlen(str) + 1);
AVPacket *packet = av_packet_alloc();
av_new_packet(packet, size);
std::memcpy(packet->data, str, size);
fprintf(stderr, "packet->data: \"%s\"\n", packet->data);
fprintf(stderr, "packet->size: %d\n", packet->size);
int ref_count = av_buffer_get_ref_count(packet->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
av_packet_unref(packet);
av_packet_free(&packet);
浅拷贝
- 原理是增加引用计数,
av_packet_clone
= av_packet_alloc()
+ av_packet_ref()
constexpr int size = 1024;
int ref_count{};
AVPacket *packet1 = av_packet_alloc();
av_new_packet(packet1, size);
ref_count = av_buffer_get_ref_count(packet1->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
AVPacket *packet2 = av_packet_alloc();
av_packet_ref(packet2, packet1);
ref_count = av_buffer_get_ref_count(packet1->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
ref_count = av_buffer_get_ref_count(packet2->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
AVPacket *packet3 = av_packet_clone(packet1);
ref_count = av_buffer_get_ref_count(packet3->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
av_packet_unref(packet1);
av_packet_unref(packet2);
ref_count = av_buffer_get_ref_count(packet3->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
av_packet_unref(packet3);
av_packet_free(&packet1);
av_packet_free(&packet2);
av_packet_free(&packet3);
引用移动
constexpr int size = 1024;
int ref_count{};
AVPacket *packet1 = av_packet_alloc();
av_new_packet(packet1, size);
ref_count = av_buffer_get_ref_count(packet1->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
AVPacket *packet2 = av_packet_alloc();
av_packet_move_ref(packet2, packet1);
fprintf(stderr, "!packet1->buf: %d\n", !packet1->buf);
ref_count = av_buffer_get_ref_count(packet2->buf);
fprintf(stderr, "reference count: %d\n", ref_count);
av_packet_unref(packet2);
av_packet_free(&packet2);
av_packet_free(&packet1);
2、AVFrame 内存结构
YUV 视频格式
- Y 亮度参量(黑白画面),UV 色度参量(U蓝V红)
- packed 打包格式:每个像素点的 YUV 分量交叉排列
- planar 平面格式:使用三个数组分开连续地存放 YUV 三个分量
- 采样表示:444(1Y ----1UV)、422(2Y----1UV,水平除二)、420(4Y----1UV,水平垂直都除二)
- Stride 对齐:假设 100 × 100、16对齐,RGB 300 => 304,Y 100 => 112,U 50 => 64,V 50 =>64
PCM 音频格式
- packed 打包格式:左右声道的样本交替存储, L1 R1 L2 R2 L3 R3 L4 R4
- planar 平面格式:左右声道的样本分别连续存储,L1 L2 L3 L4 ......R1 R2 R3 R4
AVFrame 结构体
AVFrame
主要用于存储解码后的原始音视频数据,解码后/编码前,引用计数原理与 AVPacket
类似
- 包括:视频帧的长度宽度、plane数据数组、行长度数组、每个声道的样本数、格式等
- plane:一片连续的缓冲区
data[]
- packed 视频:YUV 交织存储在
data[0]
;
- planar 视频:
data[0]
指向 Y-plane,data[1]
指向 U-plane,data[2]
指向 V-plane
- packed 音频:LR 交织存储在
data[0]
- planar 音频:
data[0]
指向 L-plane,data[1]
指向 R-plane
linesize[]
- packed 视频:
linesize[0]
表示一行图像所占空间,需 stride 对齐
- planar 视频:
linesize[i]
表示一行图像在当前 plane 所占空间
- 音频:仅可设置
linesize[0]
,表示一个音频 plane 的大小
typedef struct AVFrame {
uint8_t *data[AV_NUM_DATA_POINTERS];
int linesize[AV_NUM_DATA_POINTERS];
int width, height;
int nb_samples;
int format;
AVBufferRef *buf[AV_NUM_DATA_POINTERS];
} AVFrame;
typedef struct AVBufferRef {
AVBuffer *buffer;
uint8_t *data;
size_t size;
} AVBufferRef;
音频帧内存分析
AVFrame *frame1 = av_frame_alloc();
frame1->nb_samples = 1024;
frame1->format = AV_SAMPLE_FMT_S16;
frame1->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
av_frame_get_buffer(frame1, 0);
fprintf(stderr, "frame1->linesize[0]: %d\n", frame1->linesize[0]);
AVFrame *frame2 = av_frame_alloc();
frame2->nb_samples = 1024;
frame2->format = AV_SAMPLE_FMT_S16P;
frame2->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
av_frame_get_buffer(frame2, 0);
fprintf(stderr, "frame2->linesize[0]: %d\n", frame2->linesize[0]);
av_frame_unref(frame1);
av_frame_unref(frame2);
av_frame_free(&frame1);
av_frame_free(&frame2);
视频帧内存分析
AVFrame *frame1 = av_frame_alloc();
frame1->format = AV_PIX_FMT_YUV420P;
frame1->width = 640;
frame1->height = 480;
av_frame_get_buffer(frame1, 0);
fprintf(stderr, "frame1->linesize[0]: %d\n", frame1->linesize[0]);
fprintf(stderr, "frame1->linesize[1]: %d\n", frame1->linesize[1]);
fprintf(stderr, "frame1->linesize[2]: %d\n", frame1->linesize[2]);
AVFrame *frame2 = av_frame_alloc();
frame2->format = AV_PIX_FMT_YUYV422;
frame2->width = 640;
frame2->height = 480;
av_frame_get_buffer(frame2, 0);
fprintf(stderr, "frame2->linesize[0]: %d\n", frame2->linesize[0]);
av_frame_unref(frame1);
av_frame_unref(frame2);
av_frame_free(&frame1);
av_frame_free(&frame2);