从学龄前开始解读FFMPEG代码 之 avformat_open_input函数 read_probe和read_head
开始学习前想说的话
在之前的open_input函数一二三的学习中,学龄前课程以一个代码运行的时序来进行了代码讲解,但是毕竟没有用一个较好的实例说明,而且在av_probe_input_format3函数中,学龄前学习也提到了,对于需要推测文件格式的数据源,会调用文件格式列表里面每种文件格式的read_probe函数去给一个评分,那么在avformat_open_input整个过程中会用到的read_probe和read_head具体要怎么做呢?本文以avi和flv两种视频封装格式为例子进行一下学龄前讨论学习
avi格式视频要如何probe
首先来看一下avi格式视频是怎么封装的,该格式的封装代码函数在libavformat/avidec.c中,拉到代码最后一行可以看到,该封装格式定义如下:
AVInputFormat ff_avi_demuxer = {
.name = "avi",
.long_name = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"),
.priv_data_size = sizeof(AVIContext),
.extensions = "avi",
.read_probe = avi_probe,
.read_header = avi_read_header,
.read_packet = avi_read_packet,
.read_close = avi_read_close,
.read_seek = avi_read_seek,
.priv_class = &demuxer_class,
};
可以看到哈,avi格式设置了自己的read_probe函数,函数名字叫avi_probe,就在同一个文件里,如下所示:
static int avi_probe(const AVProbeData *p)
{
int i;
/* check file header */
for (i = 0; avi_headers[i][0]; i++)
if (AV_RL32(p->buf ) == AV_RL32(avi_headers[i] ) &&
AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4))
return AVPROBE_SCORE_MAX;
return 0;
}
看上去非常简短哈,但是这里涉及到了一个比较重要的变量是avi_headers,一个二位数组,记录了一些headers信息,这个数组在同文件中定义了,如下所示:
static const char avi_headers[][8] = {
{ 'R', 'I', 'F', 'F', 'A', 'V', 'I', ' ' },
{ 'R', 'I', 'F', 'F', 'A', 'V', 'I', 'X' },
{ 'R', 'I', 'F', 'F', 'A', 'V', 'I', 0x19 },
{ 'O', 'N', '2', ' ', 'O', 'N', '2', 'f' },
{ 'R', 'I', 'F', 'F', 'A', 'M', 'V', ' ' },
{ 0 }
};
那么在probe里的这个*AV_RL32(p- >buf) == AV_RL32(avi_headers[i])*是在比较什么呢?我们知道,文件以二进制数据的方式存储,而文件头,即文件head中一般存储了关于这个文件的各种相关信息;实际上,AVI 文件是一种RIFF 文件格式,所以其文件头部的组成为 ‘RIFF’(4字节) + RIFF文件大小(4字节) + ‘AVIx’(文件类型,4字节,x可能为不同的类型,对应不同的字符)共12字节,如果要推测检查buf中是不是avi的文件头,就是将读入的文件头中的byte 0 ~ Byte 3(32位所以用了FFmpeg中的宏,是AV_RL32), byte 8 ~ Byte 11 与 avi_header 进行比较,如果对比成功,那么就证明该文件是avi文件,直接一伯分通过!否则,直接给予一个0的评分,推测条件非常严格,没有其他的中间分数。
从这个函数来看,avi的probe函数还是较为简单的,下面来看一下flv的probe函数是怎么样的
FLV格式视频是如何probe的
flv的probe函数写在了libavformat/flvdec.c文件中,该文件里有flv,有flv_live以及kux(优酷所使用的一种格式)等flv解码的格式,那么来看一下flv的probe吧
static int flv_probe(const AVProbeData *p)
{
return probe(p, 0);
}
直接指向了该文件内的probe函数,所以实际上再跳转到probe函数中
static int probe(const AVProbeData *p, int live)
{
const uint8_t *d = p->buf;
unsigned offset = AV_RB32(d + 5);
if (d[0] == 'F' &&
d[1] == 'L' &&
d[2] == 'V' &&
d[3] < 5 && d[5] == 0 &&
offset + 100 < p->buf_size &&
offset > 8) {
int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);
if (live == is_live)
return AVPROBE_SCORE_MAX;
}
return 0;
}
在flv的flv_probe中跳转到了同文件中的函数probe,而在该函数中可以看到,flv采用了跟avi差不多的文件头数据的字节对比的方法,首先用AV_RB32(d + 5)这样一个宏定义获取了byte 5~byte 8的数据(从byte0开始算的话),并使用AV_RB32进行了大小端转换,转换为小端并存到变量offset中。
之后,检查开头3个字符(代表signature)是否为“FLV”,再检查第四个字符(代表flv的version)是否小于5,再判断第6个字节(Headersize的第1个字节)为0,以及offset取值大于8.进入if判断后,是live flv和flv种类的判断,通过之后直接返回一伯分!同样的,如果没有通过检查,那么直接没有余地,评分为零。
FLV文件read_header做了什么
继续上面的flv分析,flv文件里的read_header又要做什么呢?
flv的read_header也在libavformat/flvdec.c文件中,如下所示:
static int flv_read_header(AVFormatContext *s)
{
int flags;
FLVContext *flv = s->priv_data;
int offset;
int pre_tag_size = 0;
/* Actual FLV data at 0xe40000 in KUX file */
if(!strcmp(s->iformat->name, "kux"))
avio_skip(s->pb, 0xe40000);
avio_skip(s->pb, 4);
flags = avio_r8(s->pb);
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);
s->ctx_flags |= AVFMTCTX_NOHEADER;
offset = avio_rb32(s->pb);
avio_seek(s->pb, offset, SEEK_SET);
/* Annex E. The FLV File Format
* E.3 TheFLVFileBody
* Field Type Comment
* PreviousTagSize0 UI32 Always 0
* */
pre_tag_size = avio_rb32(s->pb);
if (pre_tag_size) {
av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
}
s->start_time = 0;
flv->sum_flv_tag_size = 0;
flv->last_keyframe_stream_index = -1;
return 0;
}
在代码中可以看见,声明好需要使用到的变量之后,代码流程中首先对kux格式(优酷的一种视频文件格式)做好skip(从代码来看实际上只是做了一个位移嘛…感觉针对这文件类型youku是不是也没有使用什么特别的视频算法编码之类的…)之后,将pb跳过4个字节,并读取设置好头部存储的flag位。
之后,检查数据中是否有视频和音频文件并设置标记位missing_streams,然后在ctx_flags中设置好noheader位置表示头部已经移动处理了。
在此之后,设置好位移offset,再对s的pb进行重定位,判断好pretagsize,之后设置好开始时间等等变量,完成整个过程。
结束时要说的话
为什么不分析avi的read_header函数呢?因为实在是太长了,看到头疼。总体上来说,每种视频格式使用到的函数也由它们自身视频文件特点来决定,所以本文只通过几个简单举例,来了解这两个函数的主要功能大概是做什么的就可以了。
不过接下来最为头疼的还是接下来几篇学习中即将要拿来分析的avformat_find_stream_info函数,该函数用于在打开解码器之前,提取文件数据,尝试读取一些视频的packet包数据,做好文件中stream等等的各项设置,功能比较重要,总共有五百多行,预计要分成三到四篇才能讲得完…而且也是个函数挖坑重灾区,慢慢走起吧