FFmpeg avformat_open_input函数分析

函数内部的总体流程如下:

avformat_open_input 精简后的代码如下:

c 复制代码
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 类似于虚函数表定义接口,由具体协议提供实现.

c 复制代码
/* 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

c 复制代码
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;
}
c 复制代码
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

c 复制代码
//主要完成底层 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;
}
c 复制代码
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);
}
c 复制代码
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;
}
相关推荐
扶尔魔ocy5 小时前
【QT window】ffmpeg实现录音功能之无损格式--PCM
ffmpeg·pcm
止礼7 小时前
FFmpeg8.0.1 源代码的深入分析
ffmpeg
小曾同学.com7 小时前
音视频中的“透传”与“DTS音频”
ffmpeg·音视频·透传·dts
vivo互联网技术7 小时前
数字人动画云端渲染方案
前端·ffmpeg·puppeteer·web3d
止礼9 小时前
FFmpeg8.0.1 编解码流程
ffmpeg
qs70169 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
止礼9 小时前
FFmpeg8.0.1 Mac环境 CMake本地调试配置
macos·ffmpeg
简鹿视频1 天前
视频转mp4格式具体作步骤
ffmpeg·php·音视频·实时音视频