本文主要来分析demuxer的流程。
1.结构流程图
从上面的结构图中我们可以看到AVFormatContext的iformat指向AVInputFormat。
2.实现流程图
3.avformat_open_input函数作用
首先看函数的声明
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
这个函数的作用是打开文件的链接,如果是网络连接,还会发起网络请求,并一直等待网络数据的返回,然后读取视频流的数据(协议操作)。
AVFormatContext **ps
该函数的主要作用是填充好AVFormatContext **ps这个结构体。AVFormatContext这个结构体里面的参数比较多,这里就不一一列举了,详细可以参考avformat.h这个头文件,具体用到到时再详细说明。
const char *filename
文件的全部路径,比如
http://videocdn.eebbk.net/7abbdb39fd2aa71efa64f9897f4f5616.mp4
AVInputFormat *fmt
AVInputFormat 的结构体也比较复杂,主要是封装媒体数据封装类型的结构体,比如flv,mpegts,mp4等。在这里可以传入空,如果为空,后面就会从网络数据中读出。当然如果我们知道文件的类型,先用av_find_input_format(“mp4”)初始化出对应的结构体,这里我们用的是mp4,先初始化好这个结构体,比如对于直播来说,会比较节约时间。
AVDictionary **options
struct AVDictionary {
int count;
AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
字典类型的可选参数,可以向ffmpeg中传入指定的参数的值。比如我们这里传入了
//number of frames used to probe fps
av_dict_set_int(&ffp->format_opts, "fpsprobesize", 0, 0);
表示fpsprobesize对应的参数值为0,当然还可以传入更多值,具体可以参考options_table.h/libavformat这个头文件。
文章最后扫码进qun,可免费领取音视频学习资料,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等
函数使用sample:
AVFormatContext *ic = NULL;//初始化ic变量
//申请AVFormatContext内存
ic = avformat_alloc_context();
if (!ic) {//内存申请失败
av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
ret = AVERROR(ENOMEM);
goto fail;
}
ic->interrupt_callback.callback = decode_interrupt_cb;//IO层错误回调
ic->interrupt_callback.opaque = is;
if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
scan_all_pmts_set = 1;
}
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
if (err < 0) {
print_error(is->filename, err);
ret = -1;
goto fail;
}
4.流程实现分析
讲解解复用流程,我们还是需要从avformat_open_input/utils.c/libavformat函数开始分析整个解析流程。
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//如果s为空,且没有申请内存的话,申请AVFormatContext内存
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
if (!s->av_class) {
av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
return AVERROR(EINVAL);
}
if (fmt)
s->iformat = fmt;//AVInputFormat赋值给AVFormatContext的AVInputFormat
if (options)//获取option,拷贝给tmp
av_dict_copy(&tmp, *options, 0);
if (s->pb) // must be before any goto fail
s->flags |= AVFMT_FLAG_CUSTOM_IO;
//把获取的option设置给AVFormatContext
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
//filename赋值给s->filename
av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
//初始化协议与初始化解封装(文件探测等)
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;//获取文件探测回来的分数
//协议白名单
if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
if (!s->protocol_whitelist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
//协议黑名单
if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
if (!s->protocol_blacklist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
ret = AVERROR(EINVAL);
goto fail;
}
//打开流的时候跳过最初的字节
avio_skip(s->pb, s->skip_initial_bytes);
/* Check filename in case an image number is expected. */
if (s->iformat->flags & AVFMT_NEEDNUMBER) {
if (!av_filename_number_test(filename)) {
ret = AVERROR(EINVAL);
goto fail;
}
}
//把AVFormatContext的duration和start_time置为NOPTS
s->duration = s->start_time = AV_NOPTS_VALUE;
/* Allocate private data. */
if (s->iformat->priv_data_size > 0) {
if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (s->iformat->priv_class) {
*(const AVClass **) s->priv_data = s->iformat->priv_class;
av_opt_set_defaults(s->priv_data);
if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
goto fail;
}
}
/* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
if (s->pb)
ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
//在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,
//avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
//id3v2信息的处理
//如果metadata是空的话
if (!s->metadata) {
s->metadata = s->internal->id3v2_meta;
s->internal->id3v2_meta = NULL;
} else if (s->internal->id3v2_meta) {
int level = AV_LOG_WARNING;
if (s->error_recognition & AV_EF_COMPLIANT)
level = AV_LOG_ERROR;
av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
av_dict_free(&s->internal->id3v2_meta);
if (s->error_recognition & AV_EF_EXPLODE)
return AVERROR_INVALIDDATA;
}
if (id3v2_extra_meta) {//如果文件名是mp3 aac 或者 tta
if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
!strcmp(s->iformat->name, "tta")) {
if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
goto fail;
if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
goto fail;
} else
av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
}
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
if ((ret = avformat_queue_attached_pictures(s)) < 0)
goto fail;
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
s->internal->data_offset = avio_tell(s->pb);
s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
update_stream_avctx(s);
for (i = 0; i < s->nb_streams; i++)
s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;
if (options) {
av_dict_free(options);
*options = tmp;
}
*ps = s;
return 0;
fail:
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
av_dict_free(&tmp);
if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
avio_closep(&s->pb);
avformat_free_context(s);
*ps = NULL;
return ret;
}
从avformat_open_input中我们可以知道函数先调用init_input来实现解协议和解封装,在解协议和解封装结束后调用iformat->read_header来读取文件头,根据视音频流创建相应的AVStream。还有一些逻辑判断,如黑白协议表以及id3v2等的处理。
下面我们看下init_input/utils.c/libavformat函数
/*
* 打开输入文件,探测文件格式,返回的是探测文件所得的分数
* 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;//默认score 25分
//如果已经有了AVIOContext,也就是文件(协议)已经打开了
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)//如果AVFormatContext中没有指定AVInputformat,指定就调用av_probe_input_buffer2()推测AVInputFormat
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)//如果有AVinputFormat,且flag是AVFMT_NOFILE
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
//如果已经指定了AVInputformat,且flag是AVFMT_NOFILE;
//或者如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式,来获取AVInputFormat
//(注意)这里没有获取到AVIOContext 没有获取到AVIOContext就直接返回了?这里这样做是为了强调没有给函数提供输入数据,
//此时函数仅仅通过文件路径来推测AVInputFormat
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
//如果AVIOContext和AVInputFormat都没有指定的话
//调aviobuf.c中的avio_open函数来打开协议(打开文件),获取AVIOContext,如果获取失败了,直接返回
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
if (s->iformat)//如果协议打开有了AVInputforamt,则直接返回
return 0;
//解完协议,创建打开一个AVIOContext后
//调用foramt.c中的av_probe_input_buffer2来探测获取AVInputFormat并返回探测分数
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
init_input函数主要用来打开输入文件(协议),探测文件格式,返回的是探测文件所得的分数。
函数中做了一下判断:
1.如果已经有了AVIOContext,也就是协议已经打开了,但没有AVInputFormat,则使用av_probe_input_buffer2来推测AVInputFormat。
2.如果已经有了AVInputFormat,直接返回;如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式(这里是没有获取到AVIOContext的,所以是根据路径来探测文件格式)。
3.如果AVIOContext和AVInputFormat都没有指定的话,调avio_open/aviobuf.函数来打开协议(打开文件),打开文件后(也就是获取一个AVIOContext后),调用av_probe_input_buffer2/foramt.c来探测获取AVInputFormat,av_probe_input_buffer2内部不断调用avio_read来读取数据,然后调用av_probe_input_format2来获取AVInputFormat,并返回探测分数。
接下来我们围绕三个判断来分别看一下流程实现。
先分析第二种情况,如果已经有了AVInputFormat,直接返回;如果没有指定AVInputFormat,调用av_probe_input_format2(NULL,…)根据文件路径判断文件格式(这里是没有获取到AVIOContext的,pb也是null,所以是根据路径来探测文件格式)。看下av_probe_input_format2(在第三种情况时内部也调用到了av_probe_input_format2函数)
/***
*将得到的匹配分数与要求的匹配值相比较,
*如果匹配分数>匹配值,这返回得到的解复用器,否则返回NULL
*pd:存储输入数据信息的AVProbeData结构体。
*is_opened:文件是否打开。
*score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。
**/
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)
{
int score_ret;
AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
//当av_probe_input_format3()返回的分数大于score_max的时候,
//才会返回AVInputFormat,否则返回NULL。
if (score_ret > *score_max) {
*score_max = score_ret;
return fmt;
} else
return NULL;
}
当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。下面跟踪函数跳转到av_probe_input_format3/format.c函数,再看这个函数前,我们先看下AVProbeData结构体,这个结构体主要是包含探测文件的data数据:
/**
* This structure contains the data a format has to probe a file.
* 这个结构体包含探测文件的data数据
*/
typedef struct AVProbeData {
const char *filename;//文件路径
unsigned char *buf; /**< buf存储用于推测AVInputFormat的媒体数据,其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0) Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
int buf_size; /**< 推测AVInputFormat的媒体数据的大小Size of buf except extra allocated bytes */
const char *mime_type; /**<保存媒体的类型 mime_type, when known. */
} AVProbeData;
继续分析av_probe_input_format3/format.c函数,在第二种情况中,是没有获取到AVIOContext的,所以也没有获取到探测AVInputformat的媒体数据。
/**
*该函数遍历所有的解复用器,调用它们的read_probe()函数计算匹配得分,
*如果解复用器定义了文件扩展名,还会比较输匹配数据跟扩展名的匹配得分。
*函数最终返回计算找到的最匹配的解复用器,并将匹配分数也返回。
*根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中
**/
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;//把输入数据赋值给lpd
AVInputFormat *fmt1 = NULL, *fmt;
int score, score_max = 0;
const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
enum nodat {
NO_ID3,
ID3_ALMOST_GREATER_PROBE,
ID3_GREATER_PROBE,
ID3_GREATER_MAX_PROBE,
} nodat = NO_ID3;
if (!lpd.buf)
lpd.buf = (unsigned char *) zerobuffer;
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
int id3len = ff_id3v2_tag_len(lpd.buf);
if (lpd.buf_size > id3len + 16) {
if (lpd.buf_size < 2LL*id3len + 16)
nodat = ID3_ALMOST_GREATER_PROBE;
lpd.buf += id3len;
lpd.buf_size -= id3len;
} else if (id3len >= PROBE_BUF_MAX) {
nodat = ID3_GREATER_MAX_PROBE;
} else
nodat = ID3_GREATER_PROBE;
}
fmt = NULL;
//该循环调用av_iformat_next()遍历FFmpeg中所有的AVInputFormat
while ((fmt1 = av_iformat_next(fmt1))) {
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
if (fmt1->read_probe) {
//调用demuxer中的read_probe来探测文件,获取匹配分数(这一方法如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)
score = fmt1->read_probe(&lpd);
if (score)
av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
//如果没有read_probe函数来探测文件,也就是不包含这个函数的话,
//就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的扩展名是否匹配,
//如果匹配的话,设定匹配分数为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。
if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
switch (nodat) {
case NO_ID3:
score = FFMAX(score, 1);
break;
case ID3_GREATER_PROBE:
case ID3_ALMOST_GREATER_PROBE:
score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
break;
case ID3_GREATER_MAX_PROBE:
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
break;
}
}
} else if (fmt1->extensions) {
if (av_match_ext(lpd.filename, fmt1->extensions))
score = AVPROBE_SCORE_EXTENSION;
}
//使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,
//如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)。
if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
if (AVPROBE_SCORE_MIME > score) {
av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
score = AVPROBE_SCORE_MIME;
}
}
//如果该AVInputFormat的匹配分数大于此前的最大匹配分数,
//则记录当前的匹配分数为最大匹配分数,
//并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。
if (score > score_max) {
score_max = score;
fmt = fmt1;
} else if (score == score_max)
fmt = NULL;
}
if (nodat == ID3_GREATER_PROBE)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;//返回获取最大分数值的AVInputFormat
}
根据输入数据来查找获取合适的AVInputFormat。输入的数据在AVProbeData中,在while循环中调用av_iformat_next来遍历FFMpeg中所有的AVInputFormat,并根据下面的规则来确定AVInputFormat和输入媒体数据的匹配分数。
1.如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数(这一方法如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分)。如果不包含该函数,就使用av_match_ext()函数比较输入媒体的扩展名和AVInputFormat的扩展名是否匹配,如果匹配的话,设定匹配分数为AVPROBE_SCORE_EXTENSION(AVPROBE_SCORE_EXTENSION取值为50,即50分)。
2.使用av_match_name()比较输入媒体的mime_type和AVInputFormat的mime_type,如果匹配的话,设定匹配分数为AVPROBE_SCORE_MIME(AVPROBE_SCORE_MIME取值为75,即75分)。
3.如果该AVInputFormat的匹配分数大于此前的最大匹配分数,则记录当前的匹配分数为最大匹配分数,并且记录当前的AVInputFormat为最佳匹配的AVInputFormat。
我们看下mov中read_probe/mov.c/libavformat函数的实现
/*
*文件探测函数
*/
static int mov_probe(AVProbeData *p)
{
int64_t offset;
uint32_t tag;
int score = 0;
int moov_offset = -1;
/* check file header */
offset = 0;
for (;;) {
/* ignore invalid offset */
if ((offset + 8) > (unsigned int)p->buf_size)//如果探测数据buf大小小于8的话,直接退出,第一次探测传进来的默认是2048,可以看av_probe_input_buffer2/format.c
break;
tag = AV_RL32(p->buf + offset + 4);//第一次取的时候偏移为0,则从buf指针开始偏移4个字节,后面再次偏移时加上offset
//如果第一次的话,则获得第5个数据到第8个数据,正常的mp4,也就是ftyp头了
switch(tag) {
/* 检查tag,check for obvious tags */
case MKTAG('m','o','o','v'):
moov_offset = offset + 4;
case MKTAG('m','d','a','t'):
case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */
case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */
case MKTAG('f','t','y','p'):
if (AV_RB32(p->buf+offset) < 8 &&
(AV_RB32(p->buf+offset) != 1 ||
offset + 12 > (unsigned int)p->buf_size ||
AV_RB64(p->buf+offset + 8) == 0)) {
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
} else if (tag == MKTAG('f','t','y','p') &&
( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')//如果第8个字节到第12个字节是jp2_或jpx_,正常的mp4是isom
|| AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
)) {
score = FFMAX(score, 5);//打5分
} else {//正常的mp4视频直接进入这里检测
//hxk 添加测试
if(AV_RL32(p->buf + offset + 8) == MKTAG('i','s','o','m')){//获取buf的第8个字节到第12个字节(offset是0)
av_log(NULL, AV_LOG_ERROR, "hxk>>>>isom!\n");
}
//添加结束
score = AVPROBE_SCORE_MAX;//100分
}
offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
break;
/* those are more common words, so rate then a bit less */
case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */
case MKTAG('w','i','d','e'):
case MKTAG('f','r','e','e'):
case MKTAG('j','u','n','k'):
case MKTAG('p','i','c','t'):
score = FFMAX(score, AVPROBE_SCORE_MAX - 5);
offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
break;
case MKTAG(0x82,0x82,0x7f,0x7d):
case MKTAG('s','k','i','p'):
case MKTAG('u','u','i','d'):
case MKTAG('p','r','f','l'):
/* if we only find those cause probedata is too small at least rate them */
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
break;
default:
offset = FFMAX(4, AV_RB32(p->buf+offset)) + offset;
}
}
if(score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {
/* moov atom in the header - we should make sure that this is not a
* MOV-packed MPEG-PS */
offset = moov_offset;
while(offset < (p->buf_size - 16)){ /* Sufficient space */
/* We found an actual hdlr atom */
if(AV_RL32(p->buf + offset ) == MKTAG('h','d','l','r') &&
AV_RL32(p->buf + offset + 8) == MKTAG('m','h','l','r') &&
AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')){
av_log(NULL, AV_LOG_WARNING, "Found media data tag MPEG indicating this is a MOV-packed MPEG-PS.\n");
/* We found a media handler reference atom describing an
* MPEG-PS-in-MOV, return a
* low score to force expanding the probe window until
* mpegps_probe finds what it needs */
return 5;
}else
/* Keep looking */
offset+=2;
}
}
return score;
}
从上面可以知道,mov的探测文件主要是从AVProbeData的探测数据buf中前4个字节开始,获取ftyp box,当查找到ftyp box后再处理一些其他的判断,然后直接返回AVPROBE_SCORE_MAX(100分)。
这里的AVProbeData 第一次探测传进来的默认是2048个字节,pb->size = 2048,具体实现逻辑在av_probe_input_buffer2/format.c中,不断地读取数据,知道读取最大的探测数据(1M大小这样).
接下来我们看下av_probe_input_buffer2/format.c函数,这里是通过io_open打开文件,获取到AVIOContext后再调用av_probe_input_buffer2函数来探测AVInputFormat(这里的AVProbeData是有数据的)。
/**
*
*文件探测
*pb:用于读取数据的AVIOContext。
*fmt:输出推测出来的AVInputFormat。
*filename:输入媒体的路径。
*logctx:日志(没有研究过)。
*offset:开始推测AVInputFormat的偏移量。
*max_probe_size:用于推测格式的媒体数据的最大值。
av_probe_input_buffer2()首先需要确定用于推测格式的媒体数据的最大值max_probe_size。
max_probe_size默认为PROBE_BUF_MAX(PROBE_BUF_MAX取值为1 << 20,即1048576Byte,大约1MB)
在确定了max_probe_size之后,函数就会进入到一个循环中,
调用avio_read()读取数据并且使用av_probe_input_format2()(该函数前文已经记录过)
推测文件格式。
肯定有人会奇怪这里为什么要使用一个循环,
而不是只运行一次?其实这个循环是一个逐渐增加输入媒体数据量的过程。
av_probe_input_buffer2()并不是一次性读取max_probe_size字节的媒体数据,
我个人感觉可能是因为这样做不是很经济,
因为推测大部分媒体格式根本用不到1MB这么多的媒体数据。
因此函数中使用一个probe_size存储需要读取的字节数,
并且随着循环次数的增加逐渐增加这个值。
函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取,
如果通过这些数据已经可以推测出AVInputFormat,
那么就可以直接退出循环了(参考for循环的判断条件“!*fmt”);
如果没有推测出来,就增加probe_size的量为过去的2倍(参考for循环的表达式“probe_size << 1”),
继续推测AVInputFormat;如果一直读取到max_probe_size字节的数据依然没能确定AVInputFormat,
则会退出循环并且返回错误信息
**/
int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
const char *filename, void *logctx,
unsigned int offset, unsigned int max_probe_size)
{
AVProbeData pd = { filename ? filename : "" };
uint8_t *buf = NULL;
int ret = 0, probe_size, buf_offset = 0;
int score = 0;
int ret2;
//探测文件的字节数,取自AVFormatContext
if (!max_probe_size)//如果没有探测字节数
max_probe_size = PROBE_BUF_MAX;//设置最大值到max_probe_size中,大约1M
else if (max_probe_size < PROBE_BUF_MIN) {//如果最大的探测字节数小于2048个,直接返回error
av_log(logctx, AV_LOG_ERROR,
"Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);
return AVERROR(EINVAL);
}
if (offset >= max_probe_size)//如果偏移大于最大探测字节数,刚传进来的offset是0
return AVERROR(EINVAL);
if (pb->av_class) {
uint8_t *mime_type_opt = NULL;
char *semi;
av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
pd.mime_type = (const char *)mime_type_opt;//赋值mime_type到probedata中
av_log(NULL, AV_LOG_ERROR,"hxk>>>>mime_type:%s\n",pd.mime_type);
semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
if (semi) {
*semi = '\0';
}
}
#if 0
if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) {
if (!av_strcasecmp(mime_type, "audio/aacp")) {
*fmt = av_find_input_format("aac");
}
av_freep(&mime_type);
}
#endif
//for循环处理探测数据
//函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取;probe_size小于最大探测值,且没有fmt
//每一次for循环probe_size的值增加一倍
for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
probe_size = FFMIN(probe_size << 1,
FFMAX(max_probe_size, probe_size + 1))) {//比较probe_size+1与探测最大值的最大值,然后取探测值的2倍与最大值做比较
//如果探测值小于最大探测值(约1m) 打分25分
score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;
/* 为探测数据buf申请内存空间Read probe data. */
if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
goto fail;
//从pb(AVIOContext)中读取探测数据buf
if ((ret = avio_read(pb, buf + buf_offset,
probe_size - buf_offset)) < 0) {
/* Fail if error was not end of file, otherwise, lower score. */
if (ret != AVERROR_EOF)
goto fail;
score = 0;
ret = 0; /* error was end of file, nothing read */
}
buf_offset += ret;
if (buf_offset < offset)
continue;
pd.buf_size = buf_offset - offset;
pd.buf = &buf[offset];
memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);
/* 将上面读取到的pd(AVProbedata)传入到av_probe_input_format2函数
猜文件格式,探测合适的解复用器Guess file format. */
*fmt = av_probe_input_format2(&pd, 1, &score);
if (*fmt) {//获取fmt
/* This can only be true in the last iteration. */
if (score <= AVPROBE_SCORE_RETRY) {//如果分数小于25分
av_log(logctx, AV_LOG_WARNING,
"Format %s detected only with low score of %d, "
"misdetection possible!\n", (*fmt)->name, score);
} else
av_log(logctx, AV_LOG_DEBUG,
"Format %s probed with size=%d and score=%d\n",
(*fmt)->name, probe_size, score);
#if 0
FILE *f = fopen("probestat.tmp", "ab");
fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);
fclose(f);
#endif
}
}
if (!*fmt)//for循环没有获取到AVInputFormat,返回数据错误
ret = AVERROR_INVALIDDATA;
fail:
/* 将探测数据返回给AVIOContext的缓冲buffer,Rewind. Reuse probe buffer to avoid seeking. */
ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
if (ret >= 0)
ret = ret2;
av_freep(&pd.mime_type);
return ret < 0 ? ret : score;
}
从上面可以看到,在确定了max_probe_size之后(第一次探测的数据大小是2048个字节),函数就会进入到一个循环中,调用avio_read()读取数据并且使用av_probe_input_format2()推测文件格式(这里传进去的AVProbeData是有数据的)。
肯定有人会奇怪这里为什么要使用一个循环,
而不是只运行一次?其实这个循环是一个逐渐增加输入媒体数据量的过程。
av_probe_input_buffer2()并不是一次性读取max_probe_size字节的媒体数据,
我个人感觉可能是因为这样做不是很经济,因为推测大部分媒体格式根本用不到1MB这么多的媒体数据。因此函数中使用一个probe_size存储需要读取的字节数,并且随着循环次数的增加逐渐增加这个值。
函数首先从PROBE_BUF_MIN(取值为2048)个字节开始读取,如果通过这些数据已经可以推测出AVInputFormat,那么就可以直接退出循环了(参考for循环的判断条件“!*fmt”);如果没有推测出来,就增加probe_size的量为过去的2倍(参考for循环的表达式“probe_size<<1”),继续推测AVInputFormat;如果一直读取到max_probe_size字节的数据依然没能确定AVInputFormat,则会退出循环并且返回错误信息。
在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。这里还是以mov来举例。由于mov的read_header代码以及相关细节比较多,关于mov的read_header的具体分析见《ffmpeg-mov格式与分离器实现详解》。