音视频日记 - ffmpeg 之 IO 架构分析
协议 (文件) 操作的顶层结构是 AVIOContext
,该对象实现了带缓冲的读写操作
ffmpeg
的输入对象 AVFormatContext
的 pb
字段指向一个 AVIOContext
AVIOContext
的 opaque
实际指向一个 URLContext
对象,这个对象封装了协议对象及协议操作对象 - prot
指向具体的协议操作对象 URLProtocol
- priv_data
指向具体的协议对象
URLProtocol
为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联 - 比如,文件操作对象为 ff_file_protocol
关联的结构体是 FileContext
函数调用关系
+ avformat_open_input()
+---+ init_input() /* 打开输入的视频数据并且探测视频的格式 */
+---- av_probe_input_buffer2()
+---- avio_read()
+---- av_probe_input_format2()
+---- av_probe_input_format3()
+---+ avio_open2()
+---+ ffurl_open_whitelist()
+---+ ffurl_alloc()
+---- url_find_protocol()
+---- url_alloc_for_protocol()
+---+ ffurl_connect()
+---- prot->url_open2()
+---- prot->url_open()
+---+ ffio_fdopen()
+---- avio_alloc_context()
init_input()
/* Open input file and probe the format if necessary. */
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = {filename, NULL, 0};
int score = AVPROBE_SCORE_RETRY;
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and"
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat) return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
这个函数在短短的几行代码中包含了好几个 return
,因此逻辑还是有点复杂的,我们可以梳理一下:
在函数的开头的 score
变量是一个判决 AVInputFormat
的分数的门限值,如果最后得到的 AVInputFormat
的分数低于该门限值,就认为没有找到合适的 AVInputFormat
。ffmpeg
内部判断封装格式的原理实际上是对每种 AVInputFormat
给出一个分数,满分是 100
分,越有可能正确的 AVInputFormat
给出的分数就越高。最后选择分数最高的 AVInputFormat
作为推测结果。score
的值是一个宏定义 AVPROBE_SCORE_RETRY
,我们可以看一下它的定义:
#define AVPROBE_SCORE_MAX 100 ///< maximum score
#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
由此我们可以得出 score
取值是 25
,即如果推测后得到的最佳 AVInputFormat
的分值低于 25
,就认为没有找到合适的 AVInputFormat
整个函数的逻辑大体如下:
- 当使用了自定义的
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