本文所使用的是FFmpeg n4.4的源码,所有分析均来自博主瞎猜,如果有误,欢迎批评指正。
建议边调试源码,边看对应的源码分析。走上一遍就了解个大概了。

avformat_open_input 作用

打开一个input类型的AVFormatContext。

AVFormatContext中包含了对这个输入文件的基本信息的总和,比如开始时间,时长,码率,流和对应的流信息AVStream,除此之前还有读取文件的上下文AVIOContext, 打开的文件格式对应的AVInputFormat等。

而avformat_open_input需要做的就是根据输入的参数,初始化这些信息。

avformat_open_input 源码分析:

int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;int i, ret = 0;AVDictionary *tmp = NULL;ID3v2ExtraMeta *id3v2_extra_meta = NULL;// 输入的ps可以已经指向一个AVFormatContext,这样在后续的过程中,就可以利用这些已经初始化过的参数进行处理,//FFmpeg源码中,也是先在外面先调用avformat_alloc_context,然后设置参数,然后再调用avformat_open_input的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;if (options)av_dict_copy(&tmp, *options, 0);//AVFormatContext在为初始化之前,就已经被赋予了IO操作上下文AVIOContext,这样的话,调用avformat_close_input也不会自动关闭AVIOContext,需要让用户自己调用avio_closep或其他方法关闭流。//AVFMT_FLAG_CUSTOM_IO就是标识这种情况的if (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;//这里是把所有设置的参数保存在AVFormatContext的AVClass*中,因为后续需要把AVFormatContext* 强转成为AVClass** 类型,//所以AVClass*必须在AVFormatContext结构体的头部,其它结构体也是这个思路。if ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;if (!(s->url = av_strdup(filename ? filename : ""))) {ret = AVERROR(ENOMEM);goto fail;}#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGSav_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif// 这个函数比较重要,就是根据之前的输入,遍历所有的 解封装器,然后选出一个评分最高的,作为AVFormatContext的AVInputFormat。具体过程看下面的分析// 在此过程中,如果需要open AVIOContext,就根据输入的filename,找到对应的URLProtocol// 然后生成对应URLContext和AVIOContext,完成流read,seek等接口的创建。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;}//将生成的AVIOContext跳转到skip_initial_bytes之后,这个是用户手动设置的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;}}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 FF_API_DEMUXER_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#elseif (s->iformat->read_header)
#endif// read header 从这里创建AVStreamif ((ret = s->iformat->read_header(s)) < 0)goto fail;if (!s->metadata) {s->metadata = s->internal->id3v2_meta;s->internal->id3v2_meta = NULL;} else if (s->internal->id3v2_meta) {av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");av_dict_free(&s->internal->id3v2_meta);}if (id3v2_extra_meta) {if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||!strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)goto close;if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)goto close;if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)goto close;} 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 close;#if FF_API_DEMUXER_OPENif (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#elseif (s->pb && !s->internal->data_offset)
#endifs->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;close:if (s->iformat->read_close)s->iformat->read_close(s);
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包含很多,包括根据输入参数选择不同的处理方式,看看AVFormatContext*是否已经初始化等等,还有对特殊格式,url的协议等处理过程,但是这些不是很关键,这里面最关键的是两个函数。

  • init_input() 这个函数非常重要,AVFormatContext中大部分信息都是在这里面初始化的,包括根据输入的url找到对应的URLContext,打开IO流,从IO流中读取部分自己,然后探测格式,从而选择对应的AVInputFormat等等,这些操作都是在init_input()中完成的,总结来说init_input()创建了AVIOContext,和AVInputFormat。
  • s->iformat->read_header() 这个函数是根据上面确定的AVInputFormat和AVIOContext读取头部数据,然后创建AVStream。

下面来分析一下init_input() 函数实现

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 };//初识评分 25,(最大为100) 如果后面的评分小于这个值,就认为没有找到int score = AVPROBE_SCORE_RETRY;//自定义AVIOContext,未自定义时s->pb = null//所以自定义AVIOContext应该在调用avformat_open_input之前调用if (s->pb) {s->flags |= AVFMT_FLAG_CUSTOM_IO;// 如果已经打开了IO,就调用av_probe_input_buffer2// s->format_probesize默认是 1 << 20(1的20次方)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;}//这个时候 AVProbeData中的buffer为NULL,所以不能通过read_probe读取一定字节,然后判断,//故这里实际上是看这个输入文件的后缀名是不是那种没有实现read_probe格式的,如果是就返回//所以这里要添加标记位AVFMT_NOFILE,比如如果输入使用的某些设备(显示器)等,就可以在这里通过名字判断//不过一般情况都是往下调用av_probe_input_buffer2if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;// 打开文件的回调,如果没有单独设置,就调用默认的回调(io_open_default)if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;if (s->iformat)return 0;//通过调用io_open函数后,已经初始化IO,这时候读取头部信息,判断格式return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}

init_input 中,最关键的流程是,通过调用io_open()函数,初始化AVIOContext,然后再通过调用av_probe_input_buffer2()函数,找到适合的AVInputFormat。

除此之前,如果自定义了AVIOContext,需要在调用avformat_open_input之前就将其赋值给s->pb ,这样在调用av_probe_input_format2来探测AVInputFormat的时候,就可以调用自定义的AVIOContext。

如果输入的并不是文件或者网络流等常规的流输入,而是设备等信息,比如显示器,音频设备等(常见是在libavdevice中),就不需要调用AVIOContext来输入,所以就不在调用io_open()函数,这个时候可以直接调用av_probe_input_format2()函数,根据名称查找设备。

下面详细分析一下具体的探测函数。

av_probe_input_format2源码

ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{int score_ret;//调用av_probe_input_format3 选择出所有AVInputFormat中评分最大的AVInputFormatff_const59 AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);//评分如果比预期的差,就不选择这个if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}

av_probe_input_format3源码

ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened,int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;ff_const59 AVInputFormat *fmt = NULL;int score, score_max = 0;void *i = 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;//id3v2是音频文件中的metadata,尤其是mp3文件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;}// 遍历demuxer_list 解封装list,然后找其中最高分的AVInputFormat// demuxer_list是在编译期自动生成的一个listwhile ((fmt1 = av_demuxer_iterate(&i))) {if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;score = 0;// 从每个格式都有自己的标准,因此读取一定的字节,按照格式要求看看是不是和当前输入的文件匹配// 但是因为默认的buf_size为0,所以这里也就没有办法判断if (fmt1->read_probe) {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);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;}}//如果没有read_probe,就通过后缀名来判断} else if (fmt1->extensions) {if (av_match_ext(lpd.filename, fmt1->extensions))score = AVPROBE_SCORE_EXTENSION;}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;}}if (score > score_max) {score_max = score;fmt       = (AVInputFormat*)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;
}

以上是av_probe_input_format3()的实现,基本上是通过这个函数来探测实际的封装结结构,通常用于探测是下面这三个函数
av_probe_input_buffer2 :主要用于从AVIOContext中读取数据,然后写入buffer。(后面有源码详解)
av_probe_input_format2:为av_probe_input_format3的封装
av_probe_input_format3: 最终探测的实现,会把buffer中的数据交给每个AVInputFormat,然后调用对应的read_probe函数来打分。

io_open_default源码

探测完是哪个AVInputFormat之后,init_input函数就会调用AVFormatContext的io_open函数,这个函数可以初始化AVIOContext。
io_open是在调用avformat_alloc_context获取AVFormatContext时候被初始化为io_open_default函数,所以下面来看看这个函数的具体实现。

static int io_open_default(AVFormatContext *s, AVIOContext **pb,const char *url, int flags, AVDictionary **options)
{int loglevel;if (!strcmp(url, s->url) ||s->iformat && !strcmp(s->iformat->name, "image2") ||s->oformat && !strcmp(s->oformat->name, "image2")) {loglevel = AV_LOG_DEBUG;} elseloglevel = AV_LOG_INFO;av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");#if FF_API_OLD_OPEN_CALLBACKS
FF_DISABLE_DEPRECATION_WARNINGSif (s->open_cb)return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
FF_ENABLE_DEPRECATION_WARNINGS
#endif//最终是调用到ffio_open_whitelist,这个方法return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}

上面就简单的进行了一下设置,关键的实现还是在ffio_open_whitelist()这个实现中。

ffio_open_whitelist源码

int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char *blacklist)
{URLContext *h;int err;*s = NULL;// 根据filename初始化 URLContexterr = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);if (err < 0)return err;// 根据URLContext 设置AVIOContexterr = ffio_fdopen(s, h);if (err < 0) {ffurl_close(h);return err;}return 0;
}

ffio_open_whitelist主要就分为两步
第一步是调用ffurl_open_whitelist函数,根据filename找到 URLProtocol,然后创建对应的URLContext,并初步connect。
第二步是调用ffio_fdopen函数,根据上一步创建的URLContext 创建出AVIOContext。
主要还是在第一步,下面详细看一下这两个函数的具体实现。

ffurl_open_whitelist源码

int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char* blacklist,URLContext *parent)
{AVDictionary *tmp_opts = NULL;AVDictionaryEntry *e;// 这里根据filename找到URLProtocol并生成URLContext。int ret = ffurl_alloc(puc, filename, flags, int_cb);if (ret < 0)return ret;if (parent)av_opt_copy(*puc, parent);if (options &&(ret = av_opt_set_dict(*puc, options)) < 0)goto fail;if (options && (*puc)->prot->priv_data_class &&(ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)goto fail;if (!options)options = &tmp_opts;av_assert0(!whitelist ||!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||!strcmp(whitelist, e->value));av_assert0(!blacklist ||!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||!strcmp(blacklist, e->value));if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)goto fail;if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)goto fail;if ((ret = av_opt_set_dict(*puc, options)) < 0)goto fail;//调用open方法,初步实现连接,不同协议,使用的方式不一样ret = ffurl_connect(*puc, options);if (!ret)return 0;
fail:ffurl_closep(puc);return ret;
}

ffurl_open_whitelist函数内也是就只有两步,第一步调用ffurl_alloc方法,初始化URLContext。第二步初始化完URLContext之后,调用ffurl_connect看看是否有对应的open方法,比如如果urlcontext是file类型,就用fopen等方法,如果是rtmp等类型,也用对应的方法,具体的实现都在URLProtocol中。
下面详细看一下这两步:

ffurl_alloc源码

int ffurl_alloc(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb)
{const URLProtocol *p = NULL;// 首先根据filename找到对应的URLProtocolp = url_find_protocol(filename);// 根据URLProtocol初始化URLContextif (p)return url_alloc_for_protocol(puc, p, filename, flags, int_cb);*puc = NULL;return AVERROR_PROTOCOL_NOT_FOUND;
}

下面看看是怎么找的URLProtocol

static const struct URLProtocol *url_find_protocol(const char *filename)
{const URLProtocol **protocols;//proto_strshi 具体的协议前缀,本地文件的话,用filechar proto_str[128], proto_nested[128], *ptr;//首先根据strspn()函数查找字符串中第一个“非字母或数字”的字符的位置//一般来说都是 :  ,比如rtmp协议 rtmp://....  ,https协议  https://.... size_t proto_len = strspn(filename, URL_SCHEME_CHARS);int i;//判断是不是本地file类型if (filename[proto_len] != ':' &&//判断filename的开头不是"subfile,", 且之后的字符串中没有':'(strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||is_dos_path(filename)) // 这个感觉即使判断是不是在windows上 一般windows是 C://...strcpy(proto_str, "file");else//判断是网络协议,非本地协议,就copy 非字母或数字之前的, 比如rtmp:// 就拷贝rtmpav_strlcpy(proto_str, filename,FFMIN(proto_len + 1, sizeof(proto_str)));av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));if ((ptr = strchr(proto_nested, '+')))*ptr = '\0';//从url_protocols数组中,获取所有URLProtocolprotocols = ffurl_get_protocols(NULL, NULL);if (!protocols)return NULL;//然后对这些URLProtocol分别进行判断,看看是不是这个协议for (i = 0; protocols[i]; i++) {const URLProtocol *up = protocols[i];if (!strcmp(proto_str, up->name)) {av_freep(&protocols);return up;}if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&!strcmp(proto_nested, up->name)) {av_freep(&protocols);return up;}}av_freep(&protocols);if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with ""openssl, gnutls or securetransport enabled.\n");return NULL;
}

从上面的代码分析中,可以看出清楚的匹配流程,每种协议都有自己的URLProtocol,这样就可以根据filename找到对应的URLProtocol了。

找到URLProtocol之后,就是根据URLProtocol来初始化URLContext了,调用的url_alloc_for_protocol函数。

url_alloc_for_protocol源码

static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,const char *filename, int flags,const AVIOInterruptCB *int_cb)
{URLContext *uc;int err;#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())return AVERROR(EIO);
#endif//用标识和相应的协议验证,如果要求只读,那么对应的协议必须实现url_read,其余同理if ((flags & AVIO_FLAG_READ) && !up->url_read) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for reading\n", up->name);return AVERROR(EIO);}if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for writing\n", up->name);return AVERROR(EIO);}uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);if (!uc) {err = AVERROR(ENOMEM);goto fail;}//设置参数uc->av_class = &ffurl_context_class;uc->filename = (char *)&uc[1];strcpy(uc->filename, filename);uc->prot            = up;uc->flags           = flags;uc->is_streamed     = 0; /* default = not streamed */uc->max_packet_size = 0; /* default: stream file */if (up->priv_data_size) {uc->priv_data = av_mallocz(up->priv_data_size);if (!uc->priv_data) {err = AVERROR(ENOMEM);goto fail;}if (up->priv_data_class) {char *start;*(const AVClass **)uc->priv_data = up->priv_data_class;av_opt_set_defaults(uc->priv_data);if (av_strstart(uc->filename, up->name, (const char**)&start) && *start == ',') {int ret= 0;char *p= start;char sep= *++p;char *key, *val;p++;if (strcmp(up->name, "subfile"))ret = AVERROR(EINVAL);while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){*val= *key= 0;if (strcmp(p, "start") && strcmp(p, "end")) {ret = AVERROR_OPTION_NOT_FOUND;} elseret= av_opt_set(uc->priv_data, p, key+1, 0);if (ret == AVERROR_OPTION_NOT_FOUND)av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);*val= *key= sep;p= val+1;}if(ret<0 || p!=key){av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);av_freep(&uc->priv_data);av_freep(&uc);err = AVERROR(EINVAL);goto fail;}memmove(start, key+1, strlen(key));}}}if (int_cb)uc->interrupt_callback = *int_cb;*puc = uc;return 0;
fail:*puc = NULL;if (uc)av_freep(&uc->priv_data);av_freep(&uc);
#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK)ff_network_close();
#endifreturn err;
}

初始化完成后,就可以得到URLContext变量了。
总体来说URLProtocol是实际执行的结构体, URLContext是管理URLProtocol的结构体,包括priv_data等,AVIOContext是FFmpeg对外暴露的接口,AVIOContext中保存着当前已经读取或者写入的状态, AVIOContext和URLContext与URLProtocol的关系有点像AVFormatContext与AVInputFormat之间的关系。

AVIOContext ➡️ URLContext ➡️ URLProtocol
实际区别在于URLProtocol

AVFormatContext ➡️ AVInputFormat
实际区别在于AVInputFormat

接下来就需要回到ffurl_open_whitelist函数中,继续调用ffurl_connect函数。

ffurl_connect源码

int ffurl_connect(URLContext *uc, AVDictionary **options)
{int err;AVDictionary *tmp_opts = NULL;AVDictionaryEntry *e;if (!options)options = &tmp_opts;// Check that URLContext was initialized correctly and lists are matching if setav_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||(uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||(uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));//这里先对黑白名单进行判断,看看我们找到的协议是否在里面if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);return AVERROR(EINVAL);}if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);return AVERROR(EINVAL);}if (!uc->protocol_whitelist && uc->prot->default_whitelist) {av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);if (!uc->protocol_whitelist) {return AVERROR(ENOMEM);}} else if (!uc->protocol_whitelist)av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelistif ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)return err;if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)return err;//这里是关键,调用对应协议的url_open方法,现在测试的是本地文件,所以用到的是ff_file_protocol的uc->prot->url_open方法。err =uc->prot->url_open2 ? uc->prot->url_open2(uc,uc->filename,uc->flags,options) :uc->prot->url_open(uc, uc->filename, uc->flags);av_dict_set(options, "protocol_whitelist", NULL, 0);av_dict_set(options, "protocol_blacklist", NULL, 0);if (err)return err;uc->is_connected = 1;/* We must be careful here as ffurl_seek() could be slow,* for example for http */if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)uc->is_streamed = 1;return 0;
}

看一下ff_file_protocol 中url_open()的一些实现

const URLProtocol ff_file_protocol = {.name                = "file",.url_open            = file_open, //这里是open方法.url_read            = file_read,.url_write           = file_write,.url_seek            = file_seek,.url_close           = file_close,.url_get_file_handle = file_get_handle,.url_check           = file_check,.url_delete          = file_delete,.url_move            = file_move,.priv_data_size      = sizeof(FileContext),.priv_data_class     = &file_class,.url_open_dir        = file_open_dir,.url_read_dir        = file_read_dir,.url_close_dir       = file_close_dir,.default_whitelist   = "file,crypto,data"
};static int file_open(URLContext *h, const char *filename, int flags)
{FileContext *c = h->priv_data;int access;int fd;struct stat st;av_strstart(filename, "file:", &filename);//根据flags设置一下打开文件的权限类型if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {access = O_CREAT | O_RDWR;if (c->trunc)access |= O_TRUNC;} else if (flags & AVIO_FLAG_WRITE) {access = O_CREAT | O_WRONLY;if (c->trunc)access |= O_TRUNC;} else {access = O_RDONLY;}
#ifdef O_BINARYaccess |= O_BINARY;
#endif//打开文件fd = avpriv_open(filename, access, 0666);if (fd == -1)return AVERROR(errno);c->fd = fd;h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode);/* Buffer writes more than the default 32k to improve throughput especially* with networked file systems */if (!h->is_streamed && flags & AVIO_FLAG_WRITE)h->min_packet_size = h->max_packet_size = 262144;if (c->seekable >= 0)h->is_streamed = !c->seekable;return 0;
}

调用完成之后,我们就完成了通过ffurl_open_whitelist()函数,找到并打开URLContext的任务,接下来就回到ffio_open_whitelist()中,继续接下来的任务:调用ffio_fdopen()函数,通过URLContext初始化AVIOContext。

ffio_fdopen源码

int ffio_fdopen(AVIOContext **s, URLContext *h)
{uint8_t *buffer = NULL;int buffer_size, max_packet_size;max_packet_size = h->max_packet_size;if (max_packet_size) {buffer_size = max_packet_size; /* no need to bufferize more than one packet */} else {buffer_size = IO_BUFFER_SIZE;}if (!(h->flags & AVIO_FLAG_WRITE) && h->is_streamed) {if (buffer_size > INT_MAX/2)return AVERROR(EINVAL);buffer_size *= 2;}buffer = av_malloc(buffer_size);if (!buffer)return AVERROR(ENOMEM);//创建AVIOContext,其中ffurl_read,ffurl_write,ffurl_seek都是套了一层壳,增加容错,本质还是调用url_read, url_write,url_seek*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,(int (*)(void *, uint8_t *, int))  ffurl_read,(int (*)(void *, uint8_t *, int))  ffurl_write,(int64_t (*)(void *, int64_t, int))ffurl_seek);if (!*s)goto fail;(*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);if (!(*s)->protocol_whitelist && h->protocol_whitelist) {avio_closep(s);goto fail;}(*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);if (!(*s)->protocol_blacklist && h->protocol_blacklist) {avio_closep(s);goto fail;}(*s)->direct = h->flags & AVIO_FLAG_DIRECT;(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;(*s)->max_packet_size = max_packet_size;(*s)->min_packet_size = h->min_packet_size;if(h->prot) {(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;(*s)->read_seek  =(int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;if (h->prot->url_read_seek)(*s)->seekable |= AVIO_SEEKABLE_TIME;}(*s)->short_seek_get = (int (*)(void *))ffurl_get_short_seek;(*s)->av_class = &ff_avio_class;return 0;
fail:av_freep(&buffer);return AVERROR(ENOMEM);
}

ffio_fdopen主要就是把之前创建的URLContext与刚刚malloc的AVIOContext结合,初始化AVIOContext中的成员。
调用ffio_fdopen 之后, 创建和打开AVIOContext的流程就完全完成了,下面不忘初心,回到init_input函数中,继续调用av_probe_input_buffer2函数检测数据类型,并生成对应的AVInputFormat。

av_probe_input_buffer2源码

int av_probe_input_buffer2(AVIOContext *pb, ff_const59 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;if (!max_probe_size)max_probe_size = PROBE_BUF_MAX;else if (max_probe_size < PROBE_BUF_MIN) {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)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;semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;if (semi) {*semi = '\0';}}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))) {score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;/* Read probe data. *///读头部数据if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)goto fail;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);/* Guess file format. */// 丢丢转转又回到这个函数,这个函数上面分析过,只不过之前调用的时候,并没有读数据// 所以没发通过数据的头部信息来判断是否是对应格式,而这此读取了部分头部数据,可以凭此判断了。*fmt = av_probe_input_format2(&pd, 1, &score);if (*fmt) {/* This can only be true in the last iteration. */if (score <= AVPROBE_SCORE_RETRY) {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)ret = AVERROR_INVALIDDATA;fail:/* 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;
}

到此为止,算是简单的把init_input()函数过了一遍,得到了一个AVInputFormat,和一个AVIOContext。

下面要调用s->iformat->read_header创建AVStream,测试调用的是本地Mp4文件,我们也解析这种格式。 read_header会解析mp4的所有box,从中提取信息,并凭借这些创建AVStream。
mp4对应的AVInputFormat在 mov.c文件中:
下面的具体分析需要先了解MP4的结构,在这里就不详细说明

AVInputFormat ff_mov_demuxer = {.name           = "mov,mp4,m4a,3gp,3g2,mj2",.long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),.priv_class     = &mov_class,.priv_data_size = sizeof(MOVContext),.extensions     = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v",.read_probe     = mov_probe,.read_header    = mov_read_header,.read_packet    = mov_read_packet,.read_close     = mov_read_close,.read_seek      = mov_read_seek,.flags          = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS,
};static int mov_read_header(AVFormatContext *s)
{MOVContext *mov = s->priv_data;AVIOContext *pb = s->pb;int j, err;//MOVAtom atom = { AV_RL32("root") };int i;if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",mov->decryption_key_len, AES_CTR_KEY_SIZE);return AVERROR(EINVAL);}mov->fc = s;mov->trak_index = -1;/* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */if (pb->seekable & AVIO_SEEKABLE_NORMAL)atom.size = avio_size(pb);elseatom.size = INT64_MAX;/* check MOV header */do {if (mov->moov_retry)avio_seek(pb, 0, SEEK_SET);if ((err = mov_read_default(mov, pb, atom)) < 0) {av_log(s, AV_LOG_ERROR, "error reading header\n");goto fail;}} while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);if (!mov->found_moov) {av_log(s, AV_LOG_ERROR, "moov atom not found\n");err = AVERROR_INVALIDDATA;goto fail;}av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb));if (pb->seekable & AVIO_SEEKABLE_NORMAL) {if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters)mov_read_chapters(s);for (i = 0; i < s->nb_streams; i++)if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) {mov_read_timecode_track(s, s->streams[i]);} else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) {mov_read_rtmd_track(s, s->streams[i]);}}/* copy timecode metadata from tmcd tracks to the related video streams */for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];MOVStreamContext *sc = st->priv_data;if (sc->timecode_track > 0) {AVDictionaryEntry *tcr;int tmcd_st_id = -1;for (j = 0; j < s->nb_streams; j++)if (s->streams[j]->id == sc->timecode_track)tmcd_st_id = j;if (tmcd_st_id < 0 || tmcd_st_id == i)continue;tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0);if (tcr)av_dict_set(&st->metadata, "timecode", tcr->value, 0);}}export_orphan_timecode(s);for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];MOVStreamContext *sc = st->priv_data;fix_timescale(mov, sc);if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&st->codecpar->codec_id   == AV_CODEC_ID_AAC) {st->internal->skip_samples = sc->start_pad;}if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0)av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX);if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {st->codecpar->width  = sc->width;st->codecpar->height = sc->height;}if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0)goto fail;}}if (mov->handbrake_version &&mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2st->codecpar->codec_id == AV_CODEC_ID_MP3) {av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");st->need_parsing = AVSTREAM_PARSE_FULL;}}if (mov->trex_data) {for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];MOVStreamContext *sc = st->priv_data;if (st->duration > 0) {if (sc->data_size > INT64_MAX / sc->time_scale / 8) {av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",sc->data_size, sc->time_scale);err = AVERROR_INVALIDDATA;goto fail;}st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale / st->duration;}}}if (mov->use_mfra_for > 0) {for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];MOVStreamContext *sc = st->priv_data;if (sc->duration_for_fps > 0) {if (sc->data_size > INT64_MAX / sc->time_scale / 8) {av_log(s, AV_LOG_ERROR, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",sc->data_size, sc->time_scale);err = AVERROR_INVALIDDATA;goto fail;}st->codecpar->bit_rate = sc->data_size * 8 * sc->time_scale /sc->duration_for_fps;}}}for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) {if (mov->bitrates[i]) {s->streams[i]->codecpar->bit_rate = mov->bitrates[i];}}ff_rfps_calculate(s);for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];MOVStreamContext *sc = st->priv_data;switch (st->codecpar->codec_type) {case AVMEDIA_TYPE_AUDIO:err = ff_replaygain_export(st, s->metadata);if (err < 0)goto fail;break;case AVMEDIA_TYPE_VIDEO:if (sc->display_matrix) {err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix,sizeof(int32_t) * 9);if (err < 0)goto fail;sc->display_matrix = NULL;}if (sc->stereo3d) {err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,(uint8_t *)sc->stereo3d,sizeof(*sc->stereo3d));if (err < 0)goto fail;sc->stereo3d = NULL;}if (sc->spherical) {err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,(uint8_t *)sc->spherical,sc->spherical_size);if (err < 0)goto fail;sc->spherical = NULL;}if (sc->mastering) {err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,(uint8_t *)sc->mastering,sizeof(*sc->mastering));if (err < 0)goto fail;sc->mastering = NULL;}if (sc->coll) {err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,(uint8_t *)sc->coll,sc->coll_size);if (err < 0)goto fail;sc->coll = NULL;}break;}}ff_configure_buffers_for_index(s, AV_TIME_BASE);for (i = 0; i < mov->frag_index.nb_items; i++)if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset)mov->frag_index.item[i].headers_read = 1;return 0;
fail:mov_read_close(s);return err;
}

完成这个后,基本就完成了avformat_open_input()流程。

从头用脚分析FFmpeg源码 --- avformat_open_input相关推荐

  1. 从头用脚分析FFmpeg源码 - av_interleaved_write_frame | av_write_frame

    本文所使用的是FFmpeg n4.4的源码,所有分析均来自博主瞎猜,如果有误,欢迎批评指正. av_write_frame 作用 /*** Write a packet to an output me ...

  2. ffmpeg源码分析与应用示例(一)——H.264解码与QP提取

    本文包含以下内容 1.H.264解码流程详述与对应ffmpeg源码解析 2.针对一个应用实例介绍通过修改ffmpeg源码解决问题的方案 具有较强的综合性. 先介绍一下在第二部分中将要解决的实际问题:自 ...

  3. FFMPEG 源码分析

    FFMPEG基本概念: ffmpeg是一个开源的编解码框架,它提供了一个音视频录制,解码和编码库.FFMPEG是在linux下开发的,但也有windows下的编译版本. ffmpeg项目由以下几部分组 ...

  4. FFMPEG源码分析(二)

    ffmpeg源码分析之数据流 本文主要介绍ffmpeg的数据流,在ffmpeg中主要分有三个主要用途用于媒体流的解码播放,媒体流的转换(解码之后再编码)和媒体流录制. 媒体流的解码播放 在ffmpeg ...

  5. FFmpeg源码分析-直播延迟-内存泄漏

    FFmpeg源码分析-直播延迟-内存泄漏|FFmpeg源码分析方法|ffmpeg播放为什么容易产生延迟|解复用.解码内存泄漏分析 专注后台服务器开发,包括C/C++,Linux,Nginx,ZeroM ...

  6. ffmpeg源码分析-parse_optgroup

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  7. ffmpeg源码分析-ffmpeg_parse_options

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  8. ffmpeg源码分析-transcode_step

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  9. FFMPEG源码分析(一)

    FFMPEG源码分析(一) ffmpeg之前公司项目中就使用过,但是多停留于应用层面,实现某个功能时,需要哪些结构体以及调用哪些函数.最近想系统的学习一下ffmpeg,于是开始看雷霄骅https:// ...

最新文章

  1. java菱形乱码 编码_JAVA:编码与乱码问题
  2. Linux下的softlink和hardlink(转)
  3. BootStrap字体图标不显示、下拉菜单不显示
  4. 《ActionScript 3.0基础教程》——1.4 对象参数
  5. 深度学习导论(3)PyTorch基础
  6. 前端学习(361):svn操作后续
  7. 阿里云服务器被[kthreaddi]挖矿病毒攻击
  8. gitlab git clone 卡住_gitlab从入门到绝望
  9. android横向滑动选项卡,android – 如何使用可滑动选项卡实现PageTransformer
  10. html点击控制盒子左右移动,JS实现鼠标拖拽盒子移动及右键点击盒子消失效果示例...
  11. 多个漏洞可被用于破坏劫持施耐德 PowerLogic 设备
  12. python爬取豆瓣代码_python爬取豆瓣视频信息代码
  13. 自定义系统右键菜单工具-使用说明
  14. 正交性的好处和实现方法
  15. 模拟科目二侧方位停车训练
  16. 哪家的云游戏服务器好?如何选择云游戏服务器?
  17. 【《Real-Time Rendering 3rd》 提炼总结】(五) 第六章 · 纹理贴图及相关技术 The Texturing
  18. sleuth zipkin reporter-sender 分析
  19. python set(集合) 与 and 、 | 与 or之间的区别
  20. 小写金额转大写c++

热门文章

  1. Vue-----Vue本地应用篇(2)
  2. android ratingbar不可点击,Android评分控件RatingBar使用实例解析
  3. 系列学习 docker 之第 5 篇 —— Docker 常用命令
  4. 插槽的使用教程(普通插槽、具名插槽、域名插槽)
  5. EasyPoi的使用(1)
  6. rad2deg() 函数
  7. 黑马传智python基础班课程_fb238 17年_传智黑马Python
  8. BLE 链路层(LL)的数据结构
  9. 宣传单彩页_宣传单彩页设计
  10. 【SEO经验分享】网站分析对SEO优化有什么用