FFmpeg avformat_open_input函数分析
函数内部的总体流程如下:
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 fails->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) {//如果自定义了 AVIOContexts->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_protocolconst 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_openret = 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: URLContextif (!s->hd) {//调用 ffurl_open_whitelist 生成 ff_tcp_protocolerr = 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_protocolerr = 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;//......//申请一个读写缓冲 bufferbuffer = 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_sizescore = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;/* Read probe data. */if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)//重新分配 bufgoto 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);} elseav_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;
}