AVFrame的作用
在 FFmpeg 中,AVFrame
是一个非常核心的数据结构,主要用于存储原始音频或视频数据的帧(frame)。它的作用可以理解为承载媒体数据的容器,具体包括:
- 视频数据:存储解码后的视频图像,比如一帧的像素数据(YUV、RGB等格式)。
- 音频数据:存储解码后的音频样本数据。
- 元数据:包含帧的时间戳(PTS/DTS)、格式(像素格式、采样格式)、宽高、通道数、采样率等信息。
主要功能
- 承载解码后的媒体数据,方便进行后续处理(编码、滤镜、显示等)。
- 传递帧数据 :解码器输出的是
AVFrame
,编码器的输入也是AVFrame
。 - 避免直接管理内存 :用户通过FFmpeg API读写
AVFrame
,内部统一管理数据内存。
AVFrame的定义
c
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
//用于存放音视频数据,
// video yuv图像数据,data[0]存放y分量, data[1]存放u分量,data[2]存放v分量
// 音频数据多声道数据如果是交错存放,就值存放在data[0], 如果是planar audio,则data[0]存放声到1,data[1]存放声道2, ...
uint8_t *data[AV_NUM_DATA_POINTERS];
// data数组中每个元素的长度,单位为字节
int linesize[AV_NUM_DATA_POINTERS];
// 当音频的声道数大于8的时候,就需要使用extended_data来存放
uint8_t **extended_data;
// 视频帧的宽高
int width, height;
// 音频sample个数
int nb_samples;
// 视频图像格式,值为枚举AVPixelFormat定义。或者音频采样格式,值为枚举AVSampleFormat锁定义
int format;
// 视频帧的像素宽高比
AVRational sample_aspect_ratio;
int64_t pts;
int64_t pkt_dts;
AVRational time_base;
//表示此帧可以重复的次数,一般为视频帧使用
int repeat_pict;
//音频的采样率
int sample_rate;
// 此framework中AVBuffer的引用,用于引用计数的计算,当引用计数为0时,自动释放buffer
AVBufferRef *buf[AV_NUM_DATA_POINTERS];
// extenddata的引用
AVBufferRef **extended_buf;
int nb_extended_buf;
//存放音视频帧的附加信息,如:视频帧中加的字幕信息,HDR信息等
AVFrameSideData **side_data;
int nb_side_data;
// 音频声道布局
AVChannelLayout ch_layout;
// 当前帧的时长
int64_t duration;
/**
* Frame flags, a combination of @ref lavu_frame_flags
*/
int flags;
/**
* MPEG vs JPEG YUV range.
* - encoding: Set by user
* - decoding: Set by libavcodec
*/
enum AVColorRange color_range;
enum AVColorPrimaries color_primaries;
enum AVColorTransferCharacteristic color_trc;
/**
* YUV colorspace type.
* - encoding: Set by user
* - decoding: Set by libavcodec
*/
enum AVColorSpace colorspace;
enum AVChromaLocation chroma_location;
/**
* frame timestamp estimated using various heuristics, in stream time base
* - encoding: unused
* - decoding: set by libavcodec, read by user.
*/
int64_t best_effort_timestamp;
AVFrame的基本用法
AVFrame中的buffer如何分配中以及如何写数据到AVFrame中?
c
int main() {
// 假定参数
int sample_rate = 44100;
int channels = 2;
AVSampleFormat sample_fmt = AV_SAMPLE_FMT_S16; // 16bit整型
int nb_samples = 1024; // 每帧样本数
int bytes_per_sample = av_get_bytes_per_sample(sample_fmt);
// 分配frame
AVFrame *frame = av_frame_alloc();
frame->format = sample_fmt;
frame->channels = channels;
frame->channel_layout = av_get_default_channel_layout(channels);
frame->sample_rate = sample_rate;
frame->nb_samples = nb_samples;
// 分配缓冲给frame->data,
// 其内部会根据nb_samples,format,channel_layout,sample_rate计算frame->data的空间
int ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
fprintf(stderr, "Failed to allocate frame buffer\n");
av_frame_free(&frame);
return -1;
}
// 这里需要有pcm_data数据准备好,这里只是个演示
int frame_bytes = nb_samples * channels * bytes_per_sample;
uint8_t *pcm_data = malloc(frame_bytes);
//AV_SAMPLE_FMT_S16是交错存放方式,直接放置在data[0]中
//frame_bytes与 frame->linesize[0]相同
memcpy(frame->data[0], pcm_data, frame_bytes);
// 使用完毕释放资源
av_frame_free(&frame);
free(pcm_data);
return 0;
}
上面的sample code中步骤:
- 通过av_frame_alloc分配AVFrame
- 通过av_frame_get_buffer函数给frame->data分配空间
- 将数据拷贝到frame->data[0]中
- av_frame_free将AVFrame以及其内部的data释放掉
AVFrame中data与extended_data有什么关系?
我们通过av_frame_get_buffer函数的源码来解答这个问题
c
int av_frame_get_buffer(AVFrame *frame, int align)
{
if (frame->format < 0)
return AVERROR(EINVAL);
if (frame->width > 0 && frame->height > 0)
return get_video_buffer(frame, align);
else if (frame->nb_samples > 0 &&
(av_channel_layout_check(&frame->ch_layout)))
//只有audio数据才使用extended_data
return get_audio_buffer(frame, align);
return AVERROR(EINVAL);
}
static int get_audio_buffer(AVFrame *frame, int align)
{
//如: AV_SAMPLE_FMT_S16就是交错存放数据,AV_SAMPLE_FMT_S16P就是独立存放各声道的数据
// AV_SAMPLE_FMT_S16 双声道: data[0]={sampleL0, sampleR0,sampleL1, sampleR1...}
// AV_SAMPLE_FMT_S16P 双声道: data[0]={sampleL0, sampleL1, sampleL2...}
// data[1]={sampleR0, sampleR1, sampleR2...}
int planar = av_sample_fmt_is_planar(frame->format);
int channels, planes;
int ret;
channels = frame->ch_layout.nb_channels;
//是否每个声道独立存放数据
planes = planar ? channels : 1;
if (!frame->linesize[0]) {
//计算data[]数组中每个元素的size
// AV_SAMPLE_FMT_S16 双声道, 100个sample: linesize = 2(sample_size) * 2(channel) * 100(sample_count) = 400 byte
// AV_SAMPLE_FMT_S16 双声道,100个sample:linesize = 2(sample_size) * 100(sample_count) = 200 byte
ret = av_samples_get_buffer_size(&frame->linesize[0], channels,
frame->nb_samples, frame->format,
align);
}
//如果声道数大于8个,则会给extended_data分配空间用于存放地址
if (planes > AV_NUM_DATA_POINTERS) {
frame->extended_data = av_calloc(planes,
sizeof(*frame->extended_data));
frame->extended_buf = av_calloc(planes - AV_NUM_DATA_POINTERS,
sizeof(*frame->extended_buf));
frame->nb_extended_buf = planes - AV_NUM_DATA_POINTERS;
} else //声道数小于8,extended_data就是data
frame->extended_data = frame->data;
for (int i = 0; i < FFMIN(planes, AV_NUM_DATA_POINTERS); i++) {
//分配AVBufferRef以及AVBuffer
frame->buf[i] = av_buffer_alloc(frame->linesize[0]);
//前8个声道extended_data和data指向同一个buffer
frame->extended_data[i] = frame->data[i] = frame->buf[i]->data;
}
for (int i = 0; i < planes - AV_NUM_DATA_POINTERS; i++) {
//分配AVBufferRef以及AVBuffer
frame->extended_buf[i] = av_buffer_alloc(frame->linesize[0]);
//超出的部分extended_data指向额外的buffer
frame->extended_data[i + AV_NUM_DATA_POINTERS] = frame->extended_buf[i]->data;
}
return 0;
}
通过上面的代码可以看出,只有在声道数大于AV_NUM_DATA_POINTERS(当前为8)的audio数据的时候,extended_data才与data字段略有不同。下面看下示意图:
js
交错存放数据时,只有data[0]有数据:
data[0] = extended_data[0] --> data buffer
平铺存放时,声道数小于8,假设为3:
data[0] = extended_data[0] --> data buffer A
data[1] = extended_data[1] --> data buffer B
data[2] = extended_data[2] --> data buffer C
平铺存放时,声道数大于8,假设为10:
data[0] = extended_data[0] --> data buffer A
data[1] = extended_data[1] --> data buffer B
data[2] = extended_data[2] --> data buffer D
data[3] = extended_data[3] --> data buffer E
data[4] = extended_data[4] --> data buffer F
data[5] = extended_data[5] --> data buffer G
data[6] = extended_data[6] --> data buffer H
data[7] = extended_data[7] --> data buffer I
//没有data[8]和data[9]
extended_data[9] --> data buffer J
extended_data[10] --> data buffer K
AVFrame中如何给视频帧分配空间比较复杂,稍后会单独出一章来阐述。
AVFrame关键函数介绍
函数 | 作用 |
---|---|
AVFrame *av_frame_alloc(void); | 分配AVFrame这个外壳,其内部的Buffer此函数内并没有分配 |
void av_frame_free(AVFrame **frame) | 释放AVFrame内部任何动态分配的资源,如果buffer有引用计数,则先引用计数减一 |
int av_frame_ref(AVFrame *dst, const AVFrame *src); | 从src 拷贝所有属性,并创建 一个新的buffer ref来引用src 的buffer |
void av_frame_unref(AVFrame *frame); | 取消引用帧所引用的所有缓冲区,并重置帧的字段 |
void av_frame_move_ref(AVFrame *dst, AVFrame *src) | 将 src 中包含的所有内容移动到 dst 中,并重置 src |
int av_frame_replace(AVFrame *dst, const AVFrame *src) | 先释放dst 内部引用,然后创建新的引用来指向src 内部的buffer |
AVFrame *av_frame_clone(const AVFrame *src) | 相当于av_frame_alloc()+av_frame_ref() |
int av_frame_copy(AVFrame *dst, const AVFrame *src) | 从src拷贝数据到dst,不拷贝属性 |
int av_frame_copy_props(AVFrame *dst, const AVFrame *src) | 仅仅从src拷贝metadata字段到dst |
int av_frame_get_buffer(AVFrame *frame, int align) | 按照align字节对齐根据AVFrame内部的数据格式计算buffer size并分配buffer |
AVBufferRef *av_frame_get_plane_buffer(const AVFrame *frame, int plane) | 获取某个平台数据的引用 |
av_frame_ref & av_frame_unref
c
int main() {
// 分配源帧
AVFrame *src_frame = av_frame_alloc();
// 设置源帧的属性
src_frame->format = AV_PIX_FMT_YUV420P;
src_frame->width = 640;
src_frame->height = 480;
// 为源帧分配缓冲区
av_frame_get_buffer(src_frame, 0);
// 分配目标帧
AVFrame *dst_frame = av_frame_alloc();
// 使用 av_frame_ref 复制源帧的引用到目标帧
// 此函数执行完之后,dst_frame和src_frame都指向同一个缓冲区,此时此缓冲区的引用计数为2
av_frame_ref(dst_frame, src_frame)
printf("Successfully referenced source frame to destination frame.\n");
// 使用 av_frame_unref 取消目标帧的引用
// 此函数执行完之后,此时此缓冲区的引用计数为1
av_frame_unref(dst_frame);
// 释放源帧, 此函数内部会再次进行引用计数减一,为0后释放缓冲区
av_frame_free(&src_frame);
return 0;
}
copy & clone & move_ref & replace的差异
c
int main() {
// 分配源帧
AVFrame *src_frame = av_frame_alloc();
// 设置源帧的属性
src_frame->format = AV_PIX_FMT_YUV420P;
src_frame->width = 640;
src_frame->height = 480;
// 为源帧分配缓冲区
av_frame_get_buffer(src_frame, 0);
// 1. av_frame_move_ref 演示
AVFrame *dst_move_ref = av_frame_alloc();
// 为dst_move_ref分配AVBufferRef并指向src_frame的AVBuffer,没有音视频数据拷贝
// 此函数执行后src_frame内部的AVBufferRef *buf[]为NULL,av_frame_free(&src_frame)之后src_frame最开始指向的AVBuffer不会被释放
av_frame_move_ref(dst_move_ref, src_frame);
// 恢复源帧,用于后续测试
av_frame_free(&src_frame);
src_frame = av_frame_alloc();
src_frame->format = AV_PIX_FMT_YUV420P;
src_frame->width = 640;
src_frame->height = 480;
av_frame_get_buffer(src_frame, 0);
// 2. av_frame_replace 演示
AVFrame *dst_replace = av_frame_alloc();
dst_replace->format = AV_PIX_FMT_YUV420P;
dst_replace->width = 640;
dst_replace->height = 480;
av_frame_get_buffer(dst_replace, 0);
// 如果dst_replace已有buffer引用,会先解引用,然后再指向src_frame的buffer,不会有内存泄漏
// 没有音视频数据拷贝
av_frame_replace(dst_replace, src_frame);
// 3. av_frame_clone 演示
// 分配一个AVFrame以及其内部AVBufferRef并引用src_frame的AVFrameBuffer
// 没有音视频数据拷贝
AVFrame *dst_clone = av_frame_clone(src_frame);
// 4. av_frame_copy 演示
AVFrame *dst_copy = av_frame_alloc();
dst_copy->format = src_frame->format;
dst_copy->width = src_frame->width;
dst_copy->height = src_frame->height;
av_frame_get_buffer(dst_copy, 0);
//需要提前把dst_copy的属性以及内部的buffer分配好,仅仅只是拷贝buffer
av_frame_copy(dst_copy, src_frame)
// 释放所有帧
av_frame_free(&src_frame);
av_frame_free(&dst_move_ref);
av_frame_free(&dst_replace);
av_frame_free(&dst_clone);
av_frame_free(&dst_copy);
return 0;
}