avformat_open_input函数分析之通用分析

avformat_open_input 函数的作用是打开一个输入源,然后读取解析它的头部信息,定义如下:

int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

avformat_open_input 函数的流程图如下:

1-1

ps 参数为 NULL 的时候,就会调 avformat_alloc_context() 函数来申请一个 format 的上下文,需要注意的是 avformat_alloc_context 里面会调 avformat_get_context_defaults 来设置 format 的默认参数,如下:

1-1-2


在阅读下面的内容之前,建议读者先参考《FFmpeg的自定义IO之AVIO》与《移植Qt示例到clion调试》,搭建好 flv-avioclion 的调试环境。

init_input() 函数的逻辑分叉比较绕,如下:

1-1-3

我的流程图可能不是特别准确,所以我用文字来描述一下这个函数的分叉场景。

场景1:使用了 AVIO 且 iformat 已指定,那就什么都不做

当你使用了 avio,也就是设置了 s->pb,并且指定了 iformat ,那 init_input() 函数就会直接返回 0 ,什么都不做。

提示:命令行的 -f 选项就是指定 iformat 的,例如 -f flv 是指定 flviformat

场景2:使用了 AVIO 且 iformat 未指定,那就读取一些数据来尝试确定 iformat

当使用了 avio,但未设置 iformat,就会调 av_probe_input_buffer2() 来从输入源读取一部分数据进行分析,尝试确定 iformat,如下:

1-2

这个操作,其实是个重点来的。在这种场景下,avformat_open_input() 函数本身就会开始读输入源的数据了,而不只是在 avformat_find_stream_info() 函数里面才会读数据。

所以如果你使用了 avio,但不想 avformat_open_input() 里面就开始读数据,最好指定一下 iformat,这样它就不用自己去探测了。

av_probe_input_buffer2() 函数的内部流程如下:

1-3

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,如下:

3-1


avio_read() 读取到数据之后,就会调 av_probe_input_format2() 函数探测数据,确定 iformat,如下:

*fmt = av_probe_input_format2(&pd, 1, &score);

av_probe_input_format2 的重点代码如下:

1-4

1-5

可以看到,av_probe_input_format2() 对 mp3 这种格式特别关照,加了很多代码来探测 mp3 格式,id3v2 就是 mp3 的一个数据。

然后他会编译所有的 Demuxer,会优选使用 Demuxerread_probe 函数来探测数据,如果 Demuxer 没有定义 read_probe 函数,就用 extensions(文件后缀)来匹配,如果没有后缀,就用 mime_type 类型来匹配。

对于 flvdemuxer 来说, read_probe 函数就是 flv_probe()。对于 MP4demuxer 来说, read_probe 函数就是 mpeg4video_probe()

最后会选取 score(分数)最高的 iformat 返回。如果没有匹配到一个 Demuxer,就会返回 NULL


场景3:未使用 AVIO 且 iformat 未指定,直接调 av_probe_input_format2 来确认 iformat

1-6

如果没有使用 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 的代码,如下:

1-7

s->io_open() 这个是通用函数,无论是建立 RTMP/RTSP 网络连接,还是打开本文文件,都是用的这个函数,他的内部函数调用层级非常多,如下:

1-8

上图中 url 不单指 rtmprtsp 这种网络的 url,也代表文件的路径之类的。

s->io_open() 里面会对 AVFormatContextpb 字段进行赋值。pb 这个字段是需要非常注意的,因为我们用 avio 的时候,是我们自己设置了 AVFormatContext::pb,而如果是直接打开本地文件之类的,是在 s->io_open() 里面设置的 AVFormatContext::pb,如下:

1-8-2

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 的,如果确认不了 iformatinit_input() 会返回错误,然后退出。


avformat_open_input() 函数里面,其实就 init_input() 的逻辑比较多,后面的处理白黑名单,id3v2,其实没有太多重点。

不过需要注意 s->iformat->read_header ,这个函数会去读取 MP4 的头部,或者 FLV 的头部,不过如果是 yuv 的输入源,这个函数应该什么都不会做

2-1

还有需要注意的是,id3v2 确实受到了非常多的特殊照顾。 init_input() 里面有针对 id3v2 的特殊处理, init_input() 的上层函数avformat_open_input() 里也有对 id3v2 的特殊处理

最后的 update_stream_avctx() 也没什么重点,就是复制了一下解码器参数,如下:

2-2


推荐阅读:

  1. FFmpeg源代码简单分析 avformat_open_input()

至此,avformat_open_input() 的内部实现就介绍完毕了,由于它里面对 id3v2 做了很多特殊处理,所以推荐一些资料供大家学习 id3v2(mp3)的格式。

  1. MP3: MP3中的ID3v2 格式分析
  2. ff_id3v2_parse_apic 函数代码分析
  3. ID3v2的结构
  4. avformat_open_input 函数源代码分析

版权所属 xianwanzhiyin.net 罗上文 2022 all right reserved,powered by Gitbook该文件修订时间: 2023-03-25 13:44:41

results matching ""

    No results matching ""