ijkplayer框架简析 -- avformat_open_input
avformat_open_input()
方法完成了媒体文件的打开和格式探测的功能
avformat_open_input
1 | int avformat_open_input(AVFormatContext **ps, const char *filename, |
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)
第一个参数是封装格式的上下文,第二个参数是文件地址,第三个参数是数据的封装格式,第四个参数是配置?
接下来比较重要的是 init_input(s, filename, &tmp)
,在这里面完成了查找流媒体协议和解复用器的工作
init_input
1 | /* Open input file and probe the format if necessary. */ |
开头的 score 变量是一个判决 AVInputFormat 的分数的门限值,如果最后得到的 AVInputFormat 的分数低于该门限值,就认为没有找到合适的 AVInputFormat。FFmpeg 内部判断封装格式的原理实际上是对每种 AVInputFormat 给出一个分数,满分是 100 分,越有可能正确的 AVInputFormat 给出的分数就越高,最后选择分数最高的 AVInputFormat 作为推测结果,这里一开始的分数是 25 分
1 | #define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4) |
- 当使用了自定义的 AVIOContext 的时候(AVFormatContext 中的 AVIOContext 不为空,即 s->pb!=NULL),如果指定了 AVInputFormat 就直接返回,如果没有指定就调用 av_probe_input_buffer2() 推测 AVInputFormat。这一情况出现的不算很多,但是当我们从内存中读取数据的时候(需要初始化自定义的 AVIOContext),就会执行这一步骤
- 在更一般的情况下,如果已经指定了 AVInputFormat,就直接返回;如果没有指定 AVInputFormat,就调用 av_probe_input_format(NULL,…) 根据文件路径判断文件格式。这里特意把 av_probe_input_format() 的第 1 个参数写成 “NULL”,是为了强调这个时候实际上并没有给函数提供输入数据,此时仅仅通过文件路径推测 AVInputFormat
- 如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个时候会首先调用 avio_open2() 打开文件,然后调用 av_probe_input_buffer2() 推测 AVInputFormat
av_probe_input_buffer2
1 | int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, |
AVIOContext *pb
用于读取数据的 AVIOContext,AVInputFormat **fmt
输出推测出来的 AVInputFormat,unsigned int offset
开始推测 AVInputFormat 的偏移量,unsigned int max_probe_size
用于推测格式的媒体数据的最大值
首先需要确定用于推测格式的媒体数据的最大值 max_probe_size。max_probe_size 默认为 PROBE_BUF_MAX(PROBE_BUF_MAX 取值为 1 << 20,即 1048576Byte,大约 1MB)
在确定了 max_probe_size 之后,函数就会进入到一个循环中(因为并不是一次性读取 max_probe_size 字节的媒体数据),调用 avio_read() 读取数据并且使用 av_probe_input_format2()
去推测文件格式:
av_probe_input_format2
1 | AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) |
av_probe_input_format3()
给出了一个分数 score_ret
,然后进行比对:
1 | AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, |
while ((fmt1 = av_iformat_next(fmt1)))
循环遍历 AVInputFormat,该方法中的 first_iformat
链表中的头节点,并根据以下规则确定 AVInputFormat 和输入媒体数据的匹配分数
- 如果 AVInputFormat 中包含 read_probe(),就调用 read_probe() 函数获取匹配分数(这一方法如果结果匹配的话,一般会获得 AVPROBE_SCORE_MAX 的分值,即 100 分)。如果不包含该函数,就使用 av_match_ext() 函数比较输入媒体的扩展名和 AVInputFormat 的扩展名是否匹配,如果匹配的话,设定匹配分数为 AVPROBE_SCORE_EXTENSION(即 50 分)
- 使用 av_match_name() 比较输入媒体的 mime_type 和 AVInputFormat 的 mime_type,如果匹配的话,设定匹配分数为 AVPROBE_SCORE_MIME(即 75 分)
- 如果该 AVInputFormat 的匹配分数大于此前的最大匹配分数,则记录当前的匹配分数为最大匹配分数,并且记录当前的 AVInputFormat 为最佳匹配的 AVInputFormat
read_probe
read_probe()
是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数
1 | AVInputFormat ff_mov_demuxer = { |
其中,read_probe()
函数对应的是 mov_probe()
函数:
1 | static int mov_probe(AVProbeData *p) |
av_match_name
1 | int av_match_name(const char *name, const char *names) |
用于比较两个格式的名称(不区分大小写)
av_match_ext
1 | int av_match_ext(const char *filename, const char *extensions) |
用于比较文件的后缀
io_open
init_input
中调用了 s->io_open
,实际上调用的就是 io_open_default
:
1 | static void avformat_get_context_defaults(AVFormatContext *s) |
在 io_open_default()
中会调用 ffio_open_whitelist()
:
1 | int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, |
接着跟下去:
1 | int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, |
在 ffurl_alloc()
中调用了 url_find_protocol()
方法:
1 | int ffurl_alloc(URLContext **puc, const char *filename, int flags, |
url_find_protocol
1 | #define URL_SCHEME_CHARS \ |
ffurl_get_protocols 可以得到当前编译的 FFmpeg 支持的所有流媒体协议,通过 url 的 scheme 和 protocol->name 相比较,得到正确的 protocol
URLProtocol
url_find_protocol()
方法返回一个 URLProtocol
变量
1 | typedef struct URLProtocol { |
URLProtocol
功能就是完成各种输入协议的读写等操作,ijkplayer 和 ffmpeg 支持的协议也有很多:
1 | extern const URLProtocol ff_async_protocol; |
read_header
read_header()
也是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数
1 | AVInputFormat ff_mov_demuxer = { |
read_header()
指向了 mov_read_header()
函数
1 | static int mov_read_header(AVFormatContext *s) |