demuxing_decoding
1 概要
样例Demo,主要展示解复用和解码。样例Demo的时序图如下,对于重点关注的接口,我用了实线箭头来做指向。
2 av_register_all
官方给定的接口解释如下,用于注册所有的formats和codecs。
针对这个接口分析,网上的资料较多,建议直接学习这篇Blog:ffmpeg 源代码简单分析 : av_register_all()
/**
* Initialize libavformat and register all the muxers, demuxers and
* protocols. If you do not call this function, then you can select
* exactly which formats you want to support.
*
* @see av_register_input_format()
* @see av_register_output_format()
*/
void av_register_all(void);
3 avformat_open_input
先看下官方给定的接口注释,该函数用于打开多媒体数据并且获得一些相关的信息。fmt参数不为NULL时,强制指定AVFormatContext中AVInputFormat的。样例Demo中设置成NULL,这里先从简分析下,利用av_find_input_format接口先查找指定的fmt,再用作入参提供给avformat_open_input接口。参考Blog如下:
1. 图解FFMPEG打开媒体的函数avformat_open_input
2. FFMPEG源码分析:avformat_open_input() 媒体打开函数
3. FFmpeg源代码简单分析:avformat_open_input()
/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
这里分析的是mp4文件,所以指定的fmt是ff_mov_demuxer。由于这里指定了fmt,init_input接口内部处理变得简单多了,只剩下io_open处理。
正常情况下需要探测视频文件的格式,这个过程也比较有趣。如果感兴趣的话,可以单独拎出来看。
avformat_open_input大致的调用流程见下面的流程图:
3.1 io_open
参考Blog:FFmpeg源代码简单分析:avio_open2()
step3-5: 从代码中可以看出,ffurl_alloc()主要调用了2个函数:url_find_protocol()根据文件路径查找合适的URLProtocol,url_alloc_for_protocol()为查找到的URLProtocol创建URLContext。由于这里使用本地文件调试,所以匹配的URL是file.c。
step9: 指定read_packet和write_packet,这样就关联到protocol的url_read和url_write。
step8: 再回到ffio_fdopen(),会发现它初始化AVIOContext的结构体的时候,首先将自己分配的Buffer设置为该AVIOContext的Buffer;然后将URLContext作为用户自定义数据(对应AVIOContext的opaque变量)提供给该AVIOContext;最后分别将3个函数作为该AVIOContext的读,写,跳转函数:ffurl_read(),ffurl_write(),ffurl_seek()。
3.2 read_header
这里还是以mp4文件为例,所以read_header实际指向的是mov_read_header接口。参考内容:MPEG-4 Part 14; Elements of the H.264 Video/AAC Audio MP4 Movie.pdf。
下面内容是执行 ffplay -loglevel 56 VID_20180131_195311.mp4,读取到的header信息。需要关注的是stts和stco box,前者表示 Sample To Time映射关系,后者是Chunk Offset Table。具体的结构描述见Elements of the H.264 Video/AAC Audio MP4 Movie.pdf文档。
调用mov_read_trak接口解析trak box时,每调用一次,都会用avformat_new_stream创建一个AVStream,离开mov_read_trak接口前,nb_streams也会自增一。
read_header解析日志:(read_header读取到的内容填充到AVStream结构体内部的×priv_data变量)
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’ftyp’ parent:’root’ sz: 24 8 3705390
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] ISO: File Type Major Brand: mp42
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’moov’ parent:’root’ sz: 1915 32 3705390
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’mvhd’ parent:’moov’ sz: 108 8 1907
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] time scale = 1000
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’udta’ parent:’moov’ sz: 38 116 1907
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’[169]xyz’ parent:’udta’ sz: 30 8 30
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’meta’ parent:’moov’ sz: 121 154 1907
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’hdlr’ parent:’meta’ sz: 33 8 113
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] ctype=[0][0][0][0]
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] stype=mdta
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f3b40000920] type:’keys’ parent:’meta’ sz: 43 41 113
[mov,mp