一、引言
通过FFmpeg命令:
./ffmpeg -i XXX.flv
可以判断出某个文件是否为FLV文件:
所以FFmpeg是怎样判断出某个文件是否为FLV文件呢?它内部其实是通过flv_probe函数来判断的。从《FFmpeg源码:av_probe_input_format3函数和AVInputFormat结构体分析(FFmpeg源码5.0.3版本)》和《7.0.1版本的FFmpeg源码中av_probe_input_format3函数和AVInputFormat结构体的改变》中我们可以知道:
FFmpeg源码中实现容器格式检测的函数是av_probe_input_format3函数,其内部通过循环while ((fmt1 = av_demuxer_iterate(&i))) 拿到所有容器格式对应的AVInputFormat结构,然后通过score = fmt1->read_probe(&lpd)语句执行不同容器格式对应的解析函数,根据是否能被解析,以及匹配程度,来判断出这是哪种容器格式。而FLV文件对应的解析函数就是flv_probe函数。
二、flv_probe函数的定义
flv_probe函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中,可以看到flv_probe函数内部调用了probe函数:
static int flv_probe(const AVProbeData *p)
{
return probe(p, 0);
}
三、probe函数的定义
probe函数也定义在libavformat/flvdec.c中:
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;
}
probe函数的作用就是检测某个文件是否为flv文件或flv格式的直播流。
形参pd:输入型参数,为AVProbeData类型的指针。
AVProbeData结构体声明在libavformat/avformat.h中:
/**
* This structure contains the data a format has to probe a file.
*/
typedef struct AVProbeData {
const char *filename;
unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
int buf_size; /**< Size of buf except extra allocated bytes */
const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
p->filename为:需要被推测格式的文件/直播流的路径。
p->buf:指向“存放从路径为p->filename的FLV文件/直播流中读取出来的二进制数据”的缓冲区。
p->buf_size:缓冲区p->buf的大小,单位为字节。注:FFmpeg判断某个文件的格式时不会读取完整个文件,只会读取它前面的一部分,比如最开始的2048个字节。只要根据前面的这些字节就足够判断出它的格式了,所以p->buf_size的值一般就是2048。
p->mime_type:一般为NULL,可忽略。
返回值:返回一个类型为整形的分值。返回0表示该文件/直播流完全不符合FLV格式。返回AVPROBE_SCORE_MAX(100)表示该文件/直播流完全符合FLV格式。
形参live:值为1表示需要被推测格式的是直播流,值为0表示需要被推测格式的是本地媒体文件。
四、probe函数的内部实现原理
下面以需要被推测格式的是FLV文件为例,讲解probe函数的内部实现原理。FLV文件对应的解析函数是flv_probe函数,flv_probe函数内部调用了probe函数,这时probe函数的形参live的值为0。
probe函数内部,由于FLV文件的开头就是FLV header,所以指针d指向FLV header:
const uint8_t *d = p->buf;
通过AV_RB32宏定义读取FLV header中的DataOffset属性,得到整个FLV header的以字节为单位的长度,赋值给局部变量offset。关于AV_RB32宏定义的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:
unsigned offset = AV_RB32(d + 5);
从《音视频入门基础:FLV专题(3)——FLV header简介》可以知道,FLV header的前3个字节固定为“FLV”,所有判断d[0] == 'F' && d[1] == 'L' && d[2] == 'V';FLV header的第4个字节为Version,对于FLV格式,值必须为1,所以判断d[3] < 5;整个FLV header的长度固定为9,所以判断offset > 8。不满足条件probe函数返回0,表示完全不符合FLV格式:
if (d[0] == 'F' &&
d[1] == 'L' &&
d[2] == 'V' &&
d[3] < 5 && d[5] == 0 &&
offset + 100 < p->buf_size &&
offset > 8) {
//...
}
return 0;
判断是否为直播流,变量is_live的值为1表示是直播流,值为0表示是FLV文件:
int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);
返回AVPROBE_SCORE_MAX(100)表示该文件/直播流完全符合FLV格式。
if (live == is_live)
return AVPROBE_SCORE_MAX;