参考链接
- 图解FFMPEG打开媒体的函数avformat_open_input_雷霄骅的博客-CSDN博客_avformat_open_input 使用
- FFmpeg源代码简单分析:avformat_open_input()_雷霄骅的博客-CSDN博客_avformat_open_input()
avformat_open_input
- FFmpeg打开媒体的的过程开始于avformat_open_input,因此该函数的重要性不可忽视。
int avformat_open_input(AVFormatContext **ps, const char *filename,const AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;FFFormatContext *si;AVDictionary *tmp = NULL;ID3v2ExtraMeta *id3v2_extra_meta = NULL;int ret = 0;if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM);si = ffformatcontext(s);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);if (s->pb) // must be before any goto fails->flags |= AVFMT_FLAG_CUSTOM_IO;if ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;if (!(s->url = av_strdup(filename ? filename : ""))) {ret = AVERROR(ENOMEM);goto fail;}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;}}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 an AVIOContext */if (s->pb)ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);if (s->iformat->read_header)if ((ret = s->iformat->read_header(s)) < 0) {if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)goto close;goto fail;}if (!s->metadata) {s->metadata = si->id3v2_meta;si->id3v2_meta = NULL;} else if (si->id3v2_meta) {av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");av_dict_free(&si->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 (s->pb && !si->data_offset)si->data_offset = avio_tell(s->pb);si->raw_packet_buffer_size = 0;update_stream_avctx(s);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;
}
- 在该函数中,FFMPEG完成了:
- 输入输出结构体AVIOContext的初始化;
- 输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):
- 1判断文件名的后缀
- 2读取文件头的数据进行比对;
- 使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
- 剩下的就是调用该URLProtocol的函数进行open,read等操作了 没看到相关的代码
- 函数的形参
- ps:函数调用成功之后处理过的AVFormatContext结构体。
- file:打开的视音频流的URL。
- fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
- options:附加的一些选项,一般情况下可以设置为NULL。
- 函数执行成功的话,其返回值大于等于0。
- 以下是函数调用关系图(有存在差异的地方)
- Avio_open源代码里面已经不用了,而是使用io_open,只是打开了,但是没看到open,read等操作的代码
- avformat_open_input()源代码比较长,一部分是一些容错代码,比如说如果发现传入的AVFormatContext指针没有初始化过,就调用avformat_alloc_context()初始化该结构体;
- 还有一部分是针对一些格式做的特殊处理,比如id3v2信息的处理等等。
- 有关上述两种信息不再详细分析,在这里只选择它关键的两个函数进行分析:
- init_input():绝大部分初始化工作都是在这里做的。
- s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream。
- 下面我们逐一看看上述函数。
init_input()
- 它的主要工作就是打开输入的视频数据并且探测视频的格式
static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
{int ret;AVProbeData pd = { filename, NULL, 0 };int score = AVPROBE_SCORE_RETRY;if (s->pb) {s->flags |= AVFMT_FLAG_CUSTOM_IO;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;}if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;if (s->iformat)return 0;return av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}
- 这个函数在短短的几行代码中包含了好几个return,因此逻辑还是有点复杂的,我们可以梳理一下:
- 在函数的开头的score变量是一个判决AVInputFormat的分数的门限值,如果最后得到的AVInputFormat的分数低于该门限值,就认为没有找到合适的AVInputFormat。
- FFmpeg内部判断封装格式的原理实际上是对每种AVInputFormat给出一个分数,满分是100分,越有可能正确的AVInputFormat给出的分数就越高。最后选择分数最高的AVInputFormat作为推测结果。
- score的值是一个宏定义AVPROBE_SCORE_RETRY,我们可以看一下它的定义:
- #define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
- 其中AVPROBE_SCORE_MAX是score的最大值,取值是100:
- #define AVPROBE_SCORE_MAX 100 ///< maximum score
- 由此我们可以得出score取值是25,即如果推测后得到的最佳AVInputFormat的分值低于25,就认为没有找到合适的AVInputFormat。
- 整个函数的逻辑大体如下:
- (1)当使用了自定义的AVIOContext的时候(AVFormatContext中的AVIOContext不为空,即s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果没有指定就调用av_probe_input_buffer2()推测AVInputFormat。这一情况出现的不算很多,但是当我们从内存中读取数据的时候(需要初始化自定义的AVIOContext),就会执行这一步骤。
- (2)在更一般的情况下,如果已经指定了AVInputFormat,就直接返回;如果没有指定AVInputFormat,就调用av_probe_input_format(NULL,…)根据文件路径判断文件格式。这里特意把av_probe_input_format()的第1个参数写成“NULL”,是为了强调这个时候实际上并没有给函数提供输入数据,此时仅仅通过文件路径推测AVInputFormat。
- (3)如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个时候会首先调用io_open()打开文件,然后调用av_probe_input_buffer2()推测AVInputFormat。
av_probe_input_format2()
- av_probe_input_format2()是一个API函数。
const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,int is_opened, int *score_max)
{int score_ret;const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}
- 该函数用于根据输入数据查找合适的AVInputFormat。参数含义如下所示:
- pd:存储输入数据信息的AVProbeData结构体。
- is_opened:文件是否打开。
- score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。
- 从函数中可以看出,av_probe_input_format2()调用了av_probe_input_format3(),并且增加了一个判断,当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。
- 下面我们看一下av_probe_input_format3()
- 从函数声明中可以看出,av_probe_input_format3()和av_probe_input_format2()的区别是函数的第3个参数不同:av_probe_input_format2()是一个分数的门限值,而av_probe_input_format3()是一个探测后的最匹配的格式的分数值。
- av_probe_input_format3函数最主要的部分是循环。该循环调用av_iformat_next()遍历(av_demuxer_iterate函数替代了av_iformat_next)FFmpeg中所有的AVInputFormat,并根据以下规则确定AVInputFormat和输入媒体数据的匹配分数(score,反应匹配程度):
- (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。
#define AVPROBE_PADDING_SIZE 32 ///< extra allocated bytes at the end of the probe buffer
#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 scoreconst AVInputFormat *av_probe_input_format3(const AVProbeData *pd,int is_opened, int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;const 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;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;}while ((fmt1 = av_demuxer_iterate(&i))) {if (fmt1->flags & AVFMT_EXPERIMENTAL)continue;if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))continue;score = 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;}}} 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 = 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;
}
const AVInputFormat *av_demuxer_iterate(void **opaque)
{static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1;uintptr_t i = (uintptr_t)*opaque;const AVInputFormat *f = NULL;uintptr_t tmp;if (i < size) {f = demuxer_list[i];} else if (tmp = atomic_load_explicit(&indev_list_intptr, memory_order_relaxed)) {const AVInputFormat *const *indev_list = (const AVInputFormat *const *)tmp;f = indev_list[i - size];}if (f)*opaque = (void*)(i + 1);return f;
}
AVInputFormat->read_probe()
- AVInputFormat中包含read_probe()是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。
- 例如,FLV封装格式的AVInputFormat模块定义如下所示。
const AVInputFormat ff_flv_demuxer = {.name = "flv",.long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.priv_data_size = sizeof(FLVContext),.read_probe = flv_probe,.read_header = flv_read_header,.read_packet = flv_read_packet,.read_seek = flv_read_seek,.read_close = flv_read_close,.extensions = "flv",.priv_class = &flv_kux_class,
};
- 其中,read_probe()函数对应的是flv_probe()函数
- 我们可以看一下flv_probe()函数的定义:
static int flv_probe(const AVProbeData *p)
{return probe(p, 0);
}
- 可见flv_probe()调用了一个probe()函数。probe()函数的定义如下。
static int probe(const AVProbeData *p, int live)
{const uint8_t *d = p->buf;unsigned offset = AV_RB32(d + 5);if (d[0] == 'F' &&d[1] == 'L' &&d[2] == 'V' &&d[3] < 5 && d[5] == 0 &&offset + 100 < p->buf_size &&offset > 8) {int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);if (live == is_live)return AVPROBE_SCORE_MAX;}return 0;
}
- 从probe()函数我们可以看出,该函数做了如下工作:
- (1)获得第6至第9字节的数据(对应Headersize字段)并且做大小端转换,然后存入offset变量。之所以要进行大小端转换是因为FLV是以“大端”方式存储数据,而操作系统是以“小端”方式存储数据,这一转换主要通过AV_RB32()函数实现。AV_RB32()是一个宏定义,其对应的函数是av_bswap32()。
- (2)检查开头3个字符(Signature)是否为“FLV”
- (3)第4个字节(Version)小于5
- (4)第6个字节(Headersize的第1个字节?)为0
- (5)offset取值大于8。
- 参照FLV文件头的格式可以对上述判断有一个更清晰的认识:
- 此外代码中还包含了有关live方式的FLV格式的判断,在这里我们不加探讨。对于我们打开FLV文件来说,live和is_live两个变量取值都为0。也就是说满足上述5个条件的话,就可以认为输入媒体数据是FLV封装格式了。
- 满足上述条件,probe()函数返回AVPROBE_SCORE_MAX(AVPROBE_SCORE_MAX取值为100,即100分),否则返回0(0分)
av_match_name()
- av_match_name()是一个API函数,声明位于libavutil\avstring.h,如下所示
- av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。
int av_match_name(const char *name, const char *names)
{const char *p;int len, namelen;if (!name || !names)return 0;namelen = strlen(name);while (*names) {int negate = '-' == *names;p = strchr(names, ',');if (!p)p = names + strlen(names);names += negate;len = FFMAX(p - names, namelen);if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))return !negate;names = p + (*p == ',');}return 0;
}
- 上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下
const AVInputFormat ff_matroska_demuxer = {.name = "matroska,webm",.long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"),.extensions = "mkv,mk3d,mka,mks,webm",.priv_data_size = sizeof(MatroskaDemuxContext),.flags_internal = FF_FMT_INIT_CLEANUP,.read_probe = matroska_probe,.read_header = matroska_read_header,.read_packet = matroska_read_packet,.read_close = matroska_read_close,.read_seek = matroska_read_seek,.mime_type = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
};
- 从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”,mime_type字段对应“audio/webm,audio/x-matroska,video/webm,video/x-matroska”。
- av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个的名称,然后一一进行比较。
av_match_ext()
- av_match_ext()是一个API函数,声明位于libavformat\avformat.h(注意位置和av_match_name()不一样),如下所示
- av_match_ext()用于比较文件的后缀。该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。
- 然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。
/*** @file* Format register and lookup*/int av_match_ext(const char *filename, const char *extensions)
{const char *ext;if (!filename)return 0;ext = strrchr(filename, '.');if (ext)return av_match_name(ext + 1, extensions);return 0;
}
AVProbeData
- av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中
- 该函数中涉及到一个结构体AVProbeData,用于存储输入文件的一些信息,它的定义如下所示。
- 其中filename是文件路径
- buf存储用于推测AVInputFormat的媒体数据,其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0)。
- 最后还有个mime_type保存媒体的类型。
/*** This structure contains the data a format has to probe a file.*/
typedef struct AVProbeData {const char *filename;unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */int buf_size; /**< Size of buf except extra allocated bytes */const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
/*** A callback for opening new IO streams.** Whenever a muxer or a demuxer needs to open an IO stream (typically from* avformat_open_input() for demuxers, but for certain formats can happen at* other times as well), it will call this callback to obtain an IO context.** @param s the format context* @param pb on success, the newly opened IO context should be returned here* @param url the url to open* @param flags a combination of AVIO_FLAG_** @param options a dictionary of additional options, with the same* semantics as in avio_open2()* @return 0 on success, a negative AVERROR code on failure** @note Certain muxers and demuxers do nesting, i.e. they open one or more* additional internal format contexts. Thus the AVFormatContext pointer* passed to this callback may be different from the one facing the caller.* It will, however, have the same 'opaque' field.*/int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,int flags, AVDictionary **options);
av_probe_input_buffer2()
- av_probe_input_buffer2()是一个API函数,它根据输入的媒体数据推测该媒体数据的AVInputFormat,声明位于libavformat\avformat.h,如下所示。
/*** Probe a bytestream to determine the input format. Each time a probe returns* with a score that is too low, the probe buffer size is increased and another* attempt is made. When the maximum probe size is reached, the input format* with the highest score is returned.** @param pb the bytestream to probe* @param fmt the input format is put here* @param url the url of the stream* @param logctx the log context* @param offset the offset within the bytestream to probe from* @param max_probe_size the maximum probe buffer size (zero for default)* @return the score in case of success, a negative value corresponding to an* the maximal score is AVPROBE_SCORE_MAX* AVERROR code otherwise*/
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,const char *url, void *logctx,unsigned int offset, unsigned int max_probe_size);
- av_probe_input_buffer2()参数的含义如下所示:
- pb:用于读取数据的AVIOContext。
- fmt:输出推测出来的AVInputFormat。
- url:输入媒体的路径。
- logctx:日志(没有研究过)。
- offset:开始推测AVInputFormat的偏移量。
- max_probe_size:用于推测格式的媒体数据的最大值。
- 返回推测后的得到的AVInputFormat的匹配分数。
- av_probe_input_buffer2()的定义位于libavformat\format.c,如下所示。
int av_probe_input_buffer2(AVIOContext *pb, const 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;
}
- 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,则会退出循环并且返回错误信息。
AVInputFormat-> read_header()
- 在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。
- read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。
- 举个例子
- 当输入视频的封装格式为FLV的时候,会调用FLV的AVInputFormat中的read_header()
- FLV的AVInputFormat定义位于libavformat\flvdec.c文件中,如下所示。
const AVInputFormat ff_flv_demuxer = {.name = "flv",.long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),.priv_data_size = sizeof(FLVContext),.read_probe = flv_probe,.read_header = flv_read_header,.read_packet = flv_read_packet,.read_seek = flv_read_seek,.read_close = flv_read_close,.extensions = "flv",.priv_class = &flv_kux_class,
};
- 可以看出read_header()指向了flv_read_header()函数
- flv_read_header()的实现同样位于libavformat\flvdec.c文件中,如下所示。
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{FLVContext *flv = s->priv_data;int ret, i, size, flags;enum FlvTagType type;int stream_type=-1;int64_t next, pos, meta_pos;int64_t dts, pts = AV_NOPTS_VALUE;int av_uninit(channels);int av_uninit(sample_rate);AVStream *st = NULL;int last = -1;int orig_size;retry:/* pkt size is repeated at end. skip it */pos = avio_tell(s->pb);type = (avio_r8(s->pb) & 0x1F);orig_size =size = avio_rb24(s->pb);flv->sum_flv_tag_size += size + 11;dts = avio_rb24(s->pb);dts |= (unsigned)avio_r8(s->pb) << 24;av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb));if (avio_feof(s->pb))return AVERROR_EOF;avio_skip(s->pb, 3); /* stream id, always 0 */flags = 0;if (flv->validate_next < flv->validate_count) {int64_t validate_pos = flv->validate_index[flv->validate_next].pos;if (pos == validate_pos) {if (FFABS(dts - flv->validate_index[flv->validate_next].dts) <=VALIDATE_INDEX_TS_THRESH) {flv->validate_next++;} else {clear_index_entries(s, validate_pos);flv->validate_count = 0;}} else if (pos > validate_pos) {clear_index_entries(s, validate_pos);flv->validate_count = 0;}}if (size == 0) {ret = FFERROR_REDO;goto leave;}next = size + avio_tell(s->pb);if (type == FLV_TAG_TYPE_AUDIO) {stream_type = FLV_STREAM_TYPE_AUDIO;flags = avio_r8(s->pb);size--;} else if (type == FLV_TAG_TYPE_VIDEO) {stream_type = FLV_STREAM_TYPE_VIDEO;flags = avio_r8(s->pb);size--;if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD)goto skip;} else if (type == FLV_TAG_TYPE_META) {stream_type=FLV_STREAM_TYPE_SUBTITLE;if (size > 13 + 1 + 4) { // Header-type metadata stuffint type;meta_pos = avio_tell(s->pb);type = flv_read_metabody(s, next);if (type == 0 && dts == 0 || type < 0) {if (type < 0 && flv->validate_count &&flv->validate_index[0].pos > next &&flv->validate_index[0].pos - 4 < next) {av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n");next = flv->validate_index[0].pos - 4;}goto skip;} else if (type == TYPE_ONTEXTDATA) {avpriv_request_sample(s, "OnTextData packet");return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_ONCAPTION) {return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_UNKNOWN) {stream_type = FLV_STREAM_TYPE_DATA;}avio_seek(s->pb, meta_pos, SEEK_SET);}} else {av_log(s, AV_LOG_DEBUG,"Skipping flv packet: type %d, size %d, flags %d.\n",type, size, flags);
skip:if (avio_seek(s->pb, next, SEEK_SET) != next) {// This can happen if flv_read_metabody above read past// next, on a non-seekable input, and the preceding data has// been flushed out from the IO buffer.av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n");return AVERROR_INVALIDDATA;}ret = FFERROR_REDO;goto leave;}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}/* now find stream */for (i = 0; i < s->nb_streams; i++) {st = s->streams[i];if (stream_type == FLV_STREAM_TYPE_AUDIO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&(s->audio_codec_id || flv_same_audio_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&(s->video_codec_id || flv_same_video_codec(st->codecpar, flags)))break;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)break;} else if (stream_type == FLV_STREAM_TYPE_DATA) {if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA)break;}}if (i == s->nb_streams) {static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_DATA};st = create_stream(s, stream_types[stream_type]);if (!st)return AVERROR(ENOMEM);}av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard);if (flv->time_pos <= pos) {dts += flv->time_offset;}if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_AUDIO))av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME);if ((st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || stream_type == FLV_STREAM_TYPE_AUDIO)) ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && stream_type == FLV_STREAM_TYPE_VIDEO)) ||st->discard >= AVDISCARD_ALL) {avio_seek(s->pb, next, SEEK_SET);ret = FFERROR_REDO;goto leave;}// if not streamed and no duration from metadata then seek to end to find// the duration from the timestampsif ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&(!s->duration || s->duration == AV_NOPTS_VALUE) &&!flv->searched_for_end) {int size;const int64_t pos = avio_tell(s->pb);// Read the last 4 bytes of the file, this should be the size of the// previous FLV tag. Use the timestamp of its payload as duration.int64_t fsize = avio_size(s->pb);
retry_duration:avio_seek(s->pb, fsize - 4, SEEK_SET);size = avio_rb32(s->pb);if (size > 0 && size < fsize) {// Seek to the start of the last FLV tag at position (fsize - 4 - size)// but skip the byte indicating the type.avio_seek(s->pb, fsize - 3 - size, SEEK_SET);if (size == avio_rb24(s->pb) + 11) {uint32_t ts = avio_rb24(s->pb);ts |= (unsigned)avio_r8(s->pb) << 24;if (ts)s->duration = ts * (int64_t)AV_TIME_BASE / 1000;else if (fsize >= 8 && fsize - 8 >= size) {fsize -= size+4;goto retry_duration;}}}avio_seek(s->pb, pos, SEEK_SET);flv->searched_for_end = 1;}if (stream_type == FLV_STREAM_TYPE_AUDIO) {int bits_per_coded_sample;channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;bits_per_coded_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;if (!av_channel_layout_check(&st->codecpar->ch_layout) ||!st->codecpar->sample_rate ||!st->codecpar->bits_per_coded_sample) {av_channel_layout_default(&st->codecpar->ch_layout, channels);st->codecpar->sample_rate = sample_rate;st->codecpar->bits_per_coded_sample = bits_per_coded_sample;}if (!st->codecpar->codec_id) {flv_set_audio_codec(s, st, st->codecpar,flags & FLV_AUDIO_CODECID_MASK);flv->last_sample_rate =sample_rate = st->codecpar->sample_rate;flv->last_channels =channels = st->codecpar->ch_layout.nb_channels;} else {AVCodecParameters *par = avcodec_parameters_alloc();if (!par) {ret = AVERROR(ENOMEM);goto leave;}par->sample_rate = sample_rate;par->bits_per_coded_sample = bits_per_coded_sample;flv_set_audio_codec(s, st, par, flags & FLV_AUDIO_CODECID_MASK);sample_rate = par->sample_rate;avcodec_parameters_free(&par);}} else if (stream_type == FLV_STREAM_TYPE_VIDEO) {int ret = flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK, 1);if (ret < 0)return ret;size -= ret;} else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) {st->codecpar->codec_id = AV_CODEC_ID_TEXT;} else if (stream_type == FLV_STREAM_TYPE_DATA) {st->codecpar->codec_id = AV_CODEC_ID_NONE; // Opaque AMF data}if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 ||st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {int type = avio_r8(s->pb);size--;if (size < 0) {ret = AVERROR_INVALIDDATA;goto leave;}if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {// sign extensionint32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;pts = av_sat_add64(dts, cts);if (cts < 0) { // dts might be wrongif (!flv->wrong_dts)av_log(s, AV_LOG_WARNING,"Negative cts, previous timestamps might be wrong.\n");flv->wrong_dts = 1;} else if (FFABS(dts - pts) > 1000*60*15) {av_log(s, AV_LOG_WARNING,"invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);dts = pts = AV_NOPTS_VALUE;}}if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264)) {AVDictionaryEntry *t;if (st->codecpar->extradata) {if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)return ret;ret = FFERROR_REDO;goto leave;}if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;/* Workaround for buggy Omnia A/XE encoder */t = av_dict_get(s->metadata, "Encoder", NULL, 0);if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))st->codecpar->extradata_size = 2;ret = FFERROR_REDO;goto leave;}}/* skip empty data packets */if (!size) {ret = FFERROR_REDO;goto leave;}ret = av_get_packet(s->pb, pkt, size);if (ret < 0)return ret;pkt->dts = dts;pkt->pts = pts == AV_NOPTS_VALUE ? dts : pts;pkt->stream_index = st->index;pkt->pos = pos;if (flv->new_extradata[stream_type]) {int ret = av_packet_add_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,flv->new_extradata[stream_type],flv->new_extradata_size[stream_type]);if (ret >= 0) {flv->new_extradata[stream_type] = NULL;flv->new_extradata_size[stream_type] = 0;}}if (stream_type == FLV_STREAM_TYPE_AUDIO &&(sample_rate != flv->last_sample_rate ||channels != flv->last_channels)) {flv->last_sample_rate = sample_rate;flv->last_channels = channels;ff_add_param_change(pkt, channels, 0, sample_rate, 0, 0);}if (stream_type == FLV_STREAM_TYPE_AUDIO ||(flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY ||stream_type == FLV_STREAM_TYPE_SUBTITLE ||stream_type == FLV_STREAM_TYPE_DATA)pkt->flags |= AV_PKT_FLAG_KEY;leave:last = avio_rb32(s->pb);if (!flv->trust_datasize) {if (last != orig_size + 11 && last != orig_size + 10 &&!avio_feof(s->pb) &&(last != orig_size || !last) && last != flv->sum_flv_tag_size &&!flv->broken_sizes) {av_log(s, AV_LOG_ERROR, "Packet mismatch %d %d %d\n", last, orig_size + 11, flv->sum_flv_tag_size);avio_seek(s->pb, pos + 1, SEEK_SET);ret = resync(s);av_packet_unref(pkt);if (ret >= 0) {goto retry;}}}if (ret >= 0)flv->last_ts = pkt->dts;return ret;
}
- 可以看出,函数读取了FLV的文件头并且判断其中是否包含视频流和音频流。如果包含视频流或者音频流,就会调用create_stream()函数。
create_stream
- create_stream()函数定义也位于libavformat\flvdec.c中,如下所示。
static int create_stream(AVFormatContext *s)
{XCBGrabContext *c = s->priv_data;AVStream *st = avformat_new_stream(s, NULL);xcb_get_geometry_cookie_t gc;xcb_get_geometry_reply_t *geo;int64_t frame_size_bits;int ret;if (!st)return AVERROR(ENOMEM);ret = av_parse_video_rate(&st->avg_frame_rate, c->framerate);if (ret < 0)return ret;avpriv_set_pts_info(st, 64, 1, 1000000);gc = xcb_get_geometry(c->conn, c->window_id);geo = xcb_get_geometry_reply(c->conn, gc, NULL);if (!geo) {av_log(s, AV_LOG_ERROR, "Can't find window '0x%x', aborting.\n", c->window_id);return AVERROR_EXTERNAL;}if (!c->width || !c->height) {c->width = geo->width;c->height = geo->height;}if (c->x + c->width > geo->width ||c->y + c->height > geo->height) {av_log(s, AV_LOG_ERROR,"Capture area %dx%d at position %d.%d ""outside the screen size %dx%d\n",c->width, c->height,c->x, c->y,geo->width, geo->height);free(geo);return AVERROR(EINVAL);}c->time_base = (AVRational){ st->avg_frame_rate.den,st->avg_frame_rate.num };c->frame_duration = av_rescale_q(1, c->time_base, AV_TIME_BASE_Q);c->time_frame = av_gettime_relative();ret = pixfmt_from_pixmap_format(s, geo->depth, &st->codecpar->format, &c->bpp);free(geo);if (ret < 0)return ret;frame_size_bits = (int64_t)c->width * c->height * c->bpp;if (frame_size_bits / 8 + AV_INPUT_BUFFER_PADDING_SIZE > INT_MAX) {av_log(s, AV_LOG_ERROR, "Captured area is too large\n");return AVERROR_PATCHWELCOME;}c->frame_size = frame_size_bits / 8;#if CONFIG_LIBXCB_SHMc->shm_pool = av_buffer_pool_init2(c->frame_size + AV_INPUT_BUFFER_PADDING_SIZE,c->conn, allocate_shm_buffer, NULL);if (!c->shm_pool)return AVERROR(ENOMEM);
#endifst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;st->codecpar->width = c->width;st->codecpar->height = c->height;st->codecpar->bit_rate = av_rescale(frame_size_bits, st->avg_frame_rate.num, st->avg_frame_rate.den);return ret;
}
- 从代码中可以看出,create_stream()调用了API函数avformat_new_stream()创建相应的视频流和音频流。
- 上面这段解析FLV头的代码可以参考一下FLV封装格式的文件头格式,如下图所示。
- 经过上面的步骤AVInputFormat的read_header()完成了视音频流对应的AVStream的创建。至此,avformat_open_input()中的主要代码分析完毕。
补充
- 可见最终都调用了URLProtocol结构体中的函数指针。 并未得出此结论
- URLProtocol结构如下,是一大堆函数指针的集合(avio.h文件)
typedef struct URLProtocol {const char *name;int (*url_open)( URLContext *h, const char *url, int flags);/*** This callback is to be used by protocols which open further nested* protocols. options are then to be passed to ffurl_open_whitelist()* or ffurl_connect() for those nested protocols.*/int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);int (*url_accept)(URLContext *s, URLContext **c);int (*url_handshake)(URLContext *c);/*** Read data from the protocol.* If data is immediately available (even less than size), EOF is* reached or an error occurs (including EINTR), return immediately.* Otherwise:* In non-blocking mode, return AVERROR(EAGAIN) immediately.* In blocking mode, wait for data/EOF/error with a short timeout (0.1s),* and return AVERROR(EAGAIN) on timeout.* Checking interrupt_callback, looping on EINTR and EAGAIN and until* enough data has been read is left to the calling function; see* retry_transfer_wrapper in avio.c.*/int (*url_read)( URLContext *h, unsigned char *buf, int size);int (*url_write)(URLContext *h, const unsigned char *buf, int size);int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);int (*url_close)(URLContext *h);int (*url_read_pause)(URLContext *h, int pause);int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);int (*url_get_file_handle)(URLContext *h);int (*url_get_multi_file_handle)(URLContext *h, int **handles,int *numhandles);int (*url_get_short_seek)(URLContext *h);int (*url_shutdown)(URLContext *h, int flags);const AVClass *priv_data_class;int priv_data_size;int flags;int (*url_check)(URLContext *h, int mask);int (*url_open_dir)(URLContext *h);int (*url_read_dir)(URLContext *h, AVIODirEntry **next);int (*url_close_dir)(URLContext *h);int (*url_delete)(URLContext *h);int (*url_move)(URLContext *h_src, URLContext *h_dst);const char *default_whitelist;
} URLProtocol;
- URLProtocol功能就是完成各种输入协议的读写等操作
- 但输入协议种类繁多,它是怎样做到“大一统”的呢?
- 原来,每个具体的输入协议都有自己对应的URLProtocol。
- 比如file协议(FFMPEG把文件也当做一种特殊的协议)(*file.c文件)
- 可见它们把各自的函数指针都赋值给了URLProtocol结构体的函数指针
- 因此avformat_open_input只需调用url_open,url_read这些函数就可以完成各种具体输入协议的open,read等操作了