AVFormat
摘要:AVStream是FFmpeg中一个流数据的抽象,是FFmpeg比较重要的结构体之一。本篇文章针对FFmpeg源码理解AVStream的作用,相关的结构定义以及一些操作API的具体实现。
关键字:AVStream
注意:阅读本文前你需要知道基本的视频封装格式中音视频流的存储方式。
1 AVStream
1.1 AVStream简介
一般封装格式中会存在多种类型的数据:音频数据,视频数据,字幕数据,用户数据等。一般情况下一个视频文件都会有一个视频流,多个(大于等于0)音频流,多个(大于等于0)字幕流。(当然对于纯音频文件没有视频流也是正常的)比如网上下载的一些电影可能由多个语言(英语流,汉语流),多种字幕(中文字幕、英文字幕)供选择。AVStream就是针对封装格式中数据流的抽象,描述了当前流的基本信息。如果视频包含2个流(音频流和视频流),在AVFormatContext中的streams字段中,就会有两个AVStream分表示两个流。
1.2 AVStream定义
在FFmpeg 5.0以前只有一个AVStream结构体,而在之后其实现进行了调整。FFmpeg内部有一个FFStream是不会对外暴露的,该结构体持有一个AVStream,在使用时FFmpeg只对用户暴露AVStream这样结构体就显得更精简避免用户接收到一些非必须的参数。因为AVStream始终在FFStream的首部,因此总是能够正确的找到对应的实例。
AVStream结构体本身比较简单,主要描述当前流的基本信息,比如帧率,宽高等,下面过一下每个成员的含义(部分成员参数都是解封装时有用户解封装器自行探测,封装时需要用户指定,下面不再一一赘述):
const AVClass *av_class;:当前结构体的参数描述,用户可以通过AVOption相关接口设置参数;int index;:当前流在AVFormatContext中的索引;int id;:媒体文件相关的流id;AVCodecParameters *codecpar;:解码器相关的参数,比如当前流的类型,解码器id等;void *priv_data;:私有数据,可以传递给封装器;AVRational time_base;:当前流的时间单位,AVPacket和AVFrame的时间戳都是以此为单位;int64_t start_time;:当前流的开始时间,并不是所有的流的开始时间都是0;int64_t duration;:以time_base为单位的流时长;int64_t nb_frames;:当前流的帧数;int disposition;:当前流的效果(AV_DISPOSITION_*),比如特效等;enum AVDiscard discard;:表明当前流是否弃置的状态;AVRational sample_aspect_ratio;:视频帧的SAR;AVDictionary *metadata;:元数据;AVRational avg_frame_rate;:当前流的平均帧率,对于CFR视频和帧率相同,VFR视频则不同;AVPacket attached_pic;:对于disposition的为AV_DISPOSITION_ATTACHED_PIC流,包含一张图像,比如音频专辑的封面;AVPacketSideData *side_data;:留的额外数据,和AVPacket中的sidedata不一定有交集;int nb_side_data;:sidedata的数量,每个类型占一项;int event_flags;:指定解码或者编码时的动作(AVSTREAM_EVENT_FLAG_*);AVRational r_frame_rate;:真实码率,即最低的码率,对于VFR视频由最低帧率和最高帧率;int pts_wrap_bits;:pts的位数,解复用时使用。
1.3 FFStream定义
FFStream是FFmpeg内部的流实现,其第一个成员就是``AVStream,因此总是能够通过AVStream找到对应的FFStream```。
AVStream pub;:向外公开的AVStream,内部使用时直接将AVStream地址强转为FFStream即可;int order;:codec是否支持reorder,1表示支持,现在主流codec(264,265)都支持;struct AVBSFContext *bsfc;:流上的filter相关的context,只有编码会使用;int bitstream_checked;:当前流的数据是否需要check,主要是check编码的数据流;struct AVCodecContext *avctx;:当前流对应的解码器的context;int avctx_inited;当前解码器的context是否被初始化,1表示yes;struct { struct AVBSFContext *bsf; int inited; } extract_extradata;:解码额外数据的结构体描述;int need_context_update;:是否需要根据解码器的codecpar更新流的codecpar,需要的话内部会更新;int is_intra_only;:当前解码器的帧是不是只有关键帧,比如MJPEG;FFFrac *priv_pts;:FFFrac结构体三个(int64_t val, num, den;)int64_t用来表示精确的时间戳,保存着当前流最后一个frame的pts;struct FFStreamInfo *info;:内部使用的一些参数,大概了解就行;AVIndexEntry *index_entries;:seek是使用的帧索引表,用于快速索引关键帧;int nb_index_entries;:帧索引表的项数;unsigned int index_entries_allocated_size;:index内存大小;int64_t interleaver_chunk_size;:chunk的大小;int64_t interleaver_chunk_duration;:chunk的时长;int request_probe;:probe的状态,-1表示finished,0表示没有进行,其他值表示以 request_probe 为接受的最低分数执行探测(FFmpeg内会对流进行打分)。int skip_to_keyframe;:是否丢弃除了关键帧的内容;int skip_samples;:开始解码时应该丢弃的sample数量;int64_t start_skip_samples;:从0开始到当前帧的数据都应该跳过,此值不应该为负数;int64_t first_discard_sample;:指定音频sample之前的帧都应该被丢弃;int64_t last_discard_sample;:指定音频sample之后的帧都应该被丢弃;int nb_decoded_frames;:内部已经解码的帧数量,用于内部跟踪解码的进度;int64_t mux_ts_offset;:mux的时间戳偏移;int64_t lowest_ts_allowed;:track中允许的最小时间戳,一般由muxer写入;int64_t pts_wrap_reference;:内部用于校正时间戳的数据;int pts_wrap_behavior;:校正时间戳的行为,比如:·AV_PTS_WRAP_ADD_OFFSET和AV_PTS_WRAP_SUB_OFFSET;int update_initial_durations_done:内部标志位避免update_initial_durations被执行两次;int64_t pts_reorder_error[MAX_REORDER_DELAY+1];:内部用于生成pts的数据;uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];:上一项的数量;int64_t pts_buffer[MAX_REORDER_DELAY+1];:内部用于生成pts的数据;int inject_global_side_data;:bool值表示是否注入全局sidedata;AVRational display_aspect_ratio;:DAR;AVProbeData probe_data;:探测的数据,FFmpeg可以指定probe_size长度;int64_t last_IP_pts;:最后的IP(不太清楚什么意思),看起来只有NUT格式用到;int last_IP_duration;:最后的IP(同上)的时长;enum AVStreamParseType need_parsing;:av_read_frame取帧数据时的解析类型;struct AVCodecParserContext *parser;:av_read_frame取帧时解析器的context;int codec_info_nb_frames;:通过avformat_find_stream_info找到的帧数量;int stream_identifier;:流id,主要是MPEG-TS个使用;int64_t first_dts;:第一帧的dts;int64_t cur_dts;:当前帧的dts;
2 AVInputFormat
AVInputFormat存储解封转的文件信息,是固定的,一般定义在对应解封装文件中,比如mov格式的定义在libavformat\mov.c文件中,如果需要自己实现解封装器将就需要定义对应的格式:
const AVInputFormat ff_mov_demuxer = {
.name = "mov,mp4,m4a,3gp,3g2,mj2",
.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
.priv_class = &mov_class,
.priv_data_size = sizeof(MOVContext),
.extensions = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif",
.flags_internal = FF_FMT_INIT_CLEANUP,
.read_probe = mov_probe,
.read_header = mov_read_header,
.read_packet = mov_read_packet,
.read_close = mov_read_close,
.read_seek = mov_read_seek,
.flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS | AVFMT_SHOW_IDS,
};
const char *name;:由,隔开的格式名称;const char *long_name;:全称;int flags;:操作文件个标志符,比如是否允许按照bytes seek等;const char *extensions;:扩展名;const struct AVCodecTag *const *codec_tag:解码器类型;const AVClasss *priv_class;:私有的选项;const char *mime_type;:,隔开的mime_type;int raw_codec_id;:裸数据的demuxer的codec id;int priv_data_size;:私有数据的大小,一般为对应格式的Context,比如mov格式为MOVContext;int flag_internal;:内部的标志符;int (*read_probe)(const AVProbeData *);:探测当前文件是那个类型的文件的函数指针;int (*read_header)(struct AVFormatContext *);:读取格式header,初始化AVFormatContext的函数指针;int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);:从文件中读取一个packet的函数指针;int (*read_close)(struct AVFormatContext *);:关闭流,但是不涉及对应流的释放;int (*read_seek)(struct AVFormatContext *, int stream_index, int64_t timestamp, int flags);:seek到对应的位置;int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index, int64_t *pos, int64_t pos_limit);:获取下一个时间戳;int (*read_play)(struct AVFormatContext *);:开始或者继续读,只有网络文件有效;int (*read_pause)(struct AVFormatContext *);:暂停读,只有网络文件有效;int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);:seek到对应的时间戳;int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);:获取设备列表。
3 AVOutputFormat
AVOutputFormat是对外的结构,是描述封装文件的信息和相关操作函数指针的结构。FFmpeg内部使用的是FFOutputFormat,其及结构和AVStream和FFStream类似:
typedef struct FFOutputFormat {
AVOutputFormat p;
};
比如mp4格式的描述如下:
const FFOutputFormat ff_mp4_muxer = {
.p.name = "mp4",
.p.long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),
.p.mime_type = "video/mp4",
.p.extensions = "mp4",
.priv_data_size = sizeof(MOVMuxContext),
.p.audio_codec = AV_CODEC_ID_AAC,
.p.video_codec = CONFIG_LIBX264_ENCODER ?
AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
.init = mov_init,
.write_header = mov_write_header,
.write_packet = mov_write_packet,
.write_trailer = mov_write_trailer,
.deinit = mov_free,
.p.flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
.p.codec_tag = mp4_codec_tags_list,
.check_bitstream = mov_check_bitstream,
.p.priv_class = &mov_isobmff_muxer_class,
};
3.1 AVOutputFormat
AVOutputFormat是FFmpeg写封装文件是描述当前文件信息的结构。
const char *name:当前文件格式的简称;cosnt char *long_name:详细名称;const char *mime_type:mime_type;enum AVCodecID audio_codec:默认的音频编码器的类型;enum AVCodecID video_codec:默认的视频编码器的类型;enum AVCodecID subtitle_codec:默认的字幕编码器的类型;int flags:AVFMT_描述的标志符;const struct AVCodecTag *const *codec_tag:当前格式支持的编解码器的列表;cosnt AVClass *priv_class:对应格式私有的Context;
3.2 FFOutputFormat
FFOutputFormat是AVOutputFormat在FFmpeg内部使用的形式:
AVOutputFormat p:描述文件的属性;int priv_data_size:私有数据的大小;int flags_internal:内部的标志符,FF_FMT_FLAG;int (*write_header)(AVFormatContext *);:写头的函数指针; /**Write a packet. If AVFMT_ALLOW_FLUSH is set in flags,
pkt can be NULL in order to flush data buffered in the muxer.
When flushing, return 0 if there still is more data to flush,
or 1 if everything was flushed and there is no more buffered
data. */
int (*write_packet)(AVFormatContext *, AVPacket *pkt);:写入一个packet,如果设置了刷新的标志符且pkt为空则会刷新缓冲区;int (*write_trailer)(AVFormatContext *);:写文件尾部;int (*interleave_packet)(AVFormatContext *s, AVPacket *pkt, int flush, int has_packet);:交错写文件的函数指针,默认是按照dts交错写的;int (*query_codec)(enum AVCodecID id, int std_compliance);:检查对应的当前容器是否支持对应的codec;void (*get_output_timestamp)(AVFormatContext *s, int stream, int64_t *dts, int64_t *wall);:int (*control_message)(AVFormatContext *s, int type, void *data, size_t data_size);:发送消息的回调;int (*write_uncoded_frame)(AVFormatContext *, int stream_index, AVFrame **frame, unsigned flags);:写rawdata;int (*get_device_list)(AVFormatContext *s, struct AVDeviceInfoList *device_list);:返回设备列表;int (*init)(AVFormatContext *);:初始化AVFormatContext```;void (*deinit)(AVFormatContext *);:deinit;int (*check_bitstream)(AVFormatContext *s, AVStream *st, const AVPacket *pkt);:检查流的正确性、
3 AVIOContext
3.1 AVIOContext
AVIOContext是FFmpeg中直接读写流的结构体,也就是说openfile,readfile,writefile等操作都是通过这个结构体进行的。先简单看下结构体成员的描述:
const AVClass *av_class;:当前类的option列表;unsigned char *buffer;:内部会维持一个缓冲区,这个就是buffer的起始地址;int buffer_size:内部维持的buffer的大小;unsigned char *buf_ptr:当前buffer数据消费的地址;unsinged char *buf_end:已读数据的地址,因为buffer内会有空闲区,这个地址比buffer+buffer_size小是正常的;void *opaque:私有的类指针;int (*read_packet)(void *opaque, uint8_t *buff, int buf_size):读packet的函数指针;int (*write_packet)(void *opaque, uint8_t *buf, int buf_size):写packet的函数指针;int64_t pos:读取到文件的位置;int eof_reached:是否读完文件;int write_flags:是否写文件,1表示写文件;int max_packet_size:刷新缓冲区的上限;int min_packet_size:刷新缓冲区的下限;unsigned long checksum:校验和;unsigned char *checksum_ptr:计算校验和内存的起始地址;unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);:用户指定的更新校验和的指针;int (*read_pause)(void *opaque, int pause);:暂停读,网络数据会使用到;int64_t (*read_seek)(void *opaque, int stream_index, int64_t timestamp, int flags);:seek;int seekable;:当前流的seek模式(AVIO_SEEKABLE_);int direct;:立即写/立即读;const char *protocol_whitelist;:协议白名单;const char *protocol_blacklist;:协议黑名单;int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size, enum AVIODataMarkerType type, int64_t time);:write_packet中调用的callback;int ignore_boundary_point;:If set, don't call write_data_type separately for AVIO_DATA_MARKER_BOUNDARY_POINT, but ignore them and treat them as AVIO_DATA_MARKER_UNKNOWN (to avoid needlessl small chunks of data returned from the callback).unsigned char *buf_ptr_max;:写buffer中逆向seek的最远位置指针;int64_t bytes_read;:读入的数据量,只读;int64_t bytes_written;:写入的数据量,只读;
3.2 相关API
2 AVFormatContext
2.1 简介
AVFormatContext是FFmpeg中媒体格式的抽象,使用FFmpeg打开一个API之后就会得到一个AVFormatContext类型的结构体,该结构体包含了当前媒体文件的相关信息。在FFmpeg中和读写文件直接相关的操作都是通过该结构完成,比如解封装,封装都是通过该结构体完成的。
2.2 详情
下面过一下AVFormatContext中结构体成员:
const AVClass *av_class:当前封装格式的参数列表,key是固定写在对应文件中,可以通过AVOption相关的操作API设置和获取。比如在libavformat\mov.c中就定义了mp4文件的选项列表mov_options;const struct AVInputFormat *iformat;:输入文件的context,解封装使用(解封装时不需要用户输入,avformat_open_input会设置);const struct AVOutputFormat *oformat;:写文件的context,封装使用;void *priv_data;:一个AVOption类型的私有数据,mux时由avformat_write_header()设置,demux时由avformat_open_input()设置;AVIOContext *pb;int ctx_flags:流的属性标志,AVFMTCTX_;unsigned int nb_streams;:当前文件中流的数量,即AVStream数组的大小;AVStream **streams;:流的数组,解封装时由avformat_open_input设置,封装时由用户设置;char *url;:文件路径/网路流的地址;int64_t start_time:第一帧的起始时间,仅仅解封装有用;int64_t duration:当前文件的时长,一般为最长的流的长度;int64_t bit_rate:当前文件的总码率,内部会自动计算;unsigned int packet_size:int max_delay:int flags:读写文件时的标志,AVFMT_FLAG_;int64_t probesize:搜索流的最大字节数,对于有些mp4文件moov写在文件末尾,较小的probesize就会导致识别不出文件类型;int64_t max_analysz_duration;打开文件搜索流时读取的最大时长;const uint6_t *key;int keylen:key的长度;AVProgram **programs:enum AVCodecID video_codec_id:视频编解码器的id;enum AVCodecID audio_coded_id:音频编解码器的id;enum AVCodecID subtitle_codec_id;:字幕的编解码id;unsigned int max_index_size;: