FFmpeg5.0源码阅读——AVFrame

  摘要AVFrame是FFmpeg中表示裸数据的结构体,是FFmpeg最重要的结构体之一。本篇文章针对FFmpeg源码理解AVFrame的作用,相关的结构定义以及一些操作API的具体实现。
  关键字AVFrameAVFrameSideData
  注意:阅读本文前你需要了解基本的视频数据格式比如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]linesizedata是一一对应的,存储当前片数据一行或者当前数据帧的大小。数值一般为了性能都是进行过对齐的:
    • 视频数据:视频数据的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_dataside_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_bufbuf是否可写;
  • 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数据如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gYPIpvR2-1676645472135)(img/mv.png)]

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并返回。

猜你喜欢

转载自blog.csdn.net/GrayOnDream/article/details/129035418