从我的笔记ffmpeg-mov格式与分离器实现详解一文中,我们已经知道了mov的demuxer相关实现。本文主要来分析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 **optionsstruct AVDictionary {int count;AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry {char *key;char *value;
} AVDictionaryEntry;

字典类型的可选参数,可以向ffmpeg中传入指定的参数的值。比如我们这里传入了


//number of frames used to probe fpsav_dict_set_int(&ffp->format_opts, "fpsprobesize", 0, 0);

表示fpsprobesize对应的参数值为0,当然还可以传入更多值,具体可以参考options_table.h/libavformat这个头文件。

函数使用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的AVInputFormatif (options)//获取option,拷贝给tmpav_dict_copy(&tmp, *options, 0);if (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;//把获取的option设置给AVFormatContextif ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;//filename赋值给s->filenameav_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置为NOPTSs->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 或者 ttaif (!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;} elseav_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()推测AVInputFormatreturn av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);else if (s->iformat->flags & AVFMT_NOFILE)//如果有AVinputFormat,且flag是AVFMT_NOFILEav_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就直接返回了?这里这样做是为了强调没有给函数提供输入数据,//此时函数仅仅通过文件路径来推测AVInputFormatif ((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;} elsereturn 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;//把输入数据赋值给lpdAVInputFormat *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;} elsenodat = ID3_GREATER_PROBE;}fmt = NULL;//该循环调用av_iformat_next()遍历FFmpeg中所有的AVInputFormatwhile ((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.cbreak;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;//探测文件的字节数,取自AVFormatContextif (!max_probe_size)//如果没有探测字节数max_probe_size = PROBE_BUF_MAX;//设置最大值到max_probe_size中,大约1Melse if (max_probe_size < PROBE_BUF_MIN) {//如果最大的探测字节数小于2048个,直接返回errorav_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是0return 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 0if (!*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)中读取探测数据bufif ((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);} elseav_log(logctx, AV_LOG_DEBUG,"Format %s probed with size=%d and score=%d\n",(*fmt)->name, probe_size, score);
#if 0FILE *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格式与分离器实现详解》。

ffmpeg系列-解复用流程解析相关推荐

  1. 《Android FFmpeg 播放器开发梳理》第一章 播放器初始化与解复用流程

    <Android FFmpeg 播放器开发梳理>: 第零章 基础公共类的封装 播放器初始化与解复用流程 这一章,我们来讲解播放器解复用(从文件中读取数据包)的流程.在讲解播放器的读数据包流 ...

  2. Flv解复用代码解析

    Flv解复用代码解析 目录 总体流程 main函数 处理函数process 解析函数 解析Flv header和Flv Tag 解析Flv header 解析Flv Tag 解析MetaData Ta ...

  3. Android 编译流程解析03-手动编译Apk

    Android编译流程 通过之前两篇文章,我们已经大致了解了编译相关的Gradle,它们的编译流程如下图所示,这篇文章我们来通过手动编译的方式,来模拟Gradle 编译Android APK文件. 手 ...

  4. 【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )

    FFmpeg 系列文章目录 [FFmpeg]Windows 搭建 FFmpeg 命令行运行环境 [FFmpeg]FFmpeg 相关术语简介 [FFmpeg]FFmpeg 相关术语简介 二 [FFmpe ...

  5. FFmpeg从入门到牛掰(一):解复用(demux)讲解

    转载请注明出处:https://blog.csdn.net/impingo 项目地址:https://github.com/im-pingo/pingos 解复用讲解 概念 解复用操作 函数调用流程 ...

  6. 【FFmpeg】FFmpeg 相关术语简介 ( 容器 | 媒体流 | 数据帧 | 数据包 | 编解码器 | 复用 | 解复用 )

    文章目录 一.FFmpeg 简介 二.FFmpeg 相关术语 1.容器 2.媒体流 3.数据帧 4.数据包 5.编解码器 6.复用 7.解复用 博客资源 一.FFmpeg 简介 FFmpeg 是 &q ...

  7. ffmpeg解复用编解码 常用API大全给出详细中文解释

    int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags); 将你给出的条目设置进入你给到的 p ...

  8. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

  9. View系列 (三) — Measure 流程详解

    Measure 流程详解 一.概述 二.单一 View 的测量流程 1. 流程图 2. 源码分析 三.ViewGroup 的测量流程 1. 流程图 2. 源码分析 一.概述 测量过程分为 View的m ...

最新文章

  1. Alibaba代码规范插件、FindBugs插件安装及详解,IDEA插件安装,代码规范,代码查错,代码格式规范
  2. ThinkPHP 分页实现
  3. 网络性能测试工具Iperf上手指南
  4. AGC011D - Half Reflector(模拟)
  5. laravel数据库相关操作说明
  6. 公司绝不会告诉你的20大秘密
  7. html5自动生成图片,HTML5拖放API如何实现自动生成相框功能 HTML5拖放API实现自动生成相框功能代码...
  8. 华为鸿蒙系统自带什么输入法,搜狗输入法华为鸿蒙系统OS2.0版下载-搜狗输入法华为鸿蒙版v10.15.2 手机版-腾飞网...
  9. 2022年按摩椅行业现状分析
  10. Ubuntu 11.04下thrift-0.8.0的安装 - Linux - 红黑联盟
  11. 设计模式-抽象工厂模式
  12. 物联网发展方向(复制来的)
  13. 米尔格拉姆连锁信实验_连锁信:使客户对个性化电子邮件感到满意
  14. 如何在VC中调用第三方lib库(step by step)
  15. 当程序员后,才突然明白的21件事……
  16. 学会做笔记-子弹笔记学习概要一
  17. linux查看端口占用终结,Linux查看端口占用
  18. 微信小程序-简历信息显示
  19. 为什么word一页没写完再写就去下一页了
  20. RHCA-RH436 V1.1.12-PCS集群

热门文章

  1. 一步一步 ITextSharp Anchor Image Chapter Section使用
  2. HDU2005 第几天?【日期计算】
  3. CCSP2020比赛太原理工学子再创佳绩
  4. Graphviz样例之有向图
  5. 计算最大值和最小值(分治法)
  6. 一些自成系统、完备的教程(链接、博客、github等)
  7. C 标准库 —— time.h
  8. Tricks(十七) —— 数组与字典(map)
  9. python 位运算符与逻辑运算符(字符串的逻辑运算)
  10. python下载教程-如何下载python包