原文链接:https://blog.csdn.net/ice_ly000/article/details/93161260

具体上,从源码来说,该函数有这么几项功能:
1. 在用户没有提供AVFormatContext的情况下,创建一个格式上下文对象AVFormatContext;
2. 在用户没有提供IO层上下文对象AVIOContext的情况下,打开文件并创建IO层上下文对象AVIOContext;
3. 在用户没有指定输入文件格式AVInputFormat的情况下,探测文件格式,得到输入文件格式信息AVInputFormat;
4. 读取文件头,在文件头描述信息足够的情况下创建流AVStream以及获取并设置编解码参数相关信息;
5. 填充AVFormatContext其他字段信息。
总之,该函数的作用就是打开文件,尽可能的收集各方面的信息并填充AVFormatContext结构体,基本上是做了除过解码之外的所有工作。

avformat_open_input()。该函数用于打开多媒体数据并且获得一些相关的信息。它的声明位于libavformat\avformat.h,如下所示。
avformat_open_input()

* 打开一个输入流并读取头部。未打开编解码器。* 必须使用 avformat_close_input() 关闭流。* @param ps 指向用户提供的 AVFormatContext 的指针(由 avformat_alloc_context 分配)。* 可能是一个指向 NULL 的指针,在这种情况下,一个 AVFormatContext 由 this 分配* 函数并写入 ps。* 请注意,用户提供的 AVFormatContext 将在失败时被释放。* @param url 要打开的流的 URL。* @param fmt 如果非空,此参数强制特定的输入格式。* 否则格式是自动检测的。* @param options 一个充满 AVFormatContext 和 demuxer-private 选项的字典。* 返回时,此参数将被销毁并替换为包含* 未找到的选项。可能为 NULL。* @return 成功时返回 0,失败时返回负的 AVERROR。* @note 如果要使用自定义 IO,请预先分配格式上下文并设置其 pb 字段。int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

avformat_open_input()源码:

参数说明:
AVFormatContext **ps, 格式化的上下文。要注意,如果传入的是一个AVFormatContext*的指针,则该空间须自己手动清理,若传入的指针为空,则FFmpeg会内部自己创建。
const char *filename, 传入的文件地址。支持http,RTSP,以及普通的本地文件。地址最终会存入到AVFormatContext结构体当中。
AVInputFormat *fmt, 指定输入的封装格式。一般传NULL,由FFmpeg自行探测。
AVDictionary **options, 其它参数设置。它是一个字典,用于参数传递,不传则写NULLint 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;// 判断传入的AVFormatContext对象是否为空// 为空则创建该对象if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM);// 判断AVFormatContext对象是否被合法的创建// 由于AVFormatContext对象只能使用avformat_alloc_context()来创建// 若外部传入了一个由av_malloc()创建的该对象,此处将检测这种情况的发生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);// 如果外部指定了I/O层,那么设置标志位AVFMT_FLAG_CUSTOM_IOif (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;// 应用选项信息到AVFormatContextif ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;// 拷贝filename到AVFormatContext.url字段if (!(s->url = av_strdup(filename ? filename : ""))) {ret = AVERROR(ENOMEM);goto fail;}// 拷贝filename到AVFormatContext.filename字段// 注意该字段已经被声明为deprecated
#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGSav_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif// 探测文件格式,将探测得分存储于AVFormatContext.probe_score字段 if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;s->probe_score = ret;// 拷贝协议白名单到AVFormatContext.protocol_whitelistif (!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;}}// 拷贝协议黑名单到AVFormatContext.protocol_blacklistif (!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内部缓冲区跳过初始字节  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;}}// 初始化文件的持续时间和起始时间为AV_NOPTS_VALUEs->duration = s->start_time = AV_NOPTS_VALUE;// 设置AVFormatContext的私有数据,套路与设置AVIOContext的私有数据一样// 注意该私有数据也是一个上下文对象,后续以FLV格式来分析该私有数据/* Allocate private data. */if (s->iformat->priv_data_size > 0) {// 分配AVFormatContext私有数据空间if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {ret = AVERROR(ENOMEM);goto fail;}if (s->iformat->priv_class) {// 设置AVFormatContext私有数据的第一个成员字段AVClass**(const AVClass **) s->priv_data = s->iformat->priv_class;// 初始化AVFormatContext私有数据的其他成员    av_opt_set_defaults(s->priv_data);// 应用用户提供的选项信息到AVFormatContext私有数据if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)goto fail;}}// 如果AVIOContext存在,读取ID3信息/* 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);// 读取文件头,可能获取到metadata信息,流信息if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)if ((ret = s->iformat->read_header(s)) < 0)goto fail;// 处理metadata信息与id3v2信息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;}// 处理id3v2额外信息if (id3v2_extra_meta) {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;if ((ret = ff_id3v2_parse_priv(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;// 更新AVFormatContext.AVFormatInternal的成员 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;
}

该函数内容比较长,按如下分段理解会很容易

  1. 入参检查:检查所有的入参,并做相应的处理。
    1.1) 处理AVFormatContext **ps:确保后续操作有一个非空的并且是合法创建的AVFormatContext对象可以使用。具体做法见源码注释
    1.2)处理AVInputFormat *fmt:判断是否强制指定了输入文件格式,若指定了,那么将该文件格式挂载于AVFormatContext.iformat成员上,后续将不再自动探测音视频文件格式。
    1.3)处理AVDictionary **options:先将选项信息拷贝到内部临时变量,然后调用av_opt_set_dict()方法来应用选项信息到AVFormatContext。av_opt_set_dict() 方法的具体分析见 FFMPEG源码分析之 av_opt_set_dict()
    1.4)处理AVFormatContext.pb:如果用户希望用自己代码处理IO层,也即AVIOContext在外部已经被创建并赋值给AVFormatContext.pb,那么确保AVFormatContext.flags的AVFMT_FLAG_CUSTOM_IO置位。
    1.5)处理const char *filename:拷贝filename到AVFormatContext.url和AVFormatContext.filename字段,后者已经被标注为deprecated,为了兼容以前的版本,此处也会赋值。
  2. 调用init_input()打开文件,并进行文件格式探测:
    2.1)init_input()进行文件格式探测,创建合适的AVIOContext上下文对象,并找到最合适的AVInputFormat。
    2.2)将探测的文件格式得分赋值给AVFormatContext.probe_score。
  3. 获取信息并填充AVFormatContext的其他字段
    3.1)AVFormatContext.protocol_whitelist&& .protocol_blacklist:复制AVIOContext持有的协议黑白名单到对应字段
    3.2)AVFormatContext.format_whitelist:判断推断的输入文件格式是否在格式白名单中。以mov格式为例,其AVInputFormat对象为以下源码所示,那么名称为一个列表,可以看到 “mov, mp4, m4a, 3gp, 3g2, mj2” 这些文件格式共享一个文件格式对象,因为这几个格式都是基于ISO base media file format衍生出来的格式,基本上遵循同样的规范。文件格式白名单format_whitelist要么不存在,要么也是以","分隔的文件格式,只要文件格式名称中的一个格式与白名单中的一个匹配则认为文件格式在白名单中。
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",.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,
};

3.3)AVFormatContext. skip_initial_bytes:该参数释义为“set number of bytes to skip before reading header and frames”,通过avio_skip()函数跳过skip_initial_bytes,该值在创建初始化AVFormatContext时被设置为0。avio_skip()向前跳过给定的字节数
3.4)检查AVInputFormat.flags标志AVFMT_NEEDNUMBER与 输入文件名中是否包含数字序列所匹配。如何理解这点呢? 首先要知道,ffmpeg支持视频文件与图片文件互转,比如支持一系列的图片组合成一个视频,那么对应的ffmpeg命令类似于"ffmpeg -i image-%3d.jpeg out.mp4",那么输入文件名为"image-%3d.jpeg"。av_filename_number_test()方法可以检测文件名中是否存在“%3d”这个字符串。
3.5)AVFormatContext.duration && .start_time:设置媒体持续时长以及起始时间为AV_NOPTS_VALUE,该值为(int64_t)UINT64_C(0x8000000000000000),表示没有提供时间信息。
3.6)AVFormatContext.priv_data:AVFormatContext的私有数据,类型为void*,根据输入文件格式不同,该字段为不同类型的,以mov格式的文件为例,见上述ff_mov_demuxer源码:该私有字段将是MOVContext的结构,源码中先分配MOVContext空间->将AVClass对象mov_class赋值给MOVContext的第一个成员->调用av_opt_set_defaults()来给MOVContext设置默认值->调用av_opt_set_dict()将用户传入的信息应用到MOVContext成员。
3.7)AVFormatContext.AVFormatInternal.id3v2_meta:读取文件开头的ID3V2信息,保存到该字段。额外的id3v2信息存在id3v2_extra_meta的临时变量中
3.8)通过AVInputFormat.read_header()来获取metadata信息,创建流等等:
以flv格式和mp4格式为例来说明,flv_read_header() 和 mov_read_header()方法在后文中详述,二者基本代表了两种类型的数据:
flv格式是适合流传输的格式,除了最初始有个简单的header外,就是一个个TAG的结构体,每个TAG都可以自描述,包含了自描述信息和音视频数据,这种格式的好处是不论从流的哪儿开始播放都很方便。相应的flv_read_header()函数比较简单,因为flv的头比较单,包含信息量很少,因此并没有获取到metadata信息,也就是s->metadata不会被赋值,并且,也没有找到足够多的关于流的信息,只简单的知道是否存在视频流,是否存在音频流,不会调用avformat_new_stream()来创建AVStream对象,所以s->nb_streams,s->streams都不会被赋值。
mp4格式是一种交换格式,所有的音视频信息集中在一起,并建立索引信息来查找正真的音视频数据,而音视频数据在一起。相应的mov_read_header()函数就特别复杂,由于mp4是由一个个box组成,mov_read_header()会读取所有的box以获取mp4文件的完整信息,最重要的就是moov这个box,因此,s->metadata会被赋值,并且流的个数,流本身的信息也都非常清楚,因此avformat_new_stream() 会被调用来创建AVStream对象,s->nb_streams,s->streams都会被正确的赋值。
3.9)AVFormatContext.metadata:在metadata存在的条件下,其更可靠,因此3.7) 中的id3v2_meta可以丢弃,若不存在那么AVFormatContext.metadata将取值AVFormatContext.AVFormatInternal.id3v2_meta。
3.10)mp3,aac,tta的音频格式,id3v2_extra_meta数据中会存储封面(apic),章节(chapters),私有帧(priv)信息。
3.11)将封面图片放到队列中
3.12)更新AVFormatContext.AVFormatInternal的成员
3.13)通过调用update_stream_avctx(s)更新流的上下文信息

av_dict_copy()

 将条目从一个 AVDictionary 结构复制到另一个。
@param dst 指向 AVDictionary 结构的指针。 如果 *dst 为 NULL,
这个函数会为你分配一个结构体并将其放入 *dst
@param src 指向源 AVDictionary 结构的指针
@param 标记在 *dst 中设置条目时使用的标志
@note 元数据使用 AV_DICT_IGNORE_SUFFIX 标志读取
@return 成功时返回 0,失败时返回负的 AVERROR 代码。 如果分配了 dst
通过这个函数,调用者应该释放相关的内存。int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);

av_dict_copy() 源码:

int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags)
{AVDictionaryEntry *t = NULL;while ((t = av_dict_get(src, "", t, AV_DICT_IGNORE_SUFFIX))) {int ret = av_dict_set(dst, t->key, t->value, flags);if (ret < 0)return ret;}return 0;
}av_dict_get()不停迭代获取条目,注意flag为AV_DICT_IGNORE_SUFFIX,这个标志使得进行key匹配的时候,只要传入的key字符串与条目的key字符串前面的字符能匹配上,则认为该条目是要查找的条目。此处传入的key字符串为"",因此可以匹配所有的条目。
av_dict_set() 将根据取出的每个条目,以及传入的flags值来设置目标AVDictionary。

av_strdup()
所属库:libavutil(lavu),lavu是ffmpeg中的功能库,本函数属于内存管理功能
声明:拷贝一份字符串。注意,该函数使用了av_malloc_attrib宏进行了属性修饰

/*** Duplicate a string.** //入参s指向需要拷贝的字符串* @param s String to be duplicated ** //返回一个指向新分配的内存,该内存拷贝了一份字符串,如果无法分配出空间,则返回NULL * @return Pointer to a newly-allocated string containing a  *         copy of `s` or `NULL` if the string cannot be allocated* @see av_strndup()*/
char *av_strdup(const char *s) av_malloc_attrib;

av_strdup()

char *av_strdup(const char *s)
{char *ptr = NULL;if (s) {// 求取存储字符串的长度,注意c串后的需要"\0",因此需要长度+1size_t len = strlen(s) + 1; // av_realloc()分配空间,为什么不是av_malloc()?ptr = av_realloc(NULL, len);// 如果空间分配成功,则memcpy进行内存拷贝if (ptr)memcpy(ptr, s, len);}return ptr;
}

1,问题再于给ptr分配空间的时候为什么不使用av_malloc(),而使用av_realloc()?这里涉及的c库的内存分配函数malloc()和realloc()函数的区别。malloc()是重新分配一块地址,而realloc()是在入参ptr指向的地址空间处进行内存的扩大或者缩小。对于本函数的功能来说,使用av_realloc()传入ptr为NULL,因此与av_malloc()个人感觉是没有太大区别。或许是对c库的内存管理函数还理解不太深刻的缘故。因此,在后续的ffmpeg学习中会专门开个内存管理的专门章节,进一步详细的研究ffmpeg中的内存管理以及对应的底层c库,以及更底层的系统调用sbrk(),brk()等等。

av_strndup() 声明:
所属库:libavutil(lavu),lavu是ffmpeg中的功能库,本函数属于内存管理功能
声明:拷贝字串。
重点:入参len指代了结果串的长度,那么需要考虑len与源串长度对比情况,大于,等于,小于的各种情况下都如何处理。

/*** Duplicate a substring of a string.** @param s   String to be duplicated* @param len Maximum length of the resulting string (not counting the*            terminating byte)* @return Pointer to a newly-allocated string containing a*         substring of `s` or `NULL` if the string cannot be allocated*/
char *av_strndup(const char *s, size_t len) av_malloc_attrib;

av_strndup()
源文件:libavutil/mem.c

char *av_strndup(const char *s, size_t len)
{char *ret = NULL, *end;if (!s)return NULL;end = memchr(s, 0, len);  // 计算'\0'的位置if (end)                  // 重新计算可拷贝字符串的长度len = end - s;ret = av_realloc(NULL, len + 1); // 分配内存if (!ret)return NULL;memcpy(ret, s, len);  // 拷贝内存ret[len] = 0;         // 最后一个字节赋值'\0'return ret;           // 返回新串地址
}

检查参数的有效性:如果源串s是空串,则目的串直接为NULL
匹配源串长度与len的大小:memchr()函数提供这样的功能:“C 库函数 void *memchr(const void *str, int c, size_t n) 在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置”,具体该函数的用法,这儿提供一个很不错的手册类查询网站,提供多种语言的api查询:https://www.runoob.com/。此处,查询0的位置,其实就是字符"\0",C类字符串的结尾。
1)如果返回值不为空,说明len的长度比源串的长度要长,需要重新计算可拷贝的字符串len的长度:len = end - s
2)如果返回值为空,说明len比源串的长度要短,拷贝len个字符是安全的,因此不需要重新计算len
分配内存,拷贝串,返回新串地址。

av_opt_set_dict()

* 在对象上设置给定字典中的所有选项。** @param obj 一个结构体,其第一个元素是指向 AVClass 的指针* @param options 要处理的选项。 这本词典将被释放和替换* 由一个新的包含在 obj 中找不到的所有选项。* 当然这本新词典需要调用者释放* 使用 av_dict_free()。** @return 0 成功,如果在 obj 中找到某些选项,则为负 AVERROR,* 但无法设置。** @see av_dict_copy()
int av_opt_set_dict(void *obj, struct AVDictionary **options);

av_opt_set_dict() 声明:
头文件:libavutil/opt.h
功能:设置所有的选项到一个对象中
入参obj:将被应用选项的对象,该对象必须第一个成员为AVClass*,也即obj应该是某个上下文结构体对象
入参options:需要被应用的选项字典。输入的选项字典将被释放,并且会被一个新的字典替代,这个新的字典中的选项由没有在obj中找到的所有选项组成。这个新的选项需要本函数的调用着在函数外部使用av_dict_free()来释放空间。
返回值:成功返回0,当某些选项在obj中被找到,但是又设置失败,那么该函数返回负的错误码。

av_opt_set_dict() 源码:

所属库:libavutil(lavu)
源文件:libavutil/opt.c
源码:该函数简单的调用了av_opt_set_dict2()

int av_opt_set_dict(void *obj, AVDictionary **options)
{return av_opt_set_dict2(obj, options, 0);
}

av_opt_set_dict() 声明:

所属库:libavutil(lavu)
头文件:libavutil/opt.h
声明:
功能:这个函数与av_opt_set_dict()作用相同。但是多了一个参数search_flags。
参数search_flags:该搜索标志是AV_OPT_SEARCH_的组合,目前ffmpeg中的搜索标志有两个。如声明中的定义。
1)AV_OPT_SEARCH_CHILDREN:这个参数用于处理嵌套的情形:传入的obj是一个AVOptions-enabled结构体(也即第一个成员是AVClass
对象,一般obj是一个XXXContext上下文对象),obj对象还有子对象也是一个AVOptions-enabled结构体,那么AV_OPT_SEARCH_CHILDREN搜索标志将会使得优先搜索子对象是否有合适的选项。
2)AV_OPT_SEARCH_FAKE_OBJ:该标志位表示传入的obj可能不是一个AVOptions-enabled结构体,而是一个相关的AVClass**,一般为了方便用来搜索选项而不需要真实的分配一个上下文对象。

/**
* 在对象上设置给定字典中的所有选项。** @param obj 一个结构体,其第一个元素是指向 AVClass 的指针* @param options 要处理的选项。 这本词典将被释放和替换* 由一个新的包含在 obj 中找不到的所有选项。* 当然这本新词典需要调用者释放* 使用 av_dict_free()。* @param search_flags AV_OPT_SEARCH_* 的组合。** @return 0 成功,如果在 obj 中找到某些选项,则为负 AVERROR,* 但无法设置。** @see av_dict_copy()*/
int av_opt_set_dict2(void *obj, struct AVDictionary **options, int search_flags);

av_opt_set_dict() 源码:

int av_opt_set_dict2(void *obj, AVDictionary **options, int search_flags)
{AVDictionaryEntry *t = NULL;AVDictionary    *tmp = NULL;int ret = 0;if (!options)return 0;while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) {ret = av_opt_set(obj, t->key, t->value, search_flags);if (ret == AVERROR_OPTION_NOT_FOUND)ret = av_dict_set(&tmp, t->key, t->value, 0);if (ret < 0) {av_log(obj, AV_LOG_ERROR, "Error setting option %s to value %s.\n", t->key, t->value);av_dict_free(&tmp);return ret;}ret = 0;}av_dict_free(options);*options = tmp;return ret;
}
  1. 使用av_dict_get()函数迭代选项字典中的option
  2. 使用av_opt_set()函数应用选项到obj中。
    2.1)若av_opt_set()函数在obj中未找到选项对应的参数,则返回AVERROR_OPTION_NOT_FOUND。此种情况,该选项信息将被设置到函数内部分配的临时选项字典中。
    2.2)若av_opt_set()函数在obj中找到选项但是设置失败则返回AVERROR(EINVAL),即-22,表示输入参数值非法。此种情况,函数将清理临时选项字典,并返回错误。
  3. 如果2中没有出现2.2)的情况,那么入参选项字典中的选项要么是已经成功应用到obj中,要么是存储到临时的选项字典中,此时,释放入参选项字典所占用的内存,并将临时的选项字典传递给入参,从而让函数调用者通过该入参获取到剩余的未应用到obj上的所有选项。

init_input() 源码:

所属库:libavformat(lavf)
源文件:libavformat/utils.c 静态函数,无头文件
源码:
功能:该函数有两个目标:一个是需要找到合适的I/O层对象,也即AVIOContext,其用于资源的访问;一个是探测文件格式,得到合适的AVInputFormat结构体对象。后续的代码分析过程中务必带着这个思维去看。

/* 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;// 用户指定了文件访问I/O层上下文对象AVIOContextif (s->pb) {// 修改下标志,表示IO层是用户提供的s->flags |= AVFMT_FLAG_CUSTOM_IO;// 如果用户没有指定文件格式if (!s->iformat)// 使用用户提供的I/O层接口来读取文件,并使用av_probe_input_buffer2// 进行文件格式探测,并返回探测得分return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);// 用户指定了文件访问IO,指定了输入文件格式,但是文件格式的标志位AVFMT_NOFILE又告知// 根本不需要访问文件,所以用户提供IO层的AVIOContext岂不是毫无作用,因此,打印下面这// 条警告日志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");// 直接返回0,也就是说用户指定了输入格式了// 不需要进行文件格式探测了。return 0;}// 如果指定了输入文件格式,该文件格式的标志AVFMT_NOFILE被设置,表示不需要IO层接口了,// 因为没有文件需要打开与读取,后续的操作肯定也不需要I/O层,此时,直接返回即可;// 若没有指定文件格式,那么通过av_probe_input_format2函数以及AVProbeData中提供的信息// 进行文件格式探测。if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;// 函数走到此处还未返回有两种情况:// 一种是用户指定了输入文件格式,但是AVFMT_NOFILE标志未被设置,即后续的操作是需要读取文件的// 那么必不可少需要I/O层的对象,因此,调用io_open()来打开文件,初始化I/O层的一切吧。// 另一种情况是用户没有指定输入文件格式,但是之前根据文件扩展名也猜不出输入文件格式,那么// 只能打开文件,读取数据来分析文件格式了,既然要读取文件,必然也需要I/O层的对象了,因此,也// 调用io_open()来打开文件,初始化I/O层的一切吧if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;// 函数走到这,若输入文件格式已确定,这种情形只可能是用户指定了// 输入文件格式,并且文件也是需要读取的,即s->iformat->flags的AVFMT_NOFILE标志位未被设置// I/O层也初始化,那么该做的都做了,返回吧~~if (s->iformat)return 0;// 函数走到此处还未返回只可能是一种情况:// 用户没有指定输入文件格式,根据文件扩展名也无法确定文件格式;// 此时需要av_probe_input_buffer2来读取文件内容进行文件格式确认return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}

1,初始化探测参数AVProbeData以及探测得分score:
1.1)AVProbeData:{ filename, NULL, 0 }; 只提供了资源URL

/*** This structure contains the data a format has to probe a file.*/
typedef struct AVProbeData {const char *filename;缓冲区必须具有填充零的额外分配字节的 AVPROBE PADDING SIZE。unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */除额外分配字节外的 buf 大小int buf_size;       /**< Size of buf except extra allocated bytes */mime type, when known. const char *mime_type; /**< mime_type, when known. */
} AVProbeData;

1.2)score初始值设置为AVPROBE_SCORE_RETRY,该值为25。
1.2.1) score变量是一个判决AVInputFormat的分数的门限值,如果最后得到的AVInputFormat的分数低于该门限值,
就认为没有找到合适的AVInputFormat。FFmpeg内部判断封装格式的原理实际上是对每种AVInputFormat给出一个分
数,满分是100分,越有可能正确的AVInputFormat给出的分数就越高。最后选择分数最高的AVInputFormat作为推测结
果。如果推测后得到的最佳AVInputFormat的分值低于25,就认为没有找到合适的AVInputFormat
1.2.2) 与文件探测得分相关的几个宏定义如下:根据不同条件推导出AVInputFormat得分是不一样的,如下所示

#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)#define AVPROBE_SCORE_EXTENSION  50 ///< score for file extension
#define AVPROBE_SCORE_MIME       75 ///< score for file mime type
#define AVPROBE_SCORE_MAX       100 ///< maximum score

2.用户提供了I/O层,即AVIOContext上下文对象情形下的处理:
出现用户提供I/O层的情形很少,可能有这么两个:一个是从内存中读取数据,而非解析某个协议的情形,此时需要初始化自定义的AVIOContext;一个是ffmpeg无法识别的新协议,这时,需要用户来提供字定义的协议解析相关接口来初始化AVIOContext。
2.1)用户指定了输入文件格式AVInputFormat,此时,输入文件格式有了,I/O层也有了,该有的都有了,直接返回;
2.2)用户未指定输入文件格式AVInputFormat,那么此时使用av_probe_input_buffer2()来读取文件内容,进行文件格式探测,非常重要的一点是,此时的I/O层是用户提供的,该函数内部使用用户提供的I/O层接口访问文件,读取数据。探测到格式后直接返回。
3. 用户没有提供I/O层的情形:
3.1)用户指定了输入文件格式AVInputFormat,并且AVInputFormat.flags的标志AVFMT_NOFILE被设置,表明根本没有文件需要访问,后续操作那也不需要也没有文件可以访问,既然这样,那就不需要IO层接口了,直接返回即可;
3.2)用户未指定输入文件格式AVInputFormat,那么优先通过av_probe_input_format2()函数进行文件格式探测,该函数通过AVProbeData结构中提供的三类信息进行格式识别:文件后缀(由资源的URL提供),文件的MIME类型(此处为空),缓冲数据。非常需要认识到的一点是该函数不会进行I/O层的操作,当av_probe_input_format2()函数根据以上三类信息识别到格式后,本函数就退出了,此时,并没有创建一个合适的I/O层对象AVIOContext。
重要!重要!重要!该函数有个入参is_opened,表征着文件是否被打开,在文件未被打开的情况下,该函数绝大多数情况下会返回空,除非对应的文件格式是"image2"并且文件格式明确不需要读文件,即AVFMT_NOFILE被置位。这样,3.2)的情形下绝大多数是不会返回的。
3.3)在3.1)3.2)情形下函数未返回,有两种情况:
3.3.1)一种情况是用户指定了输入文件格式,并且存在文件后续需要被访问,那么使用s->io_open()打开文件,创建I/O层的对象AVIOContext,此时,输入文件格式有了,I/O层也有了,该有的都有了,直接返回;
3.3.2)另外一种情况是用户未指定输入文件格式,并且3.2)探测文件格式的方式失败,那么使用s->io_open()打开文件,创建I/O层的对象AVIOContext,并调用av_probe_input_buffer2()函数进行文件格式探测。注意在用户提供I/O层的情形下也使用了av_probe_input_buffer2()函数进行文件格式探测,差别在于av_probe_input_buffer2()使用的I/O层对象AVIOContext对象,一个是用户外部提供的,一个是s->io_open()自主分析资源URL的所属访问协议来产生的。
4. 分析完毕
到3该函数就已经分析完毕,分析过程中有3个重要的函数未进行具体解析:
1)根据文件后缀等探测文件格式的函数av_probe_input_format2(),后文详述;
2)打开文件读取文件内容来推测文件格式的函数av_probe_input_buffer2(),后文详述;
3)另,s->io_open是一个函数指针

av_probe_input_buffer2声明:

所属库:libavformat(lavf) 头文件:libavformat/avformat.h 声明:
功能:读取开的文件中的比特流,探测输入文件格式。每次探测返回一个得分,若得分太低,则增加探测缓冲的长度,读取更多的数据,再进行尝试。当达到探测缓冲区的上限时,返回具有最大探测得分的输入格式。
参数logctx: 日志上下文对象,影响到日志输出,详细情况需要分析ffmpeg中的日志系统,可以参见雷神博客
FFmpeg源代码简单分析:日志输出系统(av_log()等
参数offset:从读取比特流的哪个位置(offset)开始进行探测,一般为0,从头开始
参数max_probe_size:探测缓冲区的最大值,该值一般为AVFormatContext.format_probesize字段提供,该字段在AVFormatContext创建过程中被设置为默认值5000000

/**
* 探测字节流以确定输入格式。 每次探测器返回* 分数太低,探针缓冲区大小增加,另一个* 进行了尝试。 当达到最大探针尺寸时,输入格式* 返回最高分。** @param pb 要探测的字节流* @param fmt 输入格式放在这里* @param url 流的url* @param logctx 日志上下文* @param offset 字节流中要探测的偏移量* @param max_probe_size 最大探针缓冲区大小(默认为零)* @return 成功时的分数,负值对应一个* 最高分是 AVPROBE_SCORE_MAX* 否则为 AVERROR 代码*/
int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,const char *url, void *logctx,unsigned int offset, unsigned int max_probe_size);

av_probe_input_buffer2() 源码

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;// 检查探测缓冲区最大长度,确保其取合适的值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);// 找寻音视频资源的mime_typeif (pb->av_class) {uint8_t *mime_type_opt = NULL;char *semi;// 搜索AVIOContext以及子对象的mime_type选项信息av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);pd.mime_type = (const char *)mime_type_opt;// 选项信息中存在多个,以分号分隔,截断pd_mime_type取第一个semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;if (semi) {*semi = '\0';}}// probe_size初始化为最小buffer大小2048// 退出条件是probe_size超过最大探测缓冲大小max_probe_size或者是找到了AVInputFormat// probe_size没循环一次,缓冲区大小乘2,指数增长。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;// 分配缓冲区大小为probe_size+AVPROBE_PADDING_SIZE// 为什么缓冲区的长度要加AVPROBE_PADDING_SIZE,将会在后文分析。// 注意此处使用的是av_realloc而非av_malloc,为了新分配的缓冲区中还保留上次读取的数据/* Read probe data. */if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)goto fail;// 调用avio_read从上次读取数据之处(合理的偏移量)处读取缓冲扩展大小的字节数// 填充到扩展的空间上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 */}// 更新读取数据bytesbuf_offset += ret;// 读取数据bytes不够传入的探测数据offset// 不进行文件格式探测,此时数据是不够的,因此,进入下一轮继续读取数据if (buf_offset < offset)continue;// 填充AVProbeData对象,数据大小是已读取数据字节数-探测偏移offset// 设置相应的指针从offset开始,注意一点AVProbeData的缓冲是复用了// 本函数内部临时变量buf分配的内存,并没有再申请新的内存。pd.buf_size = buf_offset - offset;pd.buf = &buf[offset];// 缓冲尾部多余的AVPROBE_PADDING_SIZE清零// 为什么要这么做?见av_probe_input_format2函数分析memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);// av_probe_input_format2根据AVProbeData提供的文件名// mime类型,读取的文件数据来猜测文件格式/* 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:// 注意,探测完毕需要将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;
}

该函数内容比较长,但是按如下分段理解就会变得很容易:
1)函数内部参数声明以及入参检查
1.1)初始化AVProbeData,注意,此时AVProbeData中只有filename是有效的
1.2)检查max_probe_size探测缓冲区最大长度,确保其取合适的值:
当传入max_probe_size为0时,max_probe_size取值PROBE_BUF_MAX如下所示,十进制为524288
当传入max_probe_size不为0时,那么max_probe_size必须大于PROBE_BUF_MIN,为2048
1.3)检查offset探测的初始偏移量是否合法,不能超过max_probe_size

/** size of probe buffer, for guessing file type from file contents */
#define PROBE_BUF_MIN 2048
#define PROBE_BUF_MAX (1 << 20)

)搜索mime_type,填充AVProbeData
通过av_opt_get()设置选项AV_OPT_SEARCH_CHILDREN来层层搜索AVIOContext及其子对象,由FFMPEG4.1源码 可知AVIOContext 持有 URLContext,URLContext持有URLProtocol以及XXXContext,XXX为协议名,目前只有http协议的HttpContext上下文对象中才会有mime_type成员。在http协议交互的过程中,http协议头 key为"Content-Type"会传输mime_type,如果可能存在多个mime_type时,其value会以";"分隔。分析代码可知:此处,取第一个mime_type。
3)读取文件数据,进行文件格式探测。
这个是本函数的主体内容,详细分析见源码注释
3.1)缓冲区大小初始化PROBE_BUF_MIN,每循环一次缓冲区大小乘2,当缓冲区超过max_probe_size或者是找到文件格式了则退出循环。
3.2)使用avio_read()读取文件字节填充临时缓冲区,然后AVProbeData中的缓冲区复用这个临时缓冲区,该函数是FFMPEG中IO层api函数
3.2)调用av_probe_input_format2()以及AVProbeData进行文件格式探测,后文将分析该函数
3.3)重复3.2)3.3)以便找到满足条件的文件格式。
4)收尾工作
4.1)若3)中未找到合适的输入文件格式,说明无法识别输入文件格式,直接返回错误就行了,一般应用程序运行到此也就结束了
4.2)若3)中找到了合适的输入文件格式,那么调用ffio_rewind_with_probe_data()将I/O层的AVIOContext内部缓冲区归位,此时内部还保留着文件探测过程中读取的数据。

av_probe_input_format2 声明:

所属库:libavformat(lavf)
头文件:libavformat/avformat.h
声明:
功能:猜测文件格式
参数pd:需要探测的数据,AVProbeData 结构体对象
参数is_opened:文件是否已被打开,相当重要的一个参数,决定了过滤掉哪些文件格式!!!!
参数 score_max:传入初始化的文件格式探测得分门限,传出整个探测过程中得到最适文件格式的值。


/**
* 猜测文件格式。** @param pd 要探测的数据* @param is_opened 文件是否已经打开; 决定是否* 探测带有或不带有 AVFMT_NOFILE 的分路器。* @param score_max 一个比这需要接受一个更大的探测分数*检测,变量设置为实际检测* 之后得分。* 如果分数 <= AVPROBE_SCORE_MAX / 4 则推荐* 使用更大的探针缓冲区重试。*/
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max);

av_probe_input_format2 源码:

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);if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}

该函数逻辑比较简单,将主要的探测过程交由av_probe_input_format3()去完成,找到最适合的输入文件格式后,判断这个最适文件格式得分是否比门限值(25)大,如果确实大于门限25,那么这次探测是成功的,返回这个最适文件格式以及得分;若是探测得分不高于(小于等于)门限值25,那么表示本次探测失败,返回空的文件格式。
av_probe_input_format3 声明:

所属库:libavformat(lavf)
头文件:libavformat/avformat.h
声明:
功能:猜测文件格式
参数pd:需要探测的数据,AVProbeData 结构体对象
参数is_opened:文件是否已被打开,相当重要的一个参数,决定了过滤掉哪些文件格式!!!!
参数 score_ret:传出整个探测过程中得到最适文件格式的值。

/**
* 猜测文件格式。** @param is_opened 文件是否已经打开; 决定是否* 探测带有或不带有 AVFMT_NOFILE 的分路器。* @param score_ret 最佳检测的分数。*/
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret);

av_probe_input_format3 源码:

AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;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;// 检查参数的有效性,必须保证buf的最尾部有32字节的0// 当buf为空时,提供一个栈上的32个字节0的数组if (!lpd.buf)lpd.buf = (unsigned char *) zerobuffer;// 对文件头部是否存在ID3进行处理,得出当前探测缓冲区长度与ID3长度之间的关系// 处理ID3,ID3的头部10个字节长,分析文件前10个字节分析是否存在ID3if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {// 分析id3头获取id3所占长度int id3len = ff_id3v2_tag_len(lpd.buf);// 将id3长度与缓冲区长度比较if (lpd.buf_size > id3len + 16) {      // id3长度小于探测缓冲区大小if (lpd.buf_size < 2LL*id3len + 16)// id3长度小于探测缓冲区大小,但差不多要大于了nodat = ID3_ALMOST_GREATER_PROBE;// 将探测buffer起始位置进行偏移,从id3数据往后进行探测lpd.buf      += id3len;lpd.buf_size -= id3len;} else if (id3len >= PROBE_BUF_MAX) {  // id3长度大于探测缓冲区的最大值了nodat = ID3_GREATER_MAX_PROBE;} else                                 // id3长度大于探测缓冲区nodat = ID3_GREATER_PROBE;}// 文件格式探测// 迭代获取一个FFMPEG支持的文件格式AVInputFormatwhile ((fmt1 = av_demuxer_iterate(&i))) {// 过滤掉一些格式// IO层已打开,那么,不需要读取数据的文件格式都会被过滤掉// IO层未打开,那么,需要读取数据的文件格式都会被过滤掉if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;// 开始计算得分score = 0;// 优先使用read_probe()进行打分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://该情况计算得分一般小于24,需读取更多数据再次进行判断case ID3_ALMOST_GREATER_PROBE: //该情况,取计算得分与24的较大值score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);break;case ID3_GREATER_MAX_PROBE: //该情况取计算得分与50的最大值,读再多数据也无用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;}// 再次根据MIME类型进行判断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;
}

strcmp()
功能:用来比较两个字符串
参数:s1、s2为两个进行比较的字符串
返回值:若s1、s2字符串相等,则返回零;若s1大于s2,则返回大于零的数;否则,则返回小于零的数。
说明:strcmp()函数是根据ACSII码的值来比较两个字符串的;strcmp()函数首先将s1字符串的第一个字符值减去s2第一个字符,若差值为零则继续比较下去;若差值不为零,则返回差值。

先画个重点:文件格式探测有3类数据可以参考:一个是文件扩展名,一个是MIME类型,还一个是读取文件数据进行分析。本函数倾向于读取文件数据,然后通过各个AVInputFormat.read_probe来分析确定是否是该格式,这样找到的文件格式是最可信的,而通过文件扩展名判断文件格式是下下策,因为文件扩展名嘛,谁都能修改,当然,通过MIME类型来判断文件格式比扩展名可信度上升一个台阶,但小于读取数据进行判定。也即可信度排序:
读取数据进行判定 > 根据MIME类型判定 > 根据文件扩展名判定
但是!有个问题是当读取的文件数据不够时,也即探测缓冲区的里面的数据不太多时,根据某个格式的AVInputFormat.read_probe()方法计算得分会比较低,这样,这个得分无法真实判定该文件是否就是该格式,这样,我们一般会期待读取更多的数据到探测缓冲区进一步的判断,FFMPEG中就是这样一个逻辑。
基于以上的分析,我们来对函数逻辑进行梳理:
1,检查参数有效性
保证AVProbeData.buf的最尾部有AVPROBE_PADDING_SIZE(32字节)的0,当buf为空时,提供一个栈上的32个字节0的数组空间。为什么要这么做?为什么是32字节的0?
2,对文件头部是否存在ID3进行处理,得出当前探测缓冲区长度与ID3长度之间的关系:
要理解这块代码那么就需要了解什么是ID3,此处简单的引用下wikipedia对ID3描述:ID3是一种metadata容器,多应用于MP3格式的音频文件中。它可以将相关的曲名、演唱者、专辑、音轨数等信息存储在MP3文件中,又称作“ID3Tags”。
可以参考如下几个链接对ID3进行详细的了解:
https://zh.wikipedia.org/wiki/ID3
https://blog.csdn.net/u014294166/article/details/53153507
https://blog.csdn.net/thomasyuan8/article/details/81571362
函数内部声明了一个枚举变量,表征了ID3长度与当前探测缓冲区长度之间的关系,如下:

enum nodat {NO_ID3,                    //不存在ID3ID3_ALMOST_GREATER_PROBE,  //ID3长度小于探测缓冲区长度,但差不多要大于了ID3_GREATER_PROBE,         //ID3长度大于探测缓冲区长度ID3_GREATER_MAX_PROBE,     //ID3长度大于探测缓冲区长度,并且大于探测缓冲区最大长度
} nodat = NO_ID3;

该参数的值会影响到后续格式探测函数AVInputFormat.read_probe得分的有效性,列举如下:
1)nodat为NO_ID3:参与read_probe()探测的数据是刨除掉ID3数据后真实有效数据,因此,通过该函数计算得分可信程度高。
2)nodat为ID3_ALMOST_GREATER_PROBE:参与read_probe()探测的数据是刨除掉ID3数据后真实有效数据,但比NO_ID3可信度要低,因为其意思是"ID3长度小于探测缓冲区长度,但差不多要大于了",意味着探测缓冲区内可用于read_probe()探测的有效数据可能不会太长,因此,得分可能不太准确,如果判断得分很低,会期待读取更多的数据再进一步的判断。但也保不准数据已经够了,得分高。
3)nodat为ID3_GREATER_PROBE:参与read_probe()探测的数据全都是ID3数据,通过该函数计算得分是不可靠的。我们需要也可以读取更多的数据再进一步的判断。
4)nodat为ID3_GREATER_MAX_PROBE:参与read_probe()探测的数据全都是ID3数据,通过该函数计算得分是不可靠的。 但是由于ID3长度实在太大,已经超过了用于探测的最大缓冲区长度,读再多数据也是无用,因此,该情况还是使用文件扩展名和MIME类型进行文件类型断定吧
3.文件格式探测
3.1)av_demuxer_iterate()迭代获取一个AVInputFormat对象
3.2)过滤掉一些文件格式,如源码上注释所说。这个地方非常重要,因为在前文分析init_input()函数中,在没有打开文件的情况下,就会调用本函数进行文件格式探测,此时,一般常规的文件格式,比如mp4,flv等等都会被过滤掉,从而获取不到对应的文件格式。
3.3)文件格式探测
3.3.1)AVInputFormat.read_probe()存在
3.3.1.1)使用其进行文件格式探测,并计算得分。
3.3.1.2)判断文件扩展名是否匹配,若匹配,则根据nodat值进行分类处理,综合考虑计算得分与文件扩展名匹配的得分AVPROBE_SCORE_EXTENSION给出最终得分
3.3.2)AVInputFormat.read_probe()不存在,则根据文件扩展名。
3.3.3)根据MIME是否匹配,来更新得分
3.3.4)保留最大得分以及对应的文件格式
4.如果nodat==ID3_GREATER_PROBE,那么期待读取更多的有效数据到缓冲区,然后进行read_probe()来计算得分,因此将最终得分更新为24与当前最大得分中的较小值。

flv_read_header源码:
源码的解释见注释,可见,对于flv格式来说,本函数只做了这么几件很简单的工作
1) 对AVFormatContext的媒体起始时间字段赋初值0:s->start_time = 0;
2) 对AVFormatContext的私有数据AVFormatContext.priv_data数据(此处为FLVContext)的字段进行了初始化,尤其是flv->missing_streams字段,在读取flv header的基础上进行了是否存在音频流,视频流的断定,该值不为0,表示存在流,为0表示不存在流
3)读取flv header之后,还读取了PreviousTagSize0,这个记录了前一个TAG的数据大小,由于这个数据之前没有TAG存在,因此恒定为0,若读到数据不为0,那么肯定不是标准的flv格式,打印下警告信息。

static int flv_read_header(AVFormatContext *s)
{int flags;// 注意在前文AVFormat_open_input()函数中,s->priv_data已经被创建正确的创建并且初始化FLVContext *flv = s->priv_data;   int offset;int pre_tag_size = 0;// 跳过前4个字节,前4个字节分别是"F" "L" "V"和版本号avio_skip(s->pb, 4);// 读取一个字节到flagsflags = avio_r8(s->pb);// flags的高5个bit为0,第二个bit为0,第1个bit和第3个bit分别是否存在视频流和音频流,只要有流// 那么missing_streams为真flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);// 设置流信息flag标志位为AVFMTCTX_NOHEADER,告知该格式无头信息,其实就是告知流本身的具体信// 息需要进一步读取数据包才能获知s->ctx_flags |= AVFMTCTX_NOHEADER;// 读取4个字节到offset,该正数值表示了整个flv头的长度,一半就是0x00000009,整个头(包含本 // offset数据本身)共9个字节,avio_seek()直接跳过整个文件头offset = avio_rb32(s->pb);avio_seek(s->pb, offset, SEEK_SET);// 文件头后的4个字节是记录前一个TAG的长度,由于前面没有TAG,只有flv header,因此该值应该为0// 如果不为0,则输出告警日志,告知本flv文件不是标准的flv格式/* 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");}// 设置其实时间戳为0,从0开始;// 设置当前位置总共的flv tag的大小为0,因为当前位置还未读取任一TAG// 设置最后关键帧的流index为-1s->start_time = 0;flv->sum_flv_tag_size = 0;flv->last_keyframe_stream_index = -1;return 0;
}

mov_read_header源码:只指出一些比较重要的点:该函数会去找所有的mp4文件格式的box,主要是moov以及mdat这两个主要的box,会去层层解析moov box,找到文件中包含几个流,调用 avformat_new_stream()方法创建对应的流,并读取流相关信息(比如码率,帧率,宽高,时间基,采样率,采样格式等等),用于流的编解码。

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;// 读取moov box/* 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");mov_read_close(s);return err;}} 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");mov_read_close(s);return AVERROR_INVALIDDATA;}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->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)return err;}}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);mov_read_close(s);return AVERROR_INVALIDDATA;}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);mov_read_close(s);return AVERROR_INVALIDDATA;}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) {mov_read_close(s);return err;}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)return err;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)return err;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)return err;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)return err;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)return err;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;
}

update_stream_avctx源码: AVStream.codecpar中保存着流的最新编码信息。在前文文件格式的读取文件头函数xxx_read_header()中,如果能获取流的信息,则会创建流AVStream对象,并且会对AVStream的AVCodecParameters类型的成员AVStream.codecpar进行赋值,但是AVStream.internal.avctx以及为了兼容老版本的AVStream.codec都需要进行赋值,这两个参数都是AVCodecContext的结构体,本函数的作用就是调用avcodec_parameters_to_context()函数将编码器参数从AVStream.codecpar拷贝到AVStream.internal.avctx以及AVStream.codec这两个AVCodecContext对象中。

static int update_stream_avctx(AVFormatContext *s)
{int i, ret;for (i = 0; i < s->nb_streams; i++) {AVStream *st = s->streams[i];// 该字段表征是否需要进行参数拷贝if (!st->internal->need_context_update)continue;// 关闭解析器,依赖于编码器/* close parser, because it depends on the codec */if (st->parser && st->internal->avctx->codec_id != st->codecpar->codec_id) {av_parser_close(st->parser);st->parser = NULL;}// 拷贝编解码参数到st->internal->avctx/* update internal codec context, for the parser */ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);if (ret < 0)return ret;// 拷贝编解码参数到st->codec
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS/* update deprecated public codec context */ret = avcodec_parameters_to_context(st->codec, st->codecpar);if (ret < 0)return ret;
FF_ENABLE_DEPRECATION_WARNINGS
#endif// 参数已更新,设置标志位st->internal->need_context_update = 0;}return 0;
}

avcodec_parameters_to_context声明:

所属库:libavcodec(lavf)
头文件:libavcodec/avcodec.h
声明:根据编码器参数中的值来填充编码器上下文结构体。编解码器上下文中的已分配的,在编码器参数中能找到对应的字段将被释放并替换成编码器参数中的对应值,那些存在于编码器上下文中但是不在编码器参数中的字段将不变。

/**
* 根据提供的编解码器中的值填充编解码器上下文* 参数。 编解码器中任何已分配的字段在* par 被释放并替换为 par 中相应字段的重复项。* 编解码器中在 par 中没有对应项的字段不会被触及。** @return >= 0 成功,失败时返回负的 AVERROR 代码。*/
int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par);

avcodec_parameters_to_context源码:

int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par)
{// 编码器类型-视频?音频?字幕?codec->codec_type = par->codec_type;// 编码器id,可以找到唯一的编码器codec->codec_id   = par->codec_id;// 编码器tag,编码器的另外一种表述codec->codec_tag  = par->codec_tag;// 平均码率codec->bit_rate              = par->bit_rate;// 采样点编码后所占位数codec->bits_per_coded_sample = par->bits_per_coded_sample;// 原始采样点所占位数codec->bits_per_raw_sample   = par->bits_per_raw_sample;// 编码profilecodec->profile               = par->profile;// 编码levelcodec->level                 = par->level;// 针对不同的编码器类型赋值switch (par->codec_type) {// 视频case AVMEDIA_TYPE_VIDEO:// 像素格式codec->pix_fmt                = par->format;// 视频宽度codec->width                  = par->width;// 视频高度codec->height                 = par->height;// 场帧顺序-逐行扫描?顶场先编码先展示,顶场先编码后展示,底场先编码先展示,底场先编码后展示codec->field_order            = par->field_order;// 颜色相关的几个参数,不太懂codec->color_range            = par->color_range;codec->color_primaries        = par->color_primaries;codec->color_trc              = par->color_trc;codec->colorspace             = par->color_space;codec->chroma_sample_location = par->chroma_location;// SAR,样本的宽高比codec->sample_aspect_ratio    = par->sample_aspect_ratio;// 是否有b帧,对应par中是否有视频延迟codec->has_b_frames           = par->video_delay;break;// 音频case AVMEDIA_TYPE_AUDIO:// 采样格式codec->sample_fmt       = par->format;// 通道布局codec->channel_layout   = par->channel_layout;// 通道数codec->channels         = par->channels;// 采样率codec->sample_rate      = par->sample_rate;// 音频包中的对齐块大小codec->block_align      = par->block_align;// 一帧音频中单个通道采样个数(Number of samples per channel in an audio fram)codec->frame_size       = par->frame_size;// 编码延迟,为送入若干采样点之后编码器才能产生合法的输出,此时的采样点个数codec->delay            =// 音频编码会在编码数据前加initial_padding个字节,解码后需要丢弃这么多字节,才能得到真正的音频数据codec->initial_padding  = par->initial_padding;// 音频编码会在编码数据后加trailing_padding个字节,解码后需要丢弃这么多字节,才能得到真正的音频数据codec->trailing_padding = par->trailing_padding;// 遇到不连续时需要跳过的样本数(编码时使用)codec->seek_preroll     = par->seek_preroll;break;// 字幕case AVMEDIA_TYPE_SUBTITLE:// 宽codec->width  = par->width;// 高codec->height = par->height;break;}// 拷贝extradata,对于h.264视频流来说,sps和pps存在此处if (par->extradata) {av_freep(&codec->extradata);codec->extradata = av_mallocz(par->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);if (!codec->extradata)return AVERROR(ENOMEM);memcpy(codec->extradata, par->extradata, par->extradata_size);codec->extradata_size = par->extradata_size;}return 0;
}

avformat_open_input()相关推荐

  1. ffmpeg avformat_open_input always returns “Protocol not found”

    ffmpeg avformat_open_input always returns "Protocol not found" rv=(-1330794744) 网上给的答案: av ...

  2. FFmpeg 源码学习(一):avformat_open_input 源码分析

    一.源码方法参数分析 下面是avformat_open_input的方法及参数: /** * Open an input stream and read the header. The codecs ...

  3. FFMPEG avformat_open_input

    FFMPEG avformat_open_input avformat_open_input(),该函数用于打开多媒体数据并且获取一些信息 声明libavformat/avformat.h /*** ...

  4. ffmpeg avformat_open_input返回失败的解决办法

    用ffmpeg做的第一个程序,参考网上的代码,就出现了一些问题,其中avformat_open_input返回失败. 下面是我在网上收集到的失败信息的相关解决: 很多朋友在使用新版本的ffmpeg时, ...

  5. 【FFMPEG源码终极解析】 avformat_open_input (一)

    avformat_open_input   打开媒体函数,先上全部源码.然后逐语句分析. int avformat_open_input(AVFormatContext **ps, const cha ...

  6. 解封装(二):初始化解封装avformat_open_input,各参数分析,以及简单流程

    如下代码: #include <iostream>extern "C" { #include "libavformat\avformat.h" }# ...

  7. FFMPEG源码分析:avformat_open_input()(媒体打开函数)

    本文分析了FFMPEG中的媒体打开函数avformat_open_input() //参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功, //会返回一个AVFormatCo ...

  8. 图解FFMPEG打开媒体的函数avformat_open_input

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  9. avformat_open_input返回-1094995529 “Invalid data found when processing input“

    avformat_open_input返回-1094995529 avformat_open_input() 返回"Invalid data found when processing in ...

  10. FFMPEG学习遇到avformat_open_input Invalid data found when processing input

    按顺序调用 av_register_all(); avcodec_register_all(); avformat_network_init(); 调用 avformat_open_input() 打 ...

最新文章

  1. 设计模式入门:建造者模式
  2. 借教室(codevs 1217)
  3. 判断一个整数是不是回文
  4. 箭头函数写法_初探ES6:箭头函数
  5. Linux安装dos环境,Ubuntu安装dos2unix工具
  6. 屏幕共享的实现与应用
  7. 计算机网路vlan划分练习
  8. CnOpenData中国工业企业股东信息数据
  9. ubuntu 替换清华源遇到的问题-不能更新,无法拉取 https 源解决
  10. 川大教师发自白书:一所高校就是一座衙门
  11. 教你用优化视频的方法提高视频的质量
  12. 2022年安全员-A证操作证考试题模拟考试平台操作
  13. 华为机试真题 Python 实现【分月饼】
  14. python的一些报错解决
  15. Java为 pdf、word和excel添加水印
  16. 3des java ecb_C# And Java 3DES加解密 ECB模式/PKCS7
  17. js创建节点及节点操作
  18. 这是一则招聘贴——招聘区块链系统开发实习生
  19. PC 版微信多开防撤回软件
  20. 【浏览器】HTTP 缓存机制

热门文章

  1. 【Oracle 优化器】动态统计(Dynamic Statistics)
  2. linux下读取excel文件
  3. 爬虫入门一:BeautifulSoup解析豆瓣即将上映的电影信息
  4. 全国计算机竞赛保送清华,35人!江苏2021清华、北大保送名单公布!
  5. 全景图转小行星视角投影原理详解
  6. chromium 编译
  7. Netgear路由被曝漏洞 几乎所有型号涉及
  8. 【《Real-Time Rendering 3rd》 提炼总结】(十二) 渲染管线优化方法论:从瓶颈定位到优化策略
  9. Linux使用基础(目录)顶顶顶
  10. jenkins结合git实现流水线作业