函数内部的总体流程如下:
avformat_open_input 精简后的代码如下:
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;
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
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 = init_input(s, filename, &tmp)) < 0)
goto fail;
s->probe_score = ret;
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
if (options) {
av_dict_free(options);
*options = tmp;
}
*ps = s;
return 0;
close:
if (s->iformat->read_close)
s->iformat->read_close(s);
fail:
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;
}
主要是两个步骤:init_input 和 read_header.
read_header 会调用对应解封装器的 read_header 接口进行解析,如 mp4 则调用 mov_read_header,解析 mp4 元数据,建立索引.
init_input 分析.
这部分会涉及到以上几个主要的结构体.
AVFormatContext.pb 指向了 AVIOContext,AVIOContext 提供了统一的接口处理各种输入/输出操作,内置缓冲区.
URLContext 位于 AVIOContext 层次下,封装不同协议的实现细节,使上层 AVIOContext 可以统一访问各种输入/输出源. URLContext.prot 指向 URLProtocol,priv_data 指向具体的协议对象 (如 HTTPContext、FileContext). URLProtocol 类似于虚函数表定义接口,由具体协议提供实现.
/* 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;
if (s->pb) {//如果自定义了 AVIOContext
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)//如果没有指定 AVInputFormat,则由 ffmpeg 去解析推测
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;
//打开文件读取内容再调用 av_probe_input_buffer2 推测格式
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);
}
s->io_open 指向了 ffio_open_whitelist
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options,
const char *whitelist, const char *blacklist
)
{
URLContext *h;
int err;
*s = NULL;
//创建 URLContext 对象,调用 ffurl_connect 建立连接
err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
if (err < 0)
return err;
//创建 AVIOContext 对象并初始化
err = ffio_fdopen(s, h);
if (err < 0) {
ffurl_close(h);
return err;
}
return 0;
}
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options,
const char *whitelist, const char* blacklist,
URLContext *parent)
{
AVDictionaryEntry *e;
/************************************************
根据 filename 查找协议 (URLProtocol), 并创建 URLContext 对象,URLContext->prot 关联了 URLProtocol
如是 http 协议则返回的结果为 ff_http_protocol
const URLProtocol ff_http_protocol = {
.name = "http",
.url_open2 = http_open,
.url_accept = http_accept,
.url_handshake = http_handshake,
.url_read = http_read,
.url_write = http_write,
.url_seek = http_seek,
.url_close = http_close,
.priv_data_size = sizeof(HTTPContext),
.priv_data_class = &http_context_class
};
***************************************************/
int ret = ffurl_alloc(puc, filename, flags, int_cb);
if (ret < 0)
return ret;
//......
//建立连接,打开对应的协议,如 http_open
ret = ffurl_connect(*puc, options);
if (!ret)
return 0;
fail:
ffurl_closep(puc);
return ret;
}
ffurl_connect -> http_open -> http_open_cnx -> http_open_cnx_internal
//主要完成底层 protocol(tcp) 的生成,以及通过底层 protocol 与服务器进行握手
static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
{
const char *path, *proxy_path, *lower_proto = "tcp", *local_path;
char hostname[1024], hoststr[1024], proto[10];
char auth[1024], proxyauth[1024] = "";
char path1[MAX_URL_SIZE];
char buf[1024], urlbuf[MAX_URL_SIZE];
int port, use_proxy, err, location_changed = 0;
HTTPContext *s = h->priv_data;
av_url_split(proto, sizeof(proto), auth, sizeof(auth),
hostname, sizeof(hostname), &port,
path1, sizeof(path1), s->location);
ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
//......
//拼凑 lower protocol 字符串(buf) -> tcp://{hostname}:{port}
ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
//s: HTTPContext, s->hd: URLContext
if (!s->hd) {
//调用 ffurl_open_whitelist 生成 ff_tcp_protocol
err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,
&h->interrupt_callback, options,
h->protocol_whitelist, h->protocol_blacklist, h);
if (err < 0)
return err;
}
//进行 http 连接
//URLProtocol 对应 ff_http_protocol,URLContext->priv_data 对应 HTTPContext,
//HTTPContext 嵌套的 URLContext,其 URLProtocol 对应着 ff_tcp_protocol
err = http_connect(h, path, local_path, hoststr,
auth, proxyauth, &location_changed);
if (err < 0)
return err;
return location_changed;
}
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
uint8_t *buffer = NULL;
int buffer_size, max_packet_size;
//......
//申请一个读写缓冲 buffer
buffer = av_malloc(buffer_size);
if (!buffer)
return AVERROR(ENOMEM);
//分配 AVIOContext 对象
*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
(int (*)(void *, uint8_t *, int)) ffurl_read,
(int (*)(void *, uint8_t *, int)) ffurl_write,
(int64_t (*)(void *, int64_t, int))ffurl_seek);
if (!*s)
goto fail;
//初始化相关变量
// .......
(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
if(h->prot) {
(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;
(*s)->read_seek =
(int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
if (h->prot->url_read_seek)
(*s)->seekable |= AVIO_SEEKABLE_TIME;
}
(*s)->short_seek_get = (int (*)(void *))ffurl_get_short_seek;
(*s)->av_class = &ff_avio_class;
return 0;
fail:
av_freep(&buffer);
return AVERROR(ENOMEM);
}
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;
//......
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))) {//循环调整 probe_size
score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;
/* Read probe data. */
if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)//重新分配 buf
goto fail;
//调用 avio 接口读取数据
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);//遍历每一个注册的解封装器的 read_probe 接口探测并打分,最终选择出一个分数最高的格式
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=%d\n",
(*fmt)->name, probe_size, score);
}
}
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;
}