cherayliu 2019-12-15
FFmpeg 中结构体很多。最关键的结构体可以分成以下几类:
1)解协议(http, rtsp, rtmp, mms)
AVIOContext,URLProtocol,URLContext 主要存储视音频使用的协议的类型以及状态。URLProtocol 存储输入视音频使用的封装格式。每种协议都对应一个 URLProtocol 结构。(注意:FFmpeg 中文件也被当做一种协议 “file”)
2)解封装(flv, avi, rmvb, mp4)
AVFormatContext 主要存储视音频封装格式中包含的信息;AVInputFormat 存储输入视音频使用的封装格式。每种视音频封装格式都对应一个 AVInputFormat 结构。
3)解码(h264, mpeg2, aac, mp3)
每个 AVStream 存储一个视频/音频流的相关数据;每个 AVStream 对应一个 AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个 AVCodecContext 中对应一个 AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个 AVCodec 结构。
4)存数据
视频的话,每个结构一般是存一帧;音频可能有好几帧。解码前数据:AVPacket;解码后数据:AVFrame。
它们之间的对应关系如下所示:
AVFrame 结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是 YUV,RGB,对音频来说是 PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP 表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用 FFmpeg 进行码流分析的时候,AVFrame 是一个很重要的结构体。
下面看几个主要变量的作用(在这里考虑解码的情况):
uint8_t *data[AV_NUM_DATA_POINTERS]; // 解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM) int linesize[AV_NUM_DATA_POINTERS]; // data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。 int width, height; // 视频帧宽和高(1920x1080,1280x720...) int nb_samples; // 音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个 int format; // 解码后原始数据类型(YUV420,YUV422,RGB24...) int key_frame; // 是否是关键帧 enum AVPictureType pict_type; // 帧类型(I,B,P...) AVRational sample_aspect_ratio; // 宽高比(16:9,4:3...) int64_t pts; // 显示时间戳 int coded_picture_number; // 编码帧序号 int display_picture_number; // 显示帧序号 int8_t *qscale_table; // QP表 uint8_t *mbskip_table; // 跳过宏块表 int16_t (*motion_val[2])[2]; // 运动矢量表 uint32_t *mb_type; // 宏块类型表 short *dct_coeff; // DCT系数,这个没有提取过 int8_t *ref_index[2]; // 运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧) int interlaced_frame; // 是否是隔行扫描 uint8_t motion_subsample_log2; // 一个宏块中的运动矢量采样个数,取log的
如果想了解这些变量具体的含义,请看:FFMPEG结构体分析:AVFrame
在使用 FFmpeg 进行开发的时候,AVFormatContext 是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是 FFmpeg 解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):
struct AVInputFormat *iformat; // 输入数据的封装格式 AVIOContext *pb; // 输入数据的缓存 unsigned int nb_streams; // 视音频流的个数 AVStream **streams; // 视音频流 char filename[1024]; // 文件名 int64_t duration; // 时长(单位:微秒us,转换为秒需要除以1000000) int bit_rate; // 比特率(单位bps,转换为kbps需要除以1000) AVDictionary *metadata; // 元数据
AVCodecContext 是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息。下面挑一些关键的变量来看看(这里只考虑解码)。
enum AVMediaType codec_type; // 编解码器的类型(视频,音频...) struct AVCodec ?*codec; // 采用的解码器AVCodec(H.264,MPEG2...) int bit_rate; // 平均比特率 uint8_t *extradata;?int extradata_size; // 针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等) AVRational time_base; // 根据该参数,可以把PTS转化为实际的时间(单位为秒s) int width, height; // 如果是视频的话,代表宽和高 int refs; // 运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了) int sample_rate; // 采样率(音频) int channels; // 声道数(音频) enum AVSampleFormat sample_fmt; // 采样格式 int profile; // 型(H.264里面就有,其他编码标准应该也有) int level; // 级(和profile差不太多)
在这里需要注意:AVCodecContext 中很多的参数是编码的时候使用的,而不是解码的时候使用的。如果想了解这些变量具体的含义,请看:FFMPEG结构体分析:AVCodecContext
AVIOContext 是 FFmpeg 管理输入输出数据的结构体。其重要的变量如下所示:
unsigned char *buffer; // 缓存开始位置 int buffer_size; // 缓存大小(默认32768) unsigned char *buf_ptr; // 当前指针读取到的位置 unsigned char *buf_end; // 缓存结束的位置 void *opaque; // URLContext结构体
在解码的情况下,buffer 用于存储 FFmpeg 读入的数据。例如打开一个视频文件的时候,先把数据从硬盘读入 buffer,然后在送给解码器用于解码。
存储编码器信息的结构体(位于avcodec.h文件中)。其重要的变量如下所示:
const char *name; // 编解码器的名字的简称 const char *long_name; // 编解码器名字的全称 enum AVMediaType type; // 指明了类型,是视频,音频,还是字幕 enum AVCodecID id; // ID,不重复 const AVRational *supported_framerates; // 支持的帧率(仅视频) const enum AVPixelFormat *pix_fmts; // 支持的像素格式(仅视频),如RGB24、YUV420P等。 const int *supported_samplerates; // 支持的采样率(仅音频) const enum AVSampleFormat *sample_fmts; // 支持的采样格式(仅音频) const uint64_t *channel_layouts; // 支持的声道数(仅音频) int priv_data_size; // 私有数据的大小
如果想了解这些变量具体的含义,请看:FFMPEG结构体分析:AVCodec
每一个编解码器对应一个该结构体,查看一下 ffmpeg 的源代码,我们可以看一下 H.264 解码器的结构体如下所示(h264.c):
AVCodec ff_h264_decoder = { .name = "h264", .type = AVMEDIA_TYPE_VIDEO, .id = CODEC_ID_H264, .priv_data_size = sizeof(H264Context), .init = ff_h264_decode_init, .close = ff_h264_decode_end, .decode = decode_frame, .capabilities = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS, .flush= flush_dpb, .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"), .init_thread_copy = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy), .update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context), .profiles = NULL_IF_CONFIG_SMALL(profiles), .priv_class = &h264_class, };
下面简单介绍一下遍历 ffmpeg 中的解码器信息的方法(这些解码器以一个链表的形式存储):
av_register_all()
;AVCodec* first_c
;AVStream 是存储每一个视频/音频流信息的结构体。其重要的变量如下所示:
int index; // 标识该视频/音频流 AVCodecContext *codec; // 指向该视频/音频流的AVCodecContext(它们是一一对应的关系) AVRational time_base; // 时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间 int64_t duration; // 该视频/音频流长度 AVDictionary *metadata; // 元数据信息 AVRational avg_frame_rate; // 帧率(注:对视频来说,这个挺重要的) AVPacket attached_pic; // 附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVPacket 是存储压缩编码数据相关信息的结构体。其重要的变量如下所示:
uint8_t *data; // 压缩编码的数据。 /* 例如对于H.264来说。1个AVPacket的data通常对应一个NAL。 注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流。因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。*/ int ? size; // data的大小 int64_t pts; // 显示时间戳 int64_t dts; // 解码时间戳 int ? stream_index; // 标识该AVPacket所属的视频/音频流。
参考:
雷霄骅大神的几篇关于 ffmpeg 结构体的文章: