我是靠谱客的博主 要减肥豌豆,这篇文章主要介绍ffmpeg源码分析:avformat_open_input()一、avformat_alloc_context()二、init_input()三、read_header(),现在分享给大家,希望可以做个参考。

目录

一、avformat_alloc_context()

二、init_input()

2.1 av_probe_input_format2()

2.2 av_probe_input_buffer2()

2.3 io_open

三、read_header()


      本文简单分析FFmpeg中一个常用的函数:avformat_open_input()。该函数用于打开多媒体数据并且获得一些相关的信息。它的声明位于libavformatavformat.h,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** * Open an input stream and read the header. The codecs are not opened. * The stream must be closed with avformat_close_input(). * * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context). * May be a pointer to NULL, in which case an AVFormatContext is allocated by this * function and written into ps. * Note that a user-supplied AVFormatContext will be freed on failure. * @param url URL of the stream to open. * @param fmt If non-NULL, this parameter forces a specific input format. * Otherwise the format is autodetected. * @param options A dictionary filled with AVFormatContext and demuxer-private options. * On return this parameter will be destroyed and replaced with a dict containing * options that were not found. May be NULL. * * @return 0 on success, a negative AVERROR on failure. * * @note If you want to use custom IO, preallocate the format context and set its pb field. */ int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

     @ ps:函数调用成功之后处理过的AVFormatContext结构体。
     @ file:打开的视音频流的URL。 
     @ fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。
     @ dictionay:附加的一些选项,一般情况下可以设置为NULL。

下面看一下的定义,位于libavformatutils.c中,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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; 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 eithern"); return AVERROR(EINVAL); } if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, *options, 0); if (s->pb) // must be before any goto fail s->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 FF_API_FORMAT_FILENAME FF_DISABLE_DEPRECATION_WARNINGS av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename)); FF_ENABLE_DEPRECATION_WARNINGS #endif 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 a AVIOContext */ if (s->pb) ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((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) { 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) { ret = AVERROR_INVALIDDATA; goto close; } } 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; } else av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skippingn"); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto close; 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; 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指针没有初始化过,就调用avformat_alloc_context()初始化该结构体;还有一部分是针对一些格式做的特殊处理,比如id3v2信息的处理等等。有关上述两种信息不再详细分析,在这里只选择它关键的两个函数进行分析:
     (1) avformat_alloc_context:申请并简单初始化AVFormatContext结构体
     (2) init_input():绝大部分初始化工作都是在这里做的。
     (3) s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream。

一、avformat_alloc_context()

    这个再前面已经总结过,现在《ffmpeg结构体(2)之AVFormatContext及其相关函数》不在做赘述。

二、init_input()

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 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; /*情况1: 使用了自定义的AVIOContext,AVIOContext结构体pb存在 */ if (s->pb) { s->flags |= AVFMT_FLAG_CUSTOM_IO; /* 如果指定了AVInputFormat *iformat,直接返回。没有指定,执行下面 *av_probe_input_buffer2函数.*/ 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; } /*情况2:自定义AVIOContext结构体pb不存在,且指定了AVInputFormat *iformat,直接返回。 *没有指定iformat,则调用av_probe_input_format2函数进行推测匹配*/ if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) return score; /* 情况3:没有指定iformat,且av_probe_input_format2函数推断不出来 */ 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,我们可以看一下它的定义:

复制代码
1
  #define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)

其中AVPROBE_SCORE_MAX是score的最大值,取值是100:

复制代码
1
#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_format2(NULL,…)根据文件路径判断文件格式。
   (3)如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个时候会首先调用AVFormatContext中的io_open()打开文件,然后调用av_probe_input_buffer2()推测AVInputFormat。

2.1 av_probe_input_format2()

av_probe_input_format2()是一个API函数,声明位于libavformatavformat.h,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
/**  * Guess the file format.  *  * @param pd        data to be probed  * @param is_opened Whether the file is already opened; determines whether  *                  demuxers with or without AVFMT_NOFILE are probed.  * @param score_max A probe score larger that this is required to accept a  *                  detection, the variable is set to the actual detection  *                  score afterwards.  *                  If the score is <= AVPROBE_SCORE_MAX / 4 it is recommended  *                  to retry with a larger probe buffer.  */ ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max);

该函数用于根据输入数据查找合适的AVInputFormat。参数含义如下所示:
      @ pd:存储输入数据信息的AVProbeData结构体。
      @ is_opened:文件是否打开。
      @ score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。
该函数中涉及到一个结构体AVProbeData,用于存储输入文件的一些信息,它的定义如下所示。

复制代码
1
2
3
4
5
6
7
8
9
/**  * 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;

av_probe_input_format2()函数的定义位于libavformatformat.c,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max) {     int score_ret;     ff_const59 AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);     if (score_ret > *score_max) {         *score_max = score_ret;         return fmt;     } else         return NULL; }

     从函数中可以看出,av_probe_input_format2()调用了av_probe_input_format3(),并且增加了一个判断,当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。
下面我们看一下av_probe_input_format3()。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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; if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) { int id3len = ff_id3v2_tag_len(lpd.buf); if (lpd.buf_size > id3len + 16) { if (lpd.buf_size < 2LL*id3len + 16) nodat = ID3_ALMOST_GREATER_PROBE; lpd.buf += id3len; lpd.buf_size -= id3len; } else if (id3len >= PROBE_BUF_MAX) { nodat = ID3_GREATER_MAX_PROBE; } else nodat = ID3_GREATER_PROBE; } while ((fmt1 = av_demuxer_iterate(&i))) { 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:%dn", 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 typen", 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()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中。前文已经提到过,AVProbeData定义如下。

复制代码
1
2
3
4
5
6
7
8
9
/** * 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;

     @ filename:文件路径;
     @ buf:存储用于推测AVInputFormat的媒体数据,其中buf可以为空,但是其后面无论如何都需要填充AVPROBE_PADDING_SIZE个0(AVPROBE_PADDING_SIZE取值为32,即32个0)。
     @ mime_type: 保存媒体的类型
该函数最主要的部分是一个循环。该循环调用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。

上述过程中涉及到以下几个知识点:

2.1.1 AVInputFormat->read_probe()

      AVInputFormat中包含read_probe()是用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。例如,FLV封装格式的AVInputFormat模块定义(位于libavformatflvdec.c)如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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_class, };

其中,read_probe()函数对应的是flv_probe()函数。我们可以看一下flv_probe()函数的定义:

复制代码
1
2
3
4
static int flv_probe(const AVProbeData *p) { return probe(p, 0); }

可见flv_probe()调用了一个probe()函数。probe()函数的定义如下。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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。

2.1.2 av_match_name()

     av_match_name()是一个API函数,声明位于libavutilavstring.h,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
/** * Match instances of a name in a comma-separated list of names. * List entries are checked from the start to the end of the names list, * the first match ends further processing. If an entry prefixed with '-' * matches, then 0 is returned. The "ALL" list entry is considered to * match all names. * * @param name Name to look for. * @param names List of names. * @return 1 on match, 0 otherwise. */ int av_match_name(const char *name, const char *names);

     av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)的定义如下。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
AVInputFormat ff_matroska_demuxer = { .name = "matroska,webm", .long_name = NULL_IF_CONFIG_SMALL("Matroska / WebM"), .extensions = "mkv,mk3d,mka,mks", .priv_data_size = sizeof(MatroskaDemuxContext), .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()函数对于这样的字符串,会把它按照“,”截断成一个个的名称,然后一一进行比较。

2.1.3 av_match_ext()

    av_match_ext()是一个API函数,声明位于libavformatavformat.h(注意位置和av_match_name()不一样),如下所示。

复制代码
1
2
3
4
5
6
7
8
/** * Return a positive value if the given filename has one of the given * extensions, 0 otherwise. * * @param filename file name to check against the given extensions * @param extensions a comma-separated list of filename extensions */ int av_match_ext(const char *filename, const char *extensions);

     av_match_ext()用于比较文件的后缀。该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** * @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; }

2.2 av_probe_input_buffer2()

    av_probe_input_buffer2()是一个API函数,它根据输入的媒体数据推测该媒体数据的AVInputFormat,声明位于libavformatavformat.h,如下所示。

复制代码
1
2
3
4
5
6
7
8
/** * Guess the file format. * * @param is_opened Whether the file is already opened; determines whether * demuxers with or without AVFMT_NOFILE are probed. * @param score_ret The score of the best detection. */ ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened, int *score_ret);

      从函数声明中可以看出,av_probe_input_format3()和av_probe_input_format2()的区别是函数的第3个参数不同:av_probe_input_format2()是一个分数的门限值,而av_probe_input_format3()是一个探测后的最匹配的格式的分数值。
av_probe_input_format3()的定义位于libavformatformat.c,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** * 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, ff_const59 AVInputFormat **fmt, const char *url, void *logctx, unsigned int offset, unsigned int max_probe_size);

av_probe_input_buffer2()参数的含义如下所示:
    @ pb:用于读取数据的AVIOContext。
    @ fmt:输出推测出来的AVInputFormat。
    @ filename:输入媒体的路径。
    @ logctx:日志(没有研究过)。
    @ offset:开始推测AVInputFormat的偏移量。
    @ max_probe_size:用于推测格式的媒体数据的最大值。
返回推测后的得到的AVInputFormat的匹配分数。
av_probe_input_buffer2()的定义位于libavformatformat.c,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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 < %un", 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 = ''; } } 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); } else av_log(logctx, AV_LOG_DEBUG, "Format %s probed with size=%d and score=%dn", (*fmt)->name, probe_size, score); #if 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%sn", 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,则会退出循环并且返回错误信息。

2.3 io_open

在申请AVFormatContext的借口中会设置io_open。avformat_alloc_context()->avformat_get_context_defaults()。
libavformatoptions.c

复制代码
1
2
3
4
5
6
7
8
9
10
11
static void avformat_get_context_defaults(AVFormatContext *s) { memset(s, 0, sizeof(AVFormatContext)); s->av_class = &av_format_context_class; s->io_open = io_open_default; s->io_close = io_close_default; av_opt_set_defaults(s); }

由上述代码可以看出io_open被赋值为io_open_default()。
libavformatoptions.c

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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; } else loglevel = AV_LOG_INFO; av_log(s, loglevel, "Opening '%s' for %sn", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading"); #if FF_API_OLD_OPEN_CALLBACKS FF_DISABLE_DEPRECATION_WARNINGS if (s->open_cb) return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options); FF_ENABLE_DEPRECATION_WARNINGS #endif return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist); }

     io_open_default最后调用了ffio_open_whitelist()函数,而ffio_open_whitelist函数我们在分析avio_open2()是否分析过,详见xxx。

三、read_header()

     在调用完init_input()完成基本的初始化并且推测得到相应的AVInputFormat之后,avformat_open_input()会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。read_header()是一个位于AVInputFormat结构体中的一个函数指针,对于不同的封装格式,会调用不同的read_header()的实现函数。举个例子,当输入视频的封装格式为FLV的时候,会调用FLV的AVInputFormat中的read_header()。FLV的AVInputFormat定义位于libavformatflvdec.c文件中,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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_class, };

    可以看出read_header()指向了flv_read_header()函数。flv_read_header()的实现同样位于libavformatflvdec.c文件中,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static int flv_read_header(AVFormatContext *s) { int flags; FLVContext *flv = s->priv_data; int offset; int pre_tag_size = 0; /* Actual FLV data at 0xe40000 in KUX file */ if(!strcmp(s->iformat->name, "kux")) avio_skip(s->pb, 0xe40000); avio_skip(s->pb, 4); flags = avio_r8(s->pb); flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO); s->ctx_flags |= AVFMTCTX_NOHEADER; offset = avio_rb32(s->pb); avio_seek(s->pb, offset, SEEK_SET); /* 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 0n"); } s->start_time = 0; flv->sum_flv_tag_size = 0; flv->last_keyframe_stream_index = -1; return 0; }

     可以看出,函数读取了FLV的文件头并且判断其中是否包含视频流和音频流。如果包含视频流或者音频流,就会设置标记位,在后面适当的时候调用create_stream创建视频流和音频流。

复制代码
1
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);

     create_stream()函数定义也位于libavformatflvdec.c中,如下所示。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static AVStream *create_stream(AVFormatContext *s, int codec_type) { FLVContext *flv = s->priv_data; AVStream *st = avformat_new_stream(s, NULL); if (!st) return NULL; st->codecpar->codec_type = codec_type; if (s->nb_streams>=3 ||( s->nb_streams==2 && s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && s->streams[1]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_DATA && s->streams[1]->codecpar->codec_type != AVMEDIA_TYPE_DATA)) s->ctx_flags &= ~AVFMTCTX_NOHEADER; if (codec_type == AVMEDIA_TYPE_AUDIO) { st->codecpar->bit_rate = flv->audio_bit_rate; flv->missing_streams &= ~FLV_HEADER_FLAG_HASAUDIO; } if (codec_type == AVMEDIA_TYPE_VIDEO) { st->codecpar->bit_rate = flv->video_bit_rate; flv->missing_streams &= ~FLV_HEADER_FLAG_HASVIDEO; st->avg_frame_rate = flv->framerate; } avpriv_set_pts_info(st, 32, 1, 1000); /* 32 bit pts in ms */ flv->last_keyframe_stream_index = s->nb_streams - 1; add_keyframes_index(s); return st; }

 

参考文献:
https://blog.csdn.net/leixiaohua1020/article/details/44064715
https://blog.csdn.net/u011913612/article/details/53482773

 

 

 

最后

以上就是要减肥豌豆最近收集整理的关于ffmpeg源码分析:avformat_open_input()一、avformat_alloc_context()二、init_input()三、read_header()的全部内容,更多相关ffmpeg源码分析内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(85)

评论列表共有 0 条评论

立即
投稿
返回
顶部