avformat_open_input函数分析之通用分析
avformat_open_input
函数的作用是打开一个输入源,然后读取解析它的头部信息,定义如下:
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
avformat_open_input
函数的流程图如下:
当 ps
参数为 NULL
的时候,就会调 avformat_alloc_context()
函数来申请一个 format 的上下文,需要注意的是 avformat_alloc_context
里面会调 avformat_get_context_defaults
来设置 format
的默认参数,如下:
在阅读下面的内容之前,建议读者先参考《FFmpeg的自定义IO之AVIO》与《移植Qt示例到clion调试》,搭建好 flv-avio
在 clion
的调试环境。
init_input()
函数的逻辑分叉比较绕,如下:
我的流程图可能不是特别准确,所以我用文字来描述一下这个函数的分叉场景。
场景1:使用了 AVIO 且 iformat 已指定,那就什么都不做
当你使用了 avio
,也就是设置了 s->pb
,并且指定了 iformat
,那 init_input()
函数就会直接返回 0 ,什么都不做。
提示:命令行的 -f
选项就是指定 iformat
的,例如 -f flv
是指定 flv
的 iformat
场景2:使用了 AVIO 且 iformat 未指定,那就读取一些数据来尝试确定 iformat
当使用了 avio
,但未设置 iformat
,就会调 av_probe_input_buffer2()
来从输入源读取一部分数据进行分析,尝试确定 iformat
,如下:
这个操作,其实是个重点来的。在这种场景下,avformat_open_input()
函数本身就会开始读输入源的数据了,而不只是在 avformat_find_stream_info()
函数里面才会读数据。
所以如果你使用了 avio
,但不想 avformat_open_input()
里面就开始读数据,最好指定一下 iformat
,这样它就不用自己去探测了。
av_probe_input_buffer2()
函数的内部流程如下:
avio_read()
函数的定义如下:
int avio_read(AVIOContext *s, unsigned char *buf, int size)
avio_read()
函数的内部实现比较复杂,但其实你可以不用管他的复杂细节,它就干了一件事,就是从输入源读取 size
大小的数据放到 buf
里面。
但 avio_read()
读取数据的时候,有两种模式:
1,direct 模式
在 direct
模式下,会调用 read_packet_wrapper()
直接从输入源读取 size
大小的数据放到 buf
里面。
具体 direct
模式 有什么好处我也不清楚,就是 AVIOContext
里面有个 direct
字段标记是不是 直接模式。默认情况 AVIO
用的是下面的 buffer
模式。
2,buffer 模式
在 buffer
模式下,会先调 fill_buffer()
,输入源读取到的数据会放到 s->buffer
里面,然后再调 memcpy()
复制给上层的 buf
,如下:
avio_read()
读取到数据之后,就会调 av_probe_input_format2()
函数探测数据,确定 iformat
,如下:
*fmt = av_probe_input_format2(&pd, 1, &score);
av_probe_input_format2
的重点代码如下:
可以看到,av_probe_input_format2()
对 mp3 这种格式特别关照,加了很多代码来探测 mp3
格式,id3v2
就是 mp3
的一个数据。
然后他会编译所有的 Demuxer
,会优选使用 Demuxer
的 read_probe
函数来探测数据,如果 Demuxer
没有定义 read_probe
函数,就用 extensions
(文件后缀)来匹配,如果没有后缀,就用 mime_type
类型来匹配。
对于 flv
的 demuxer
来说, read_probe
函数就是 flv_probe()
。对于 MP4
的 demuxer
来说, read_probe
函数就是 mpeg4video_probe()
。
最后会选取 score
(分数)最高的 iformat
返回。如果没有匹配到一个 Demuxer
,就会返回 NULL
场景3:未使用 AVIO 且 iformat 未指定,直接调 av_probe_input_format2 来确认 iformat
如果没有使用 AVIO
,而且没有指定 iformat
,上面的代码是直接调 av_probe_input_format2()
来确认 iformat
。但是大部分场景都是确认不了 iformat
,因为 pd
是空的数据。 具体 场景3 的代码是用来处理什么情况的,我也不太清楚,后面补充。
之前的 场景2 的内部也调用了 av_probe_input_format2()
,但是 场景2 与 场景3 的区别是, 场景3 的 av_probe_input_format2()
的第二个参数 is_open
是 0,具体这个参数用在哪些场景,我暂时也不太清楚,后面补充。
场景4:直接打开输入文件,或者是网络URL
当你调用 avformat_open_input()
的时候,如果没使用 avio
,那 init_input()
函数的 场景1~3 的代码都不会执行,而是直接执行的 场景4 的代码,如下:
s->io_open()
这个是通用函数,无论是建立 RTMP/RTSP 网络连接,还是打开本文文件,都是用的这个函数,他的内部函数调用层级非常多,如下:
上图中 url
不单指 rtmp
,rtsp
这种网络的 url
,也代表文件的路径之类的。
s->io_open()
里面会对 AVFormatContext
的 pb
字段进行赋值。pb
这个字段是需要非常注意的,因为我们用 avio
的时候,是我们自己设置了 AVFormatContext::pb
,而如果是直接打开本地文件之类的,是在 s->io_open()
里面设置的 AVFormatContext::pb
,如下:
s->io_open()
仅仅只是打开了本地文件,并没有从里面读取任何内容的。但是最后的那个 return av_probe_input_buffer2()
还是读取了一部分本地文件的内容来确定 iformat
,代码如下:
if (s->iformat)
return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
总结:无论有没有自定义IO(avio),大部分情况 init_input()
函数里面都需要读取一段数据来确认 s->iformat
的,如果确认不了 iformat
,init_input()
会返回错误,然后退出。
avformat_open_input()
函数里面,其实就 init_input()
的逻辑比较多,后面的处理白黑名单,id3v2
,其实没有太多重点。
不过需要注意 s->iformat->read_header
,这个函数会去读取 MP4 的头部,或者 FLV 的头部,不过如果是 yuv 的输入源,这个函数应该什么都不会做
还有需要注意的是,id3v2
确实受到了非常多的特殊照顾。 init_input()
里面有针对 id3v2
的特殊处理, init_input()
的上层函数avformat_open_input()
里也有对 id3v2
的特殊处理
最后的 update_stream_avctx()
也没什么重点,就是复制了一下解码器参数,如下:
推荐阅读:
至此,avformat_open_input()
的内部实现就介绍完毕了,由于它里面对 id3v2
做了很多特殊处理,所以推荐一些资料供大家学习 id3v2(mp3)的格式。