FFmpeg中的关键方法及结构体(二)avformat_open_input


avformat_open_input

该方法声明在libavformat/avformat.h:2093

int avformat_open_input(AVFormatContext **ps, const char *url,
                        const AVInputFormat *fmt, AVDictionary **options);

方法实现位于libavformat/demux.c:207,该方法主要用来选择IO以及解复用组件

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;

// 如果传入得AVFormatContext对象为空则在内部创建一个
if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM);
// 通过强转来获得一个FFFormatContext类型的指针,记录了AVFormatContext得状态信息 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); }
// 是否指定AVInputFormat,如果指定的话就使用指定的Format
if (fmt) s->iformat = fmt;
// 如果有参数选项传递则拷贝选项
if (options) av_dict_copy(&tmp, *options, 0);
// 这个pb的类型为AVIOContext,pb不为null表示使用自定义的IO
if (s->pb) // must be before any goto fail s->flags |= AVFMT_FLAG_CUSTOM_IO;
// 将参数设置给AVFormatContext
if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail;    // 拷贝url到AVFormatContext中 if (!(s->url = av_strdup(filename ? filename : ""))) { ret = AVERROR(ENOMEM); goto fail; } // 调用init_input方法来初始化,主要做了两件事选择AVIOContext、选择AVInputFormat if ((ret = init_input(s, filename, &tmp)) < 0) goto fail;
   // 探测得分记录到AVFormatContext得probe_score当中 s
->probe_score = ret;
// 拷贝白名单到AVFormatContext得protocol_whitelist中
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; } } // 拷贝黑名单到AVFormatContext得protocol_blacklist中 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; }
// 设置AVIOContext内部缓冲区跳过初始字节 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; } }
// 初始化文件的时长和起始时间为AV_NOPTS_VALUE 0x8000000000000000 s
->duration = s->start_time = AV_NOPTS_VALUE;
// 如果AVInputFormat带有private data,那么为AVFormatContext的priv_data分配内存
/* 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) {
// 将AVInputFormat的priv_class拷贝给AVFormatContext的priv_data
*(const AVClass **) s->priv_data = s->iformat->priv_class; // 初始化AVFormatCOntext私有数据的其他成员
       av_opt_set_defaults(s
->priv_data);
// 给私有数据设置用户选项
if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0) goto fail; } }
// 如果创建了AVIOContext,读取id3信息
/* 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);
// 如果AVInputFormat的read_header方法不为NULL,那么就去调用read_header读取header中的内容
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; }
// 处理meta与id3信息
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;
// 如果AVIOContext不为NULL,并且FFFormatContext记录的AVFormatContext数据读取偏移量为0,则记录下当前AVIOContext写入数据偏移量到FFFormatContext中
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; }

init_input

该方法实现位于libavformat/demux.c:150,在avformat_open_input中被调用用于找到合适的IO组件以及合适的demux组件

static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
// 创建一个AVProbeData AVProbeData pd
= { filename, NULL, 0 }; int score = AVPROBE_SCORE_RETRY;
// 默认不使用自己的IO
if (s->pb) {
     // 给AVFormatContext添加flag,表示IO由用户指定 s
->flags |= AVFMT_FLAG_CUSTOM_IO;
// 如果AVInputFormat为NULL则调用方法去探测
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; }
// 如果AVInputFormat不为空并且标志位NOFILE,或者为空并且找到了合适的demux组件,那么这里直接返回score
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) return score;
// 否则先调用io_open查找IO组件,io_open的实现在libavformat/options.c中,pb的类型为AVIOContext
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0) return ret; if (s->iformat) return 0;
   // 调用av_probe_input_buffer2去探测IO类型
return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize); }

io_open

该方法的实现位于libavformat/options.c,方法实现为io_open_default,用于创建并初始化AVFormatContext中的AVIOContext

static int io_open_default(AVFormatContext *s, AVIOContext **pb,
                           const char *url, int flags, AVDictionary **options)
{
    int loglevel;

    if (!strcmp(url, s->url) ||
        s->iformat && !strcmp(s->iformat->name, "image2") ||
        s->oformat && !strcmp(s->oformat->name, "image2")
    ) {
        loglevel = AV_LOG_DEBUG;
    } else
        loglevel = AV_LOG_INFO;

    av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");
    // 这里的url就是前面的filename
    return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}

ffio_open_whitelist声明在libavformat/avio_internal.h:225,实现位于libavformat/aviobuf.c:1225

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
    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
    if (err < 0)
        return err;
// 用URLContext来创建并初始化一个AVIOContext err
= ffio_fdopen(s, h); if (err < 0) { ffurl_close(h); return err; } return 0; }

ffurl_open_whitelist实现位于libavformat/avio.c:306

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)
{
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;
// 创建并且初始化一个URLContext对象
int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; if (parent) { ret = av_opt_copy(*puc, parent); if (ret < 0) goto fail; } if (options && (ret = av_opt_set_dict(*puc, options)) < 0) goto fail; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; if (!options) options = &tmp_opts; av_assert0(!whitelist || !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) || !strcmp(whitelist, e->value)); av_assert0(!blacklist || !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) || !strcmp(blacklist, e->value)); if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0) goto fail; if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0) goto fail; if ((ret = av_opt_set_dict(*puc, options)) < 0) goto fail;
// 调用ffurl_connect,打开url ret
= ffurl_connect(*puc, options); if (!ret) return 0; fail: ffurl_closep(puc); return ret; }

ffurl_alloc实现在libavformat/avio.c:293

int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    const URLProtocol *p = NULL;
    // 找到合适的protocol
    p = url_find_protocol(filename);
// 利用找到的protocol来创建一个URLContext
if (p) return url_alloc_for_protocol(puc, p, filename, flags, int_cb); *puc = NULL; return AVERROR_PROTOCOL_NOT_FOUND; }

url_find_protocol实现位于libavformat/avio.c:251

static const struct URLProtocol *url_find_protocol(const char *filename)
{
    const URLProtocol **protocols;
    char proto_str[128], proto_nested[128], *ptr;
// 这个方法比较有意思,是用于查找filename中第一个没有出现在URL_SCHEME_CHARS中的字符的下表,起始就是查找":" size_t proto_len
= strspn(filename, URL_SCHEME_CHARS); int i;
// 如果filename中没有":",并且以subfile开头或者没有":",或者是路径,protocol类型为file
if (filename[proto_len] != ':' && (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) || is_dos_path(filename)) strcpy(proto_str, "file"); else av_strlcpy(proto_str, filename, FFMIN(proto_len + 1, sizeof(proto_str))); av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); if ((ptr = strchr(proto_nested, '+'))) *ptr = '\0';
// 获取所有的protocal protocols
= ffurl_get_protocols(NULL, NULL); if (!protocols) return NULL;
// 遍历protocals列表,对比protocol name
for (i = 0; protocols[i]; i++) { const URLProtocol *up = protocols[i]; if (!strcmp(proto_str, up->name)) { av_freep(&protocols); return up; } if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) { av_freep(&protocols); return up; } }
// 释放protocols指针 av_freep(
&protocols); if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL)) av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with " "openssl, gnutls or securetransport enabled.\n"); return NULL; }

ffurl_get_protocols实现位于libavformat/protocols.c

const URLProtocol **ffurl_get_protocols(const char *whitelist,
                                        const char *blacklist)
{
    const URLProtocol **ret;
    int i, ret_idx = 0;
   // 开辟一块内存空间
    ret = av_calloc(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));
    if (!ret)
        return NULL;

    for (i = 0; url_protocols[i]; i++) {
        const URLProtocol *up = url_protocols[i];

        if (whitelist && *whitelist && !av_match_name(up->name, whitelist))
            continue;
        if (blacklist && *blacklist && av_match_name(up->name, blacklist))
            continue;
     // 将url_protocals中的地址拷贝到返回值中,它这里做拷贝主要是为了使用白名单和黑名单的筛选功能
        ret[ret_idx++] = up;
    }

    return ret;
}

url_protocols定义在libavformat/protocol_list.c,是一个二级指针,保存着各个protocol的地址

static const URLProtocol * const url_protocols[] = {
    &ff_async_protocol,
    &ff_cache_protocol,
    &ff_concat_protocol,
    &ff_concatf_protocol,
    &ff_crypto_protocol,
    &ff_data_protocol,
    &ff_ffrtmphttp_protocol,
    &ff_file_protocol,
    &ff_ftp_protocol,
    &ff_gopher_protocol,
    &ff_hls_protocol,
    &ff_http_protocol,
    &ff_httpproxy_protocol,
    &ff_icecast_protocol,
    &ff_mmsh_protocol,
    &ff_mmst_protocol,
    &ff_md5_protocol,
    &ff_pipe_protocol,
    &ff_prompeg_protocol,
    &ff_rtmp_protocol,
    &ff_rtmpt_protocol,
    &ff_rtp_protocol,
    &ff_srtp_protocol,
    &ff_subfile_protocol,
    &ff_tee_protocol,
    &ff_tcp_protocol,
    &ff_udp_protocol,
    &ff_udplite_protocol,
    &ff_unix_protocol,
    NULL };

下面以 ff_hls_protocol 和 ff_file_protocol举个例子

URLProtocol这个结构体中有很多函数指针,用于实现多态,在这里不同的protocol会为结构体中的函数指针赋不同的值

ff_hls_protocol 定义在libavformat/hlsproto.c:311

const URLProtocol ff_hls_protocol = {
    .name           = "hls",
    .url_open       = hls_open,
    .url_read       = hls_read,
    .url_close      = hls_close,
    .flags          = URL_PROTOCOL_FLAG_NESTED_SCHEME,
    .priv_data_size = sizeof(HLSContext),
};

ff_file_protocol定义在libavformat/file.c:357

const URLProtocol ff_file_protocol = {
    .name                = "file",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_get_file_handle = file_get_handle,
    .url_check           = file_check,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
    .default_whitelist   = "file,crypto,data"
};

再回到ffurl_alloc,找到合适的protocol之后,调用url_alloc_for_protocol来创建URLContext

url_alloc_for_protocol实现位于libavformat/avio.c:74

URLContext这个上下文保存有URL相关信息,并保存了获取到的URLProtocol

static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                  const char *filename, int flags,
                                  const AVIOInterruptCB *int_cb)
{
    URLContext *uc;
    int err;

#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
        return AVERROR(EIO);
#endif
    if ((flags & AVIO_FLAG_READ) && !up->url_read) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for reading\n", up->name);
        return AVERROR(EIO);
    }
    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for writing\n", up->name);
        return AVERROR(EIO);
    }
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    if (!uc) {
        err = AVERROR(ENOMEM);
        goto fail;
    }
    uc->av_class = &ffurl_context_class;
    uc->filename = (char *)&uc[1];
    strcpy(uc->filename, filename);
    uc->prot            = up;
    uc->flags           = flags;
    uc->is_streamed     = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
    if (up->priv_data_size) {
        uc->priv_data = av_mallocz(up->priv_data_size);
        if (!uc->priv_data) {
            err = AVERROR(ENOMEM);
            goto fail;
        }
        if (up->priv_data_class) {
            char *start;
            *(const AVClass **)uc->priv_data = up->priv_data_class;
            av_opt_set_defaults(uc->priv_data);
            if (av_strstart(uc->filename, up->name, (const char**)&start) && *start == ',') {
                int ret= 0;
                char *p= start;
                char sep= *++p;
                char *key, *val;
                p++;

                if (strcmp(up->name, "subfile"))
                    ret = AVERROR(EINVAL);

                while(ret >= 0 && (key= strchr(p, sep)) && p1, sep))){
                    *val= *key= 0;
                    if (strcmp(p, "start") && strcmp(p, "end")) {
                        ret = AVERROR_OPTION_NOT_FOUND;
                    } else
                        ret= av_opt_set(uc->priv_data, p, key+1, 0);
                    if (ret == AVERROR_OPTION_NOT_FOUND)
                        av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);
                    *val= *key= sep;
                    p= val+1;
                }
                if(ret<0 || p!=key){
                    av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);
                    av_freep(&uc->priv_data);
                    av_freep(&uc);
                    err = AVERROR(EINVAL);
                    goto fail;
                }
                memmove(start, key+1, strlen(key));
            }
        }
    }
    if (int_cb)
        uc->interrupt_callback = *int_cb;

    *puc = uc;
    return 0;
fail:
    *puc = NULL;
    if (uc)
        av_freep(&uc->priv_data);
    av_freep(&uc);
#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
        ff_network_close();
#endif
    return err;
}

再回到ffurl_open_whitelist中来,调用ffurl_alloc获取到URLContext之后会调用ffurl_connect来打开url

ffurl_connect实现位于libavformat/avio.c:166

int ffurl_connect(URLContext *uc, AVDictionary **options)
{
    int err;
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;

    if (!options)
        options = &tmp_opts;

    // Check that URLContext was initialized correctly and lists are matching if set
    av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
               (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));
    av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
               (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));

    if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {
        av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);
        return AVERROR(EINVAL);
    }

    if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {
        av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);
        return AVERROR(EINVAL);
    }

    if (!uc->protocol_whitelist && uc->prot->default_whitelist) {
        av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);
        uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);
        if (!uc->protocol_whitelist) {
            return AVERROR(ENOMEM);
        }
    } else if (!uc->protocol_whitelist)
        av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelist

    if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)
        return err;
    if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)
        return err;
  
  // 调用URLContext的prot url_open方法,如果有url_open2就调用,没有就调用url_open,该方法定义在URLProtocol中,不同的protocol会有不同的实现 err
= uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) : uc->prot->url_open(uc, uc->filename, uc->flags); av_dict_set(options, "protocol_whitelist", NULL, 0); av_dict_set(options, "protocol_blacklist", NULL, 0); if (err) return err;
// 将URLContext中的连接flag置1 uc
->is_connected = 1; /* We must be careful here as ffurl_seek() could be slow, * for example for http */ if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file")) if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0) uc->is_streamed = 1; return 0; }

下面以ff_file_protocol的file_open为例子,这是用来打开本地文件的protocol

方法实现位于libavformat/file.c:208

static int file_open(URLContext *h, const char *filename, int flags)
{
// URLContext中还有一个FileContext,用于保存和文件相关的内容 FileContext
*c = h->priv_data; int access; int fd; struct stat st; av_strstart(filename, "file:", &filename); if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) { access = O_CREAT | O_RDWR; if (c->trunc) access |= O_TRUNC; } else if (flags & AVIO_FLAG_WRITE) { access = O_CREAT | O_WRONLY; if (c->trunc) access |= O_TRUNC; } else { access = O_RDONLY; } #ifdef O_BINARY access |= O_BINARY; #endif // 调用系统函数open来打开文件,获取文件描述符
fd
= avpriv_open(filename, access, 0666); if (fd == -1) return AVERROR(errno);
// 将文件描述符保存到FileContext中 c
->fd = fd; h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode); /* Buffer writes more than the default 32k to improve throughput especially * with networked file systems */ if (!h->is_streamed && flags & AVIO_FLAG_WRITE) h->min_packet_size = h->max_packet_size = 262144; if (c->seekable >= 0) h->is_streamed = !c->seekable; return 0; }

av_probe_input_buffer2

我们再回到init_input中,调用io_open获取到AVIOContext之后,会再调用av_probe_input_buffer2来做探测

实现位于libavformat/format.c:225,这里开始就是来探测demux的组件了,也就是AVInputFomat

int av_probe_input_buffer2(AVIOContext *pb, const 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;

// 检查是否设定最大探测长度,如果没有定义则使用PROBE_BUF_MAX (1 << 20)
if (!max_probe_size) max_probe_size = PROBE_BUF_MAX; else if (max_probe_size < PROBE_BUF_MIN) { // 探测值不能小于 2048 av_log(logctx, AV_LOG_ERROR, "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN); return AVERROR(EINVAL); }
// 检查探测的初始便宜量是否合法
if (offset >= max_probe_size) return AVERROR(EINVAL); if (pb->av_class) { uint8_t *mime_type_opt = NULL; char *semi;
// 从AVIOContext中搜索mime_type av_opt_get(pb,
"mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt); pd.mime_type = (const char *)mime_type_opt;
// 选项信息中存在多少个,以分号分隔,截取第一个 semi
= pd.mime_type ? strchr(pd.mime_type, ';') : NULL; if (semi) { *semi = '\0'; } }
// probe_size至少读2048,直到到达上限或者AVInputFormat被找到
for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt; // probe_size x 2
     probe_size
= FFMIN(probe_size << 1, FFMAX(max_probe_size, probe_size + 1))) {
// 设置score为25 score
= probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0; /* Read probe data. */
     // 重新分配一块 probe_size + padding_size大小的buf,缓冲区中仍然保存有上次读取的数据
if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) goto fail;
// 调用avio_read来读取buffer,存储到从buf_offset开始,大小为probe_size - buf_offset的数据
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中数据的长度 buf_offset
+= ret;
// 如果数据没有到达offset的位置
if (buf_offset < offset) continue;
// 将读到的数据封装到AVProbeData中 pd.buf_size
= buf_offset - offset; pd.buf = &buf[offset]; // 多余的padding_size设置为0 memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE); /* Guess file format. */ // 调用该方法来创建一个最为合适的 AVInputFormat
*fmt = av_probe_input_format2(&pd, 1, &score); 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 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename); fclose(f); #endif } } if (!*fmt) ret = AVERROR_INVALIDDATA; fail: /* Rewind. Reuse probe buffer to avoid seeking. */
// 如果找到了合适的demux组件,那么让AVIOContext内部的缓冲区归位,保留文件探测过程中读取到的数据
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; }

avio_read实现位于libavformat/aviobuf.c:641

int avio_read(AVIOContext *s, unsigned char *buf, int size)
{
    int len, size1;

    size1 = size;
    while (size > 0) {
        len = FFMIN(s->buf_end - s->buf_ptr, size);
        if (len == 0 || s->write_flag) {
            if((s->direct || size > s->buffer_size) && !s->update_checksum && s->read_packet) {
                // bypass the buffer and read data directly into buf
                // 调用AVIOContext的read_packet方法读取数据
len = read_packet_wrapper(s, buf, size); if (len == AVERROR_EOF) { /* do not modify buffer if EOF reached so that a seek back can be done without rereading data */ s->eof_reached = 1; break; } else if (len < 0) { s->eof_reached = 1; s->error= len; break; } else { s->pos += len; ffiocontext(s)->bytes_read += len; s->bytes_read = ffiocontext(s)->bytes_read; size -= len; buf += len; // reset the buffer s->buf_ptr = s->buffer; s->buf_end = s->buffer/* + len*/; } } else { fill_buffer(s); len = s->buf_end - s->buf_ptr; if (len == 0) break; } } else { memcpy(buf, s->buf_ptr, len); buf += len; s->buf_ptr += len; size -= len; } } if (size1 == size) { if (s->error) return s->error; if (avio_feof(s)) return AVERROR_EOF; } return size1 - size; }

av_probe_input_format2实现位于libavformat/format.c:128

主要内容就是调用了av_probe_input_format3

const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
                                            int is_opened, int *score_max)
{
    int score_ret;
    const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
    if (score_ret > *score_max) {
// 如果得分大于25则返回该demux组件
*score_max = score_ret; return fmt; } else return NULL; }

av_probe_input_format3 实现于 libavformat/format.c:207

const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
                                            int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    const AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    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;

    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;

// 这是是用来判断是否有id3信息,分析前10个字节中是否存在id3
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) { 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; lpd.buf_size -= id3len; } else if (id3len >= PROBE_BUF_MAX) { nodat = ID3_GREATER_MAX_PROBE; } else nodat = ID3_GREATER_PROBE; }
// 调用av_demuxer_iterate来遍历获取demuxer
while ((fmt1 = av_demuxer_iterate(&i))) { if (fmt1->flags & AVFMT_EXPERIMENTAL) continue; if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2")) continue; score = 0; if (fmt1->read_probe) {
// 调用AVInputFormat的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); // 如果没有id3,取计算得分 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) { // 如果没有read_probe,存在扩展名,根据扩展名来判断 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; } }
// 记录下最高分数和对应的AVInputFormat
if (score > score_max) { score_max = score; fmt = fmt1; } else if (score == score_max) fmt = NULL; } if (nodat == ID3_GREATER_PROBE) score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max); *score_ret = score_max; return fmt; }

av_demuxer_iterate的实现位于libavformat/allformats.c:564,它和前面遍历获取URLContext时很类似

const AVInputFormat *av_demuxer_iterate(void **opaque)
{
    static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1;
    uintptr_t i = (uintptr_t)*opaque;
    const AVInputFormat *f = NULL;
    uintptr_t tmp;

    if (i < size) {
// 从demuxer_list中获取指定项 f
= demuxer_list[i]; } else if (tmp = atomic_load_explicit(&indev_list_intptr, memory_order_relaxed)) { const AVInputFormat *const *indev_list = (const AVInputFormat *const *)tmp; f = indev_list[i - size]; }
// 将索引加一
if (f) *opaque = (void*)(i + 1); return f; }

demuxer_list声明于libavformat/demuxer_list.c中,由于实在太长,这里就不贴代码了,这是一个二级指针,保存了各个demuxer(AVInputFormat)的地址。下面以ff_mpegts_demuxer为例子看看代码

ff_mpegts_demuxer声明在libavformat/mpegts.c:3422中

const AVInputFormat ff_mpegts_demuxer = {
    .name           = "mpegts",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
    .priv_data_size = sizeof(MpegTSContext),
    .read_probe     = mpegts_probe,
    .read_header    = mpegts_read_header,
    .read_packet    = mpegts_read_packet,
    .read_close     = mpegts_read_close,
    .read_timestamp = mpegts_get_dts,
    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
    .priv_class     = &mpegts_class,
};

read_probe方法就是用来探测的

ffio_rewind_with_probe_data

将探测数据保存到AVIOContext中,避免重新读取

int ffio_rewind_with_probe_data(AVIOContext *s, unsigned char **bufp, int buf_size)
{
    int64_t buffer_start;
    int buffer_size;
    int overlap, new_size, alloc_size;
    uint8_t *buf = *bufp;

    if (s->write_flag) {
        av_freep(bufp);
        return AVERROR(EINVAL);
    }

    buffer_size = s->buf_end - s->buffer;

    /* the buffers must touch or overlap */
    if ((buffer_start = s->pos - buffer_size) > buf_size) {
        av_freep(bufp);
        return AVERROR(EINVAL);
    }

    overlap = buf_size - buffer_start;
    new_size = buf_size + buffer_size - overlap;

    alloc_size = FFMAX(s->buffer_size, new_size);
    if (alloc_size > buf_size)
        if (!(buf = (*bufp) = av_realloc_f(buf, 1, alloc_size)))
            return AVERROR(ENOMEM);

    if (new_size > buf_size) {
        memcpy(buf + buf_size, s->buffer + overlap, buffer_size - overlap);
        buf_size = new_size;
    }

    av_free(s->buffer);
    s->buf_ptr = s->buffer = buf;
    s->buffer_size = alloc_size;
    s->pos = buf_size;
    s->buf_end = s->buf_ptr + buf_size;
    s->eof_reached = 0;

    return 0;
}

read_header

读取文件头,获取到metadata信息,流信息,下面以ff_mov_demuxer举个例子

实现路径 libavformat/mov.c:8566

mov_read_header起始就是取查找mp4的box,找到moov和mdat两个主要的box,找到trak box之后调用mov_read_trak,调用avformat_new_stream来创建流,读取流相关的信息

static int mov_read_header(AVFormatContext *s)
{
    MOVContext *mov = s->priv_data;
    AVIOContext *pb = s->pb;
    int j, err;
    MOVAtom atom = { AV_RL32("root") };
    int i;

    if (mov->decryption_key_len != 0 && mov->decryption_key_len != AES_CTR_KEY_SIZE) {
        av_log(s, AV_LOG_ERROR, "Invalid decryption key len %d expected %d\n",
            mov->decryption_key_len, AES_CTR_KEY_SIZE);
        return AVERROR(EINVAL);
    }

    mov->fc = s;
    mov->trak_index = -1;
    /* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
    if (pb->seekable & AVIO_SEEKABLE_NORMAL)
        atom.size = avio_size(pb);
    else
        atom.size = INT64_MAX;

    /* check MOV header */
    do {
        if (mov->moov_retry)
            avio_seek(pb, 0, SEEK_SET);
        if ((err = mov_read_default(mov, pb, atom)) < 0) {
            av_log(s, AV_LOG_ERROR, "error reading header\n");
            return err;
        }
    } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
    if (!mov->found_moov) {
        av_log(s, AV_LOG_ERROR, "moov atom not found\n");
        return AVERROR_INVALIDDATA;
    }
    av_log(mov->fc, AV_LOG_TRACE, "on_parse_exit_offset=%"PRId64"\n", avio_tell(pb));

    if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
        if (mov->nb_chapter_tracks > 0 && !mov->ignore_chapters)
            mov_read_chapters(s);
        for (i = 0; i < s->nb_streams; i++)
            if (s->streams[i]->codecpar->codec_tag == AV_RL32("tmcd")) {
                mov_read_timecode_track(s, s->streams[i]);
            } else if (s->streams[i]->codecpar->codec_tag == AV_RL32("rtmd")) {
                mov_read_rtmd_track(s, s->streams[i]);
            }
    }

    /* copy timecode metadata from tmcd tracks to the related video streams */
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;
        if (sc->timecode_track > 0) {
            AVDictionaryEntry *tcr;
            int tmcd_st_id = -1;

            for (j = 0; j < s->nb_streams; j++)
                if (s->streams[j]->id == sc->timecode_track)
                    tmcd_st_id = j;

            if (tmcd_st_id < 0 || tmcd_st_id == i)
                continue;
            tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", NULL, 0);
            if (tcr)
                av_dict_set(&st->metadata, "timecode", tcr->value, 0);
        }
    }
    export_orphan_timecode(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        FFStream *const sti = ffstream(st);
        MOVStreamContext *sc = st->priv_data;
        fix_timescale(mov, sc);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
            st->codecpar->codec_id   == AV_CODEC_ID_AAC) {
            sti->skip_samples = sc->start_pad;
        }
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && sc->nb_frames_for_fps > 0 && sc->duration_for_fps > 0)
            av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
                      sc->time_scale*(int64_t)sc->nb_frames_for_fps, sc->duration_for_fps, INT_MAX);
        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
            if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {
                st->codecpar->width  = sc->width;
                st->codecpar->height = sc->height;
            }
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE) {
                if ((err = mov_rewrite_dvd_sub_extradata(st)) < 0)
                    return err;
            }
        }
        if (mov->handbrake_version &&
            mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2
            st->codecpar->codec_id == AV_CODEC_ID_MP3) {
            av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");
            sti->need_parsing = AVSTREAM_PARSE_FULL;
        }
    }

    if (mov->trex_data) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (st->duration > 0) {
                /* Akin to sc->data_size * 8 * sc->time_scale / st->duration but accounting for overflows. */
                st->codecpar->bit_rate = av_rescale(sc->data_size, ((int64_t) sc->time_scale) * 8, st->duration);
                if (st->codecpar->bit_rate == INT64_MIN) {
                    av_log(s, AV_LOG_WARNING, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    st->codecpar->bit_rate = 0;
                    if (s->error_recognition & AV_EF_EXPLODE)
                        return AVERROR_INVALIDDATA;
                }
            }
        }
    }

    if (mov->use_mfra_for > 0) {
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st = s->streams[i];
            MOVStreamContext *sc = st->priv_data;
            if (sc->duration_for_fps > 0) {
                /* Akin to sc->data_size * 8 * sc->time_scale / sc->duration_for_fps but accounting for overflows. */
                st->codecpar->bit_rate = av_rescale(sc->data_size, ((int64_t) sc->time_scale) * 8, sc->duration_for_fps);
                if (st->codecpar->bit_rate == INT64_MIN) {
                    av_log(s, AV_LOG_WARNING, "Overflow during bit rate calculation %"PRId64" * 8 * %d\n",
                           sc->data_size, sc->time_scale);
                    st->codecpar->bit_rate = 0;
                    if (s->error_recognition & AV_EF_EXPLODE)
                        return AVERROR_INVALIDDATA;
                }
            }
        }
    }

    for (i = 0; i < mov->bitrates_count && i < s->nb_streams; i++) {
        if (mov->bitrates[i]) {
            s->streams[i]->codecpar->bit_rate = mov->bitrates[i];
        }
    }

    ff_rfps_calculate(s);

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        MOVStreamContext *sc = st->priv_data;

        switch (st->codecpar->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            err = ff_replaygain_export(st, s->metadata);
            if (err < 0)
                return err;
            break;
        case AVMEDIA_TYPE_VIDEO:
            if (sc->display_matrix) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, (uint8_t*)sc->display_matrix,
                                              sizeof(int32_t) * 9);
                if (err < 0)
                    return err;

                sc->display_matrix = NULL;
            }
            if (sc->stereo3d) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,
                                              (uint8_t *)sc->stereo3d,
                                              sizeof(*sc->stereo3d));
                if (err < 0)
                    return err;

                sc->stereo3d = NULL;
            }
            if (sc->spherical) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,
                                              (uint8_t *)sc->spherical,
                                              sc->spherical_size);
                if (err < 0)
                    return err;

                sc->spherical = NULL;
            }
            if (sc->mastering) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
                                              (uint8_t *)sc->mastering,
                                              sizeof(*sc->mastering));
                if (err < 0)
                    return err;

                sc->mastering = NULL;
            }
            if (sc->coll) {
                err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
                                              (uint8_t *)sc->coll,
                                              sc->coll_size);
                if (err < 0)
                    return err;

                sc->coll = NULL;
            }
            break;
        }
    }
    ff_configure_buffers_for_index(s, AV_TIME_BASE);

    for (i = 0; i < mov->frag_index.nb_items; i++)
        if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset)
            mov->frag_index.item[i].headers_read = 1;

    return 0;
}

update_stream_avctx

方法实现位于libavformat/demux.c:181

static int update_stream_avctx(AVFormatContext *s)
{
    int ret;
    for (unsigned i = 0; i < s->nb_streams; i++) {
        AVStream *const st  = s->streams[i];
        FFStream *const sti = ffstream(st);

        if (!sti->need_context_update)
            continue;

        /* close parser, because it depends on the codec */
        if (sti->parser && sti->avctx->codec_id != st->codecpar->codec_id) {
            av_parser_close(sti->parser);
            sti->parser = NULL;
        }

        /* update internal codec context, for the parser */
        ret = avcodec_parameters_to_context(sti->avctx, st->codecpar);
        if (ret < 0)
            return ret;

        sti->need_context_update = 0;
    }
    return 0;
}