/**
* 打开文件流,读取头,codecs此时并没有打开。
* 这个流必须调用avformat_close_input().
* @param ps AVFormatContext *ps = NULL ,该参数应该用(&ps)
* @param url 要打开的URL.
* @param fmt 如果不是NULL,则将打开指定的格式的媒体文件,
否则将进行自动探测文件封装格式。
* @param options 一个字典,内容填充为AVFormatContext
和demuxer-private 选项。是一个输出参数
*
* @return 成功返回0,失败返回一个负数错误码( AVERROR)
*
* @note 如果你想要自定义IO, 请预先分配 format context 同时设置 pb field
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
该函数第一步是调用avformat_alloc_context
创建一个AVFormatContext
对象,同时为这个对象进行部分初始化。
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
//存放外界传递过来的参数
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//1.创建一个`AVFormatContext`对象,同时进行部分初始化
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
//在1步对象创建的时候,已经进行赋值了。
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;
//如果指定了附加参数,则将参数复制到tmp中
if (options)
av_dict_copy(&tmp, *options, 0);
//pb是AVIOContext对象,这里如果不是null,则表示使用的是自定义IO的方式
//该值不为null,则表示*ps不是一个null对象,是提前赋值好在传递进来的。
if (s->pb)
s->flags |= AVFMT_FLAG_CUSTOM_IO;
//使用tmp里的参数对AVFormatContext *s这个对象进行赋值
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
//赋值filename里的内容到一个新的内存中,同时返回这片内存的地址
//说白了就是赋值s->url
if (!(s->url = av_strdup(filename ? filename : ""))) {
ret = AVERROR(ENOMEM);
goto fail;
}
//如果定义了FF_API_FORMAT_FILENAME,则赋值filename
#if FF_API_FORMAT_FILENAME,则赋值
FF_DISABLE_DEPRECATION_WARNINGS
av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
上面的代码是对AVFormatContext *s这个对象进行部分初始化 ,紧接着调用init_input函数打开文件:
在探究init_input之前,我们需要先了解几个准备的数据结构和准备函数:
//探测文件格式数据的结构体
typedef struct AVProbeData {
//文件名
const char *filename;
//数据域,存放用于探测的数据,这个buffer必须要额外分配32个被零填充的字节
unsigned char *buf;
//除去额外分配的字节后的buf大小
int buf_size;
//mimeType ,当时位置文件格式时值为"*/"
const char *mime_type;
}
/**
* 探测输入文件的格式信息
*/
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);
//如果探测结果大于输入的分数,则返回探测分数,同时返回探测结果
//否则,返回NULL
if (score_ret > *score_max) {
*score_max = score_ret;
return fmt;
} else
return NULL;
}
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;
//分配的额外32个字节的数据
//AVPROBE_PADDING_SIZE探测填充数据大小:32
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;
//如果lpd.buf为null,赋值额外的空数据
if (!lpd.buf)
lpd.buf = (unsigned char *) zerobuffer;
//因为Id3v2在文件的首部顺序记录10个字节的ID3V2.3的头部。
//因此当探测数据满足10字节时,判断是否满足ID3V2
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
//id3的总长度:包含了10个字节的头
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;
//实际数据大小减去id3标签长度
lpd.buf_size -= id3len;
} else if (id3len >= PROBE_BUF_MAX) {
//id3的长度大于设定的最大探测缓冲大小
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:%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) { //回调为NULL
//直接依据扩展名计算得分
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)//出现多次相同的最佳匹配,无法确定返回NULL
fmt = NULL;
} //循环结束
if (nodat == ID3_GREATER_PROBE)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;
}
//打开文件,同时同时探测文件格式
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
//初始化探测数据结构体
AVProbeData pd = { filename, NULL, 0 };
//默认探测得分:该数值为100/4 = 25;
int score = AVPROBE_SCORE_RETRY;
//判断是否是自定义IO的方式,这里我们暂时不讨论
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;
}
//如果之前指定过文件格式,并且改格式是非文件格式,则直接返回score
//如果没有指定文件格式,则自动探测文件格式。
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);
}
init_input函数还有两个很重要的没有分析:io_open和av_probe_input_buffer2。