在 FFmpeg 学习(六):FFmpeg 核心模块 libavformat 与 libavcodec 分析 中,我们分析了FFmpeg中最重要的两个模块以及重要的结构体之间的关系。
后面的文章,我们先不去继续了解其他模块,先针对在之前的学习中接触到的结构体进行分析,然后在根据功能源码,继续了解FFmpeg。
AVFormatContext是包含码流参数较多的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。
一、源码整理
首先我们先看一下结构体AVFormatContext的定义的结构体源码(位于libavformat/avformat.h,本人已经将相关注释翻译成中文,方便大家理解):
1 /** 2 * I/O格式上下文 3 * 4 * sizeof(AVFormatContext)方法不能在libav*外部调用,使用avformat_alloc_context()来创建一个AVFormatContext. 5 */ 6 typedef struct AVFormatContext { 7 /** 8 * 一个用来记录和指向avoptions的类。由avformat_all_context()设置。 9 * 如果(de)muxer存在私有option也会输出。 10 */ 11 const AVClass *av_class; 12 13 /** 14 * 输入容器的格式结构体 15 * 16 * 只在解码中生成,由avformat_open_input()生成 17 */ 18 struct AVInputFormat *iformat; 19 20 /** 21 * 输出容器的格式的结构体 22 * 23 * 只在编码中生成后,必须在调用avformat_write_header()方法之前被生成好。 24 */ 25 struct AVOutputFormat *oformat; 26 27 /** 28 * 私有数据的格式。这是一个AVOptions-enabled的结构体。 29 * 当且仅当iformat/oformat.priv_class不为空的时候才会用到。 30 * 31 * - 编码时: 由avformat_write_header()设置 32 * - 解码时: 由avformat_open_input()设置 33 */ 34 void *priv_data; 35 36 /** 37 * 输入/输出上下文. 38 * 39 * - 解码时: 可以由用户自己设置(在avformat_open_intput()之前,而且必须手动关闭),也可以由avformat_open_input()设置. 40 * - 编码时: 由用户设置(在avformat_write_header之前).调用者必须注意关闭和释放的问题。 41 * 42 * 如果在iformat/oformat.flags里面设置了AVFMT_NOFILE的标志,就不要设置设个字段。 因为在这个情况下,编解码器将以其他的方式进行I/O操作,这个字段将为NULL. 43 */ 44 AVIOContext *pb; 45 46 /***************************** 流信息相关字段 ***********************************/ 47 /** 48 * 流属性标志.是AVFMTCTX_*的集合 49 * 由libavformat设置. 50 */ 51 int ctx_flags; 52 53 /** 54 * AVFormatContext.streams -- 流的数量 55 * 56 * 由avformat_new_stream()设置,而且不能被其他代码更改. 57 */ 58 unsigned int nb_streams; 59 /** 60 * 文件中所有流的列表.新的流主要由avformat_new_stream()创建. 61 * 62 * - 解码时: 流是在avformat_open_input()方法里,由libavformat创建的。如果在ctx_flags里面设置了AVFMTCTX_NOHEADER,那么新的流也可能由av_read_frame()创建. 63 * - 编码时: 流是由用户创建的(在调用avformat_write_header()之前). 64 * 65 * 在avformat_free_context()释放. 66 */ 67 AVStream **streams; 68 69 #if FF_API_FORMAT_FILENAME 70 /** 71 * 输入或输出的文件名 72 * 73 * - 解码时: 由avformat_open_input()设置 74 * - 编码时: 应该在调用avformat_write_header之前由调用者设置 75 * 76 * @deprecated 本字段目前已经启用,更改为使用url地址 77 */ 78 attribute_deprecated 79 char filename[1024]; 80 #endif 81 82 /** 83 * 输入或输出的URL. 和旧文件名字段不同的是,这个字段没有长度限制. 84 * 85 * - 解码时: 有avformat_open_input()设置, 如果在avformat_open_input()设置的参数为NULL,则初始化为空字符串 86 * - 编码时: 应该在调用avformat_writer_header()之前由调用者设置(或者调用avformat_init_output_()进行设置),如果在avformat_open_output()设置的参数为NULL,则初始化为空字符串。 87 * 88 * 调用avformat_free_context()后由libavformat释放. 89 */ 90 char *url; 91 92 /** 93 * 第一帧的时间(AV_TIME_BASE:单位为微秒),不要直接设置这个值,这个值是由AVStream推算出来的。 94 * 95 * 仅用于解码,由libavformat设置. 96 */ 97 int64_t start_time; 98 99 /** 100 * 流的时长(单位AV_TIME_BASE:微秒) 101 * 102 * 仅用于解码时,由libavformat设置. 103 */ 104 int64_t duration; 105 106 /** 107 * 所有流的比特率,如果不可用的时候为0。不要设置这个字段,这个字段的值是由FFmpeg自动计算出来的。 108 */ 109 int64_t bit_rate; 110 111 unsigned int packet_size; 112 int max_delay; 113 114 /** 115 * 用于修改编(解)码器行为的标志,由AVFMT_FLAG_*集合构成,需要用户在调用avformat_open_input()或avformat_write_header()之前进行设置 116 */ 117 int flags; 118 #define AVFMT_FLAG_* 0x**** //***** 119 120 /** 121 * 在确定输入格式的之前的最大输入数据量. 122 * 仅用于解码, 在调用avformat_open_input()之前设置。 123 */ 124 int64_t probesize; 125 126 /** 127 * 从avformat_find_stream_info()的输入数据里面读取的最大时长(单位AV_TIME_BASE:微秒) 128 * 仅用于解码, 在avformat_find_stream_info()设置 129 * 可以设置0让avformat使用启发式机制. 130 */ 131 int64_t max_analyze_duration; 132 133 const uint8_t *key; 134 int keylen; 135 136 unsigned int nb_programs; 137 AVProgram **programs; 138 139 /** 140 * 强制使用指定codec_id视频解码器 141 * 仅用于解码时: 由用户自己设置 142 */ 143 enum AVCodecID video_codec_id; 144 145 /** 146 * 强制使用指定codec_id音频解码器 147 * 仅用于解码时: 由用户自己设置. 148 */ 149 enum AVCodecID audio_codec_id; 150 151 /** 152 * 强制使用指定codec_id字母解码器 153 * 仅用于解码时: 由用户自己设置. 154 */ 155 enum AVCodecID subtitle_codec_id; 156 157 /** 158 * 每个流的最大内存索引使用量。 159 * 如果超过了大小,就会丢弃一些,这可能会使得seek操作更慢且不精准。 160 * 如果提供了全部内存使用索引,这个字段会被忽略掉. 161 * - 编码时: 未使用 162 * - 解码时: 由用户设置 163 */ 164 unsigned int max_index_size; 165 166 /** 167 * 最大缓冲帧的内存使用量(从实时捕获设备中获得的帧数据) 168 */ 169 unsigned int max_picture_buffer; 170 171 /** 172 * AVChapter数组的数量 173 */ 174 unsigned int nb_chapters; 175 AVChapter **chapters; 176 177 /** 178 * 整个文件的元数据 179 * 180 * - 解码时: 在avformat_open_input()方法里由libavformat设置 181 * - 编码时: 可以由用户设置(在avformat_write_header()之前) 182 * 183 * 在avformat_free_context()方法里面由libavformat释放 184 */ 185 AVDictionary *metadata; 186 187 /** 188 * 流开始的绝对时间(真实世界时间) 189 */ 190 int64_t start_time_realtime; 191 192 /** 193 * 用于确定帧速率的帧数 194 * 仅在解码时使用 195 */ 196 int fps_probe_size; 197 198 /** 199 * 错误识别级别. 200 */ 201 int error_recognition; 202 203 /** 204 * I/O层的自定义中断回调. 205 */ 206 AVIOInterruptCB interrupt_callback; 207 208 /** 209 * 启动调试的标志 210 */ 211 int debug; 212 #define FF_FDEBUG_TS 0x0001 213 214 /** 215 * 最大缓冲持续时间 216 */ 217 int64_t max_interleave_delta; 218 219 /** 220 * 允许非标准扩展和实验 221 */ 222 int strict_std_compliance; 223 224 /** 225 * 检测文件上发生事件的标志 226 */ 227 int event_flags; 228 #define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 229 230 /** 231 * 等待第一个事件戳要读取的最大包数 232 * 仅解码 233 */ 234 int max_ts_probe; 235 236 /** 237 * 在编码期间避免负时间戳. 238 * 值的大小应该是AVFMT_AVOID_NEG_TS_*其中之一. 239 * 注意,这个设置只会在av_interleaved_write_frame生效 240 * - 编码时: 由用户设置 241 * - 解码时: 未使用 242 */ 243 int avoid_negative_ts; 244 #define AVFMT_AVOID_NEG_TS_* 245 246 /** 247 * 传输流id. 248 * 这个将被转移到解码器的私有属性. 所以没有API/ABI兼容性 249 */ 250 int ts_id; 251 252 /** 253 * 音频预加载时间(单位:毫秒) 254 * 注意:并非所有的格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. 255 * - 编码时: 由用户设置 256 * - 解码时: 未使用 257 */ 258 int audio_preload; 259 260 /** 261 * 最大块时间(单位:微秒). 262 * 注意:并非所有格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. 263 * - 编码时: 由用户设置 264 * - 解码时: 未使用 265 */ 266 int max_chunk_duration; 267 268 /** 269 * 最大块大小(单位:bytes) 270 * 注意:并非所有格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. 271 * - 编码时: 由用户设置 272 * - 解码时: 未使用 273 */ 274 int max_chunk_size; 275 276 /** 277 * 强制使用wallclock时间戳作为数据包的pts/dts 278 */ 279 int use_wallclock_as_timestamps; 280 281 /** 282 * avio标志 283 */ 284 int avio_flags; 285 286 /** 287 * 可以用各种方法估计事件的字段 288 */ 289 enum AVDurationEstimationMethod duration_estimation_method; 290 291 /** 292 * 打开流时跳过初始字节 293 */ 294 int64_t skip_initial_bytes; 295 296 /** 297 * 纠正单个时间戳溢出 298 */ 299 unsigned int correct_ts_overflow; 300 301 /** 302 * 强制寻找任何帧 303 */ 304 int seek2any; 305 306 /** 307 * 在每个包只会刷新I/O context 308 */ 309 int flush_packets; 310 311 /** 312 * 格式探索得分 313 */ 314 int probe_score; 315 316 /** 317 * 最大读取字节数(用于识别格式) 318 */ 319 int format_probesize; 320 321 /** 322 * 允许的编码器列表(通过','分割) 323 */ 324 char *codec_whitelist; 325 326 /** 327 * 允许的解码器列表(通过','分割 ) 328 */ 329 char *format_whitelist; 330 331 ......./** 332 * 强制视频解码器 333 */ 334 AVCodec *video_codec; 335 336 /** 337 * 强制音频解码器 338 */ 339 AVCodec *audio_codec; 340 341 /** 342 * 强制字母解码器 343 */ 344 AVCodec *subtitle_codec; 345 346 /** 347 * 强制数据解码器 348 */ 349 AVCodec *data_codec; 350 351 /** 352 * 在元数据头中写入填充的字节数 353 */ 354 int metadata_header_padding; 355 356 /** 357 * 用户数据(放置私人数据的地方) 358 */ 359 void *opaque; 360 361 /** 362 * 用于设备和应用程序之间的回调 363 */ 364 av_format_control_message control_message_cb; 365 366 /** 367 * 输出时间戳偏移量(单位:微秒) 368 */ 369 int64_t output_ts_offset; 370 371 /** 372 * 转储格式分隔符 373 */ 374 uint8_t *dump_separator; 375 376 /** 377 * 强制使用的数据解码器id 378 */ 379 enum AVCodecID data_codec_id; 380 381 #if FF_API_OLD_OPEN_CALLBACKS 382 /** 383 * 需要为解码开启更多的IO contexts时调用 384 * @deprecated 已弃用,建议使用io_open and io_close. 385 */ 386 attribute_deprecated 387 int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); 388 #endif 389 390 /** 391 * ',' separated list of allowed protocols. 392 * - encoding: unused 393 * - decoding: set by user 394 */ 395 char *protocol_whitelist; 396 397 /** 398 * 打开新IO流的回调 399 */ 400 int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, 401 int flags, AVDictionary **options); 402 403 /** 404 * 关闭流的回调(流是由AVFormatContext.io_open()打开的) 405 */ 406 void (*io_close)(struct AVFormatContext *s, AVIOContext *pb); 407 408 /** 409 * ',' 单独的不允许的协议的列表 410 * - 编码: 没使用到 411 * - 解码: 由用户设置 412 */ 413 char *protocol_blacklist; 414 415 /** 416 * 最大流数 417 * - 编码: 没使用到 418 * - 解码: 由用户设置 419 */ 420 int max_streams; 421 } AVFormatContext;
二、AVForamtContext 重点字段
在使用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:元数据
视频的时长可以转换成HH:MM:SS的形式,示例代码如下:
AVFormatContext *pFormatCtx; CString timelong; ... //duration是以微秒为单位 //转换成hh:mm:ss形式 int tns, thh, tmm, tss; tns = (pFormatCtx->duration)/1000000; thh = tns / 3600; tmm = (tns % 3600) / 60; tss = (tns % 60); timelong.Format("%02d:%02d:%02d",thh,tmm,tss);
视频的原数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中,如下所示:
typedef struct AVDictionaryEntry { char *key; char *value; } AVDictionaryEntry;
每一条元数据分为key和value两个属性。
在ffmpeg中通过av_dict_get()函数获得视频的原数据。
下列代码显示了获取元数据并存入meta字符串变量的过程,注意每一条key和value之间有一个" :",value之后有一个" "
//MetaData------------------------------------------------------------ //从AVDictionary获得 //需要用到AVDictionaryEntry对象 //CString author,copyright,description; CString meta=NULL,key,value; AVDictionaryEntry *m = NULL; //不用一个一个找出来 /*
m=av_dict_get(pFormatCtx->metadata,"author",m,0); author.Format("作者:%s",m->value); m=av_dict_get(pFormatCtx->metadata,"copyright",m,0); copyright.Format("版权:%s",m->value); m=av_dict_get(pFormatCtx->metadata,"description",m,0); description.Format("描述:%s",m->value); */ //使用循环读出 //(需要读取的数据,字段名称,前一条字段(循环时使用),参数) while(m=av_dict_get(pFormatCtx->metadata,"",m,AV_DICT_IGNORE_SUFFIX)){ key.Format(m->key); value.Format(m->value); meta+=key+" :"+value+" " ; }