本文主要参考文章: blog.csdn.net/u011913612/... 但是该文章是2016年的,分析的代码已经比较老,笔者用的是"ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers"
结合笔者自己的debug过程,主要分析avformat_open_input函数的具体实现。 先上一个大致的函数调用流程图。
其源代码如下:
c
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 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 ((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;
} else
av_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;
}
从上图可以大致看到"avformat_open_input"函数在ffplay中的整体位置。
这个函数的作用是:打开一个输入流并且读它的头部信息,编解码器不会被打开,这个方法调用结束后,一定要调用avformat_close_input()关闭流。
使用要这个函数要注意一点:如果fmt参数非空,也就是人为的指定了一个AVInputFormat的实例,那么这个函数就不会再检测输入文件的格式了,相反,如果fmt为空,那么这个函数就会自动探测输入文件的格式等信息。
从源码来看,这个函数主要做了下面几件事:
1.分配一个AVFormatContext的实例。 这是通过第10行"if (!s && !(s = avformat_alloc_context()))" 完成,。但是如果入参中s已经实例化,那么这里是不会执行的。从流程图可以看到,在read_thread的第三步中,它已经完成了"avformat_alloc_context"的调用。(笔者是ffplay一个本地的mp4文件,从debug看,入参已经被实例化。)
2.调用init_input函数初始化输入流的信息。这里会初始化AVInputFormat。
3.根据上一步初始化好的AVInputFormat的类型,调用它的read_header方法,读取文件头。
它最终的目的,是为了完成下面一些数据结构的初始化:
【1】avformat_alloc_context的实现
作用
AVFormatContext *avformat_alloc_context(void)
{
FFFormatContext *const si = av_mallocz(sizeof(*si));
AVFormatContext *s;
if (!si)
return NULL;
s = &si->pub;
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
#if FF_API_AVFORMAT_IO_CLOSE
FF_DISABLE_DEPRECATION_WARNINGS
s->io_close = ff_format_io_close_default;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
s->io_close2= io_close2_default;
av_opt_set_defaults(s);
si->pkt = av_packet_alloc();
si->parse_pkt = av_packet_alloc();
if (!si->pkt || !si->parse_pkt) {
avformat_free_context(s);
return NULL;
}
#if FF_API_LAVF_SHORTEST
si->shortest_end = AV_NOPTS_VALUE;
#endif
return s;
}
它主要完成av_class的初始化,以及io_open 和io_close赋值,它们分别会用在第二步的init_input中,结合流程图可以看到io_open调用的地方。
总的来说,"avformat_alloc_context"的主要作用就是,创建AVFormatContext结构体并初始化它的AVClass,AVOption成员。再给它的io_open赋值为io_open_default 函数,io_close 赋值为io_close_default函数。构建pkt,parse_pkt
【2】init_input函数 它有两个功能:打开输入文件;探测输入文件的格式;
io_open -> io_open_default -> ffio_open_whitelist -> ffurl_open_whitelist -> ffurl_alloc(url_alloc_for_protocol) -> ffurl_connect -> file_open -> avpriv_open-> open
到了这里,调用open打开文件后返回文件描述符,并把得到的文件描述符赋值给FileContext结构体的fd成员。 返回到ffio_open_whitelist之后,继续调用ffio_fdopen去做如下事情:
- 分配一快buffer内存,其大小有这里定义:#define IO_BUFFER_SIZE 32768,buffer的大小本来取决于URLContext的max_packet_size,前面在url_alloc_for_protocol函数中初始化URLContext的时候将其初始化为0了,因此,这里分配的大小为32768。
- 分配一个AVIOContext结构体,这个结构体也很重要,分配完以后我们就有AVIOContext结构体的实例了,分配完成后做了一些初始化工作。
这要注意seekable 是AVIO_SEEKABLE_NORMAL,因为is_streamed 为0,这在url_alloc_for_protocol函数中初始化URLContext的时候将其初始化。
打开输入文件之后,我们就需要探测文件的格式,它是通过函数av_probe_input_buffer2来实现的。它会不断的读文件,然后分析,直到确定了文件的格式位置;这个函数的for循环每次读PROBE_BUF_MIN个字节的内容,其实就是2048了,然后调用av_probe_input_format2函数来猜测文件的格式。 av_probe_input_buffer2 -> av_probe_input_format2 -> av_probe_input_format3.
"av_probe_input_format3"会根据文件名的后缀,遍历AVInputFormat的全局链表,比如我们输入的xxx.mp4,那么匹配到ff_mov_demuxer
c
const AVInputFormat ff_mov_demuxer = {
.name = "mov,mp4,m4a,3gp,3g2,mj2",
.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
.priv_class = &mov_class,
.priv_data_size = sizeof(MOVContext),
.extensions = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif",
.flags_internal = FF_FMT_INIT_CLEANUP,
.read_probe = mov_probe,
.read_header = mov_read_header,
.read_packet = mov_read_packet,
.read_close = mov_read_close,
.read_seek = mov_read_seek,
.flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS | AVFMT_SHOW_IDS,
};
【Q1】AVInputFormat的全局链表是什么?作用? 【A1】xxxxx
找到对应的AVInputFormat的实例后,我们的AVFormatContext中的iformat成员就有内容了。这样AVFormatContext中的初始化就完成了:
c
const AVClass *av_class;
struct AVInputFormat *iformat;
void *priv_data;
AVIOContext *pb;
【3】read_header解析文件 ff_mov_demuxer中定义的read_header是mov_read_header 【TBD】