AVFrame
摘要:AVFrame是FFmpeg中表示裸数据的结构体,是FFmpeg最重要的结构体之一。本篇文章针对FFmpeg源码理解AVFrame的作用,相关的结构定义以及一些操作API的具体实现。
关键字:AVFrame、AVFrameSideData
注意:阅读本文前你需要了解基本的视频数据格式比如YUV420P,RGBA8等,以及音频数据格式,比如FLTP,S16等。并且了解FFmpeg的基本解码流程,以及AVFrame的简单使用。
1. AVFrame简介
FFmpeg中解码的裸数据都是通过AVFrame存储的,因此理解AVFrame的具体实现对于使用FFmpeg有比较大的帮助。AVFrame是一个复合的结构体,他可以存储音频数据或者视频数据。但是因为音频和视频数据的参数不兼容比如宽高和采样率等,AVFrame中会保留两者参数的定义,以至于结构体略显臃肿(同时包含了音频和视频的参数定义)。
FFmpeg解码一个视频时,会先通过解封装器对视频解封装得到编码的流数据AVPacket,再将该流数据送给解码器进行解码,解码出来的裸数据就会存储在AVFrame中返回。而一个AVFrame中只有一帧画面或者一段音频数据。
2 AVFrame
2.1 AVFrame结构定义
AVFrame有一套自己的操作API,必须通过相关的api进行创建(av_frame_alloc)和释放(av_frame_free)。因为AVFrame中的内存时通过AVBufferPool进行管理的。这就意味着AVFrame的内存是通过引用计数管理的,可以重复使用。另外需要注意的AVFrame的abi并不稳定这就意味着sizeof(AVFrame)并不固定,后续更新可能会直接在AVFrame结构体定义的尾部添加成员导致其值改变。
AVFrame(在libavutil/avframe.h中)的结构定义较为复杂,这里就不罗列代码而是直接根据代码定义解释所有成员的含义:
uint8_t *data[AV_NUM_DATA_POINTERS]:存储了音频和视频的raw数据。raw数据的组织方式是按照片来进行存储的。视频数据:
如果是packed的数据,就只有一片,比如rgba等数据就会存储在
data[0],其他几个指针全部置空;如果是planner数据,需要根据planner的数量存储,比如YUV420P数据存在三片分别为Y、U和V通道分别存储在
data[0]、data[1]、data[2]中,而NV12数据有两片,Y单独一片,UV数据一片,分别存储在data[0]和data[1]中;
音频数据:音频数据同样也分packed和planner:
packed数据,只有一片;
planner数据,片数和通道数挂钩,双通道数据
data[0]和data[1]就分别存储两个通道的数据;
如果需要存储的数据超过AV_NUM_DATA_POINTERS(8)个通道,额外的数据就需要存储在
extended_data中;
int linesize[AV_NUM_DATA_POINTERS]:linesize和data是一一对应的,存储当前片数据一行或者当前数据帧的大小。数值一般为了性能都是进行过对齐的:视频数据:视频数据的
linesize存储了当前片数据一行所占的字节数即(bytes per row),一片的实际数据大小就是width*linesize[index],比如对于576x432的RGBA数据,linesize[0]=align(576x4)=2304,而对于YUV420Plinesize[0]==width;音频数据:存储当前片音频的数据大小,所有片的大小相同都是
linesize[0],其他域置空;
uint8_t **extended_data:音频数据,如果音频数据是planner且通道数大于8则需要通过extended_data找到多余的数据,同时包含所有数据;其他情况其指向和data相同;int width,height:视频数据的宽高,对于音频数据无意义;int nb_samples:音频数据的sample数量,对于视频数据无意义;int format:当前数据的格式,视频时AVPixelFormat,音频是AVSampleFormat;int key_frame:当前帧是否为关键帧;AVPictureType pict_type:当前帧的类型,比如IBP帧等;AVRational sample_aspect_ratio:SAR,视频的帧的采样比,0/1表示未知;
视频中有DAR、PAR和SAR三种比例:
SAR:(Storage Aspect Ratio):存储在本地的视频帧的宽高比;
DAR:(Display Aspect Ratio)实际显示的宽高比,DAR=SAR x PAR;
PAR:(Pixel Aspect Ratio)像素宽高比,一般而言像素都是正方形的即1:1,但是也不绝对;
int64_t pts:当前帧以time_base为单位的显示时间戳;int64_t dts:当前帧以time_base为单位的解码时间戳;int64_t pkt_dts:当前帧对应的AVPacket的pts;int coded_picture_number:码流中帧的序列号;int display_picture_number:帧的显示序列号;int quality:图像质量,取值[1, FF_LAMBDA_MAX];void *opaque:用户的私有数据指针,内部会直接透传;int repeat_pict:解码时表明当前帧额外延迟的时间,计算方式为extra_delay=repeat_pict/(2 x fps),实际帧间隔时间为额外间隔+帧率间隔;int interlaced_frame:当前帧图像是否为隔行采样模式,取值0/1;int top_field_first:如果当前帧为隔行采样,表明是否先显示顶部的行;int palette_has_changed:对于支持调色板的格式,表示调色板是否发生变化;int64_t reordered_opaque:int sample_rate:音频的采样率,比如8000、44100等;AVBufferRef buf[AV_NUM_DATA_POINTERS]:对当前帧中data的内存管理的AVBufferRef,如果为空则表示当前帧内存不是通过该方式管理的;AVBufferRef **extended_buf:对于planner的音频数据超过AV_NUM_DATA_POINTERS个片的数据会用extended_data存储,这个字段就是对应管理大于AV_NUM_DATA_POINTERS通道数的extended_data的;int nb_extended_buf````:extended_buf```中字段的项数;AVFrameSideData **side_data:额外的数据,比如motion vector解码成功就是存储在此项;int nb_side_data:side_data的项数;int flags:当前帧的标志位表明当前帧的状态;enum AVColorRange color_range;enum AVColorPrimaries color_primaries;enum AVColorTransferCharacteristic color_trc;enum AVColorSpace colorspace;enum AVChromaLocation chroma_location;:当前帧颜色空间相关信息;int64_t best_effort_timestamp:通过启发式算法计算出来的pts(编码无用,解码时由解码器设置)int64_t pkt_pos:最后一个送入解码器的帧在文件中的偏移量;int64_t pkt_duration:当前帧的时长;AVDictionary *metadata:元数据,编码时由用户设置,解码时由libavcodec设置;int decode_error_flags:解码错误的标志符,(FF_DECODE_ERROR_xxx);int channels:音频通道数,废弃;int pkt_size:当前packet编码数据的大小,只有解码有用;AVBufferRef *hw_frames_ctx:对于使用硬件加速的解码帧,指向对应AVHWFramesContext;AVBufferRef *opaque_ref:用户数据,但是看代码基本没有用到;size_t crop_top;size_t crop_bottom;size_t crop_left;size_t crop_right;:当前帧的矩形区域,其他都丢弃;AVBufferRef *private_ref:内部使用的数据,外部不应该关心。AVChannelLayout ch_layout:当前音频数据的存储格式,比如单通道,双通道等,旧版本这个值时int;
2.2 AVFrameAPI实现
& AVFrame的一定要通过相关的API进行操作,除非是读,修改内存相关的操作如果不使用对应的API可能导致AVBufferRef释放内存不正确。
AVFrame *av_frame_alloc(void):创建一个AVFrame,会对内存清零,并将部分参数设置为默认值,需要注意的是这里只是申请了AVFrame,内部的数据还是空的,对应的释放API为av_frame_free;void av_frame_unref(AVFrame *frame):AVFrame中所有通过AVBufferRef管理的内存都引用计数-1,并将对应的AVBufferRef释放,最后将所有参数设置为默认值;void av_frame_free(AVFrame **frame):释放整个AVFrame;int av_frame_get_buffer(AVFrame *frame, int align):根据当前音频和视频帧的参数填充data域;int av_frame_ref(AVFrame *dst, const AVFrame *src):将src内的数据和参数拷贝到dst,实际的数据是指向相同的data只是通过不同的AVBufferRef管理;AVFrame *av_frame_clone(const AVFrame *src):拷贝AVFrame,和av_frame_ref的区别是函数内创建目标AVFrame;void av_frame_move_ref(AVFrame *dst, AVFrame *src):move后src会被置空;int av_frame_is_writable(AVFrame *frame):检查当前帧中的extended_buf和buf是否可写;int av_frame_make_writable(AVFrame *frame):主要就是通过av_frame_get_buffer申请内存;int av_frame_copy_props(AVFrame *dst, const AVFrame *src):拷贝参数;AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane):返回期望获得的plane的AVBufferRef;int av_frame_apply_cropping(AVFrame *frame, int flags):应用指定的裁剪参数,并不会释放对应的内存而是将数据指针和宽高设置为对应的值。
3 AVFrameSideData
sidedata就是解码过程中的一些中间数据,比如运动向量等
AVFrameSideData的结构体比较简单,
3.1 AVFrameSideData结构定义
typedef struct AVFrameSideData {
enum AVFrameSideDataType type;
uint8_t *data; //实际的数据域,具体的数据类型和存储方式通过type解析
size_t size; //数据大小
AVDictionary *metadata; //元数据
AVBufferRef *buf;
} AVFrameSideData;
FFmpeg支持的数据如下,
const char *av_frame_side_data_name(enum AVFrameSideDataType type)
{
switch(type) {
case AV_FRAME_DATA_PANSCAN: return "AVPanScan";
case AV_FRAME_DATA_A53_CC: return "ATSC A53 Part 4 Closed Captions";
case AV_FRAME_DATA_STEREO3D: return "Stereo 3D";
case AV_FRAME_DATA_MATRIXENCODING: return "AVMatrixEncoding";
case AV_FRAME_DATA_DOWNMIX_INFO: return "Metadata relevant to a downmix procedure";
case AV_FRAME_DATA_REPLAYGAIN: return "AVReplayGain";
case AV_FRAME_DATA_DISPLAYMATRIX: return "3x3 displaymatrix";
case AV_FRAME_DATA_AFD: return "Active format description";
case AV_FRAME_DATA_MOTION_VECTORS: return "Motion vectors";
case AV_FRAME_DATA_SKIP_SAMPLES: return "Skip samples";
case AV_FRAME_DATA_AUDIO_SERVICE_TYPE: return "Audio service type";
case AV_FRAME_DATA_MASTERING_DISPLAY_METADATA: return "Mastering display metadata";
case AV_FRAME_DATA_CONTENT_LIGHT_LEVEL: return "Content light level metadata";
case AV_FRAME_DATA_GOP_TIMECODE: return "GOP timecode";
case AV_FRAME_DATA_S12M_TIMECODE: return "SMPTE 12-1 timecode";
case AV_FRAME_DATA_SPHERICAL: return "Spherical Mapping";
case AV_FRAME_DATA_ICC_PROFILE: return "ICC profile";
case AV_FRAME_DATA_DYNAMIC_HDR_PLUS: return "HDR Dynamic Metadata SMPTE2094-40 (HDR10+)";
case AV_FRAME_DATA_REGIONS_OF_INTEREST: return "Regions Of Interest";
case AV_FRAME_DATA_VIDEO_ENC_PARAMS: return "Video encoding parameters";
case AV_FRAME_DATA_SEI_UNREGISTERED: return "H.26[45] User Data Unregistered SEI message";
case AV_FRAME_DATA_FILM_GRAIN_PARAMS: return "Film grain parameters";
case AV_FRAME_DATA_DETECTION_BBOXES: return "Bounding boxes for object detection and classification";
}
return NULL;
}
如果要解码对应的数据时,设置解码相关参数然后从AVFrame中使用相关API拿出数据即可:
av_dict_set(&opts, "flags2", "+export_mvs", 0);
ret = avcodec_open2(dec_ctx, dec, &opts);
while(){
//send_packet
while(){
//receive avframe
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MOTION_VECTORS);
}
}
比如motion vector数据如下:

3.2 AVFrameSideData相关API
const char *av_frame_side_data_name(enum AVFrameSideDataType type):获取对应类型的sidedata的字符串描述;void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type):释放当前AVFrame中对应的sidedata的数据;AVFrameSideData *av_frame_get_side_data(const AVFrame *frame, enum AVFrameSideDataType type):从AVFrame中获取对应类型的sidedata;AVFrameSideData *av_frame_new_side_data(AVFrame *frame, enum AVFrameSideDataType type, size_t size):创建对应类型的sidedata并返回。