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

1、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以及解复用组件,其中有几个关键方法:

1.1、init_input,该方法用来探测合适的IO和demux组件

1.2、调用AVInputFormat的read_header方法来尝试读取Stream的相关信息

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; }
////////////////////////////////////////////////////////////////////////// // 1.1、调用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)
///////////////////////////////////////////////////
// 1.2、调用read_header方法读取stream相关信息
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; }

 1.1、init_input

该方法实现位于libavformat/demux.c:150,在avformat_open_input中被调用用于找到合适的IO组件以及合适的demux组件。一般来说不指定AVIOContext以及AVInputFormat,需要自己探测拿到最合适的。这里面有2个重要的方法:

1.1.1、io_open,选择并打开IO组件

1.1.2、av_probe_input_buffer2,探测合适的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;
// 1.1.1、否则先调用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;
   // 1.1.2、调用av_probe_input_buffer2去探测IO类型
return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize); }

 1.1.1、io_open

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

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,这个方法做了两件事:

1.1.1.1、ffurl_open_whitelist,该方法用于创建一个URLContext对象,URLContext中封装有最终的IO实现

1.1.1.2、ffio_fdopen,使用URLContext来创建一个IO上下文AVIOContext

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

1.1.1.1、ffurl_open_whitelist

方法实现位于libavformat/avio.c:306,用于创建一个URLContext对象,总共分别两步:

1.1.1.1.1、ffurl_alloc,创建并初始化一个URLContext,并且要选择好合适的IO protocol

1.1.1.1.2、ffurl_connect,打开url

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;    // 删减部分代码
// 调用ffurl_connect,打开url ret
= ffurl_connect(*puc, options); if (!ret) return 0; fail: ffurl_closep(puc); return ret; }
1.1.1.1.1、ffurl_alloc

方法实现在libavformat/avio.c:293,该方法会先调用url_find_protocol,根据文件名来查找合适的URLProtocol,这部分部分就不单独拆分为小节了;查找到合适的URLProtocol之后就调用url_alloc_for_protocol,来为URLProtocol创建上下文URLContext

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,主要是根据文件名的前缀来判断使用什么URLProtocol,所有的protocol可以通过ffurl_get_protocols来获取

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,所有的URLProtocol的地址都组织在一个指针数组url_protocol当中,

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会为结构体中的函数指针赋不同的值,URLProtocol是真正的文件读写或者网络读写的实现

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来创建URLProtocol的上下文URLContext

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

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);
    }
// 为一个URLContext对象开辟空间,空间的大小是URLContext结构体的大小 + 文件名的字符串长度
//(因为URLCOntext中的filename是一个char指针) uc
= av_mallocz(sizeof(URLContext) + strlen(filename) + 1); if (!uc) { err = AVERROR(ENOMEM); goto fail; }
// 初始化URLContext uc
->av_class = &ffurl_context_class; uc->filename = (char *)&uc[1]; // 字符串的地址在整个结构体的末端,利用该地址为filename地址赋值 strcpy(uc->filename, filename); uc->prot = up;     // 将URLProtocol对象地址保存到prot指针 uc->flags = flags; uc->is_streamed = 0; /* default = not streamed */ uc->max_packet_size = 0; /* default: stream file */ if (up->priv_data_size) {     // 需要根据实际的URLProtocol来判断是否有priv_data_size
// 这里的priv_data指的是不同protocol特有的数据,比如本地文件打开拿到的fd uc
->priv_data = av_mallocz(up->priv_data_size); // 给priv_data开辟空间,这个空间大小需要到对应的Protocol中查找 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)) && p<key && (val = strchr(key+1, 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; // 将创建的URLContext成员的地址返回 *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; }
1.1.1.1.2、ffurl_connect

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

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

打开url的方法很简单,调用URLContext中的URLProtocol成员的url_open方法就好了,如果打开成功那么就将URLContext中的is_connect置为1

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

    if (!options)
        options = &tmp_opts;

  // 删减部分代码
  
  // 调用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中有一个成员priv_data,用于存储不同protocol的独有数据,ff_file_protocol的priv_data为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;
// 初始化URLContext的is_stream状态,获取fd的状态,并判断其是否为FIFO文件,判断该文件是否为streaming 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; }

1.1.1.2、ffio_fdopen

该方法实现位于libavformat/aviobuf.c:956

做完ffurl_open_whitelist(创建URLContext,选择了合适的URLProtocol并且url_open)之后,调用ffio_fdopen方法来为URLContext方法创建更高级的上下文AVIOContext,AVIOContext的read_pause和read_seek等于URLProtocol中的方法

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    uint8_t *buffer = NULL;
    int buffer_size, max_packet_size;

    max_packet_size = h->max_packet_size;
    if (max_packet_size) {
        buffer_size = max_packet_size; /* no need to bufferize more than one packet */
    } else {
        buffer_size = IO_BUFFER_SIZE;
    }
    if (!(h->flags & AVIO_FLAG_WRITE) && h->is_streamed) {
        if (buffer_size > INT_MAX/2)
            return AVERROR(EINVAL);
        buffer_size *= 2;
    }
// 为一块buffer开辟空间 buffer
= av_malloc(buffer_size); if (!buffer) return AVERROR(ENOMEM); // 调用avio_alloc_context方法创建FFIOContext对象 *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) { av_freep(&buffer); return AVERROR(ENOMEM); } // ......
  // 初始化AVIOContext中的成员  (
*s)->direct = h->flags & AVIO_FLAG_DIRECT; (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL; (*s)->max_packet_size = max_packet_size; (*s)->min_packet_size = h->min_packet_size; if(h->prot) {
// 给AVIOContext中的 read_pause、read_seek、url_read_seek赋值 (
*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; }
// 给FFIOContext中的成员方法赋值 ((FFIOContext
*)(*s))->short_seek_get = (int (*)(void *))ffurl_get_short_seek; (*s)->av_class = &ff_avio_class; return 0; }

avio_aloc_context用于创建并初始化一个FFIOContext对象,里面会包含有一个AVIOContext对象

libavformat/aviobuf.c:135:

AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    FFIOContext *s = av_malloc(sizeof(*s));
    if (!s)
        return NULL;
    ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
                  read_packet, write_packet, seek);
    return &s->pub;
}

ffio_init_context实现位于libavformat/aviobuf.c:81,用于初始化AVIOContext,该结构体中持有的buffer用于存储读取到的数据

其中write_packet、read_packet、seek方法对应libavformat/avio.c:401中的ffurl_read、ffurl_write、ffurl_seek,其实就是调用的URLProtocol中的方法。上面可以看到AVIOContext中还有read_pause、read_seek这两个方法,也是指向了URLProtocol中的方法,可能这两个方法是用来做读写控制的,实现pause、seek等命令的

void ffio_init_context(FFIOContext *ctx,
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
    AVIOContext *const s = &ctx->pub;

    memset(ctx, 0, sizeof(*ctx));
    // 用于存储读取的数据
    s->buffer      = buffer;
// buffer的最大大小 ctx
->orig_buffer_size = s->buffer_size = buffer_size;
// buffer的读写指针 s
->buf_ptr = buffer;
// 可读写最大位置 s
->buf_ptr_max = buffer;
// URLProtocol s
->opaque = opaque; s->direct = 0; url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ); // 初始化write、read、seek方法,这里是直接给定的ffurl_read、ffurl_write、ffurl_seek s->write_packet = write_packet; s->read_packet = read_packet; s->seek = seek;
// 初始化AVIOContext中的其他成员 s
->pos = 0; s->eof_reached = 0; s->error = 0; s->seekable = seek ? AVIO_SEEKABLE_NORMAL : 0; s->min_packet_size = 0; s->max_packet_size = 0; s->update_checksum = NULL; ctx->short_seek_threshold = SHORT_SEEK_THRESHOLD; if (!read_packet && !write_flag) { s->pos = buffer_size; s->buf_end = s->buffer + buffer_size; } s->read_pause = NULL; s->read_seek = NULL; s->write_data_type = NULL; s->ignore_boundary_point = 0; ctx->current_type = AVIO_DATA_MARKER_UNKNOWN; ctx->last_time = AV_NOPTS_VALUE; ctx->short_seek_get = NULL; #if FF_API_AVIOCONTEXT_WRITTEN FF_DISABLE_DEPRECATION_WARNINGS s->written = 0; FF_ENABLE_DEPRECATION_WARNINGS #endif }

 

1.1.2、av_probe_input_buffer2

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

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

这个方法主要内容分为两步:

1.1.2.1、调用avio_read读取数据,为什么要看这个方法呢?是因为读取的数据后续可能还会再用,所以看下数据是如何存储的

1.1.2.2、调用av_probe_input_format2来探测input format,直到有一个合适的demux组件返回,就会停止读取了

1.1.2.3、调用ffio_rewind_with_probe_data复用已经读取的数据

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;
// 选项信息中存在多少个,以分号分隔,截取第一个;如果没有mime这置为null 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;
// 1.1.2.1、调用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. */ // 1.1.2.2、调用该方法来创建一个最为合适的 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; }

1.1.2.1、avio_read

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

这个方法是对AVIOContext的read_packet的又一层封装,将数据读到AVIOContext中,然后从中拷贝到外部buf中。如果buf大小大于了AVIOContext的最大大小,就直接读到外部buffer中

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

    size1 = size;
    while (size > 0) {
// 检查AVIOContext中的buf的大小 len
= FFMIN(s->buf_end - s->buf_ptr, size);
// 如果AVIOContext没有空间或者要读取的size为0(其实一般不会出现size为0,因为这时候直接退出循环了
     // 也就是读满AVIOContext之后就不再往里面读了,直接读到buffer中)
if (len == 0 || s->write_flag) {
// 如果AVIOContext的direct flag为 1 或者要读取的大小大于AVIOContext的最大buf大小
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方法直接将读取数据到buf当中
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 {
// 从AVIOContext中拷贝buffer出来,并且移动读写指针 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; }

libavformat/aviobuf.c:527

read_packet_wrapper方法会调用AVIOContext的read_packet方法

static int read_packet_wrapper(AVIOContext *s, uint8_t *buf, int size)
{
    int ret;

    if (!s->read_packet)
        return AVERROR(EINVAL);
    ret = s->read_packet(s->opaque, buf, size);
    av_assert2(ret || s->max_packet_size);
    return ret;
}

1.1.2.2、av_probe_input_format2

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方法就是用来探测的

1.1.2.3、调用ffio_rewind_with_probe_data

ffio_rewind_with_probe_data这个方法用于复用已经读取的用于探测demux组件的buffer,我的理解如下:因为读取buffer时可能超过了AVIOContext最大缓存值,也就是bufp中的大小会超过该最大缓存值,

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中数据的长度 buffer_size
= s->buf_end - s->buffer; /* the buffers must touch or overlap */
// buffer_start表示bufp比AVIOContext多出的数据长度
   // 如果该多出的长度大于总长度说明有错误,始放掉buffer
if ((buffer_start = s->pos - buffer_size) > buf_size) { av_freep(bufp); return AVERROR(EINVAL); } // 计算出重合的长度 overlap = buf_size - buffer_start;
// 计算出新长度buffer_size + buffer_start,我觉得好像就和buf_size大小是相同的 new_size
= buf_size + buffer_size - overlap;
// 如果new_size(buf_size)大于原来的AVIOContext size,那么alloc_size就赋大值 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; }
// 释放掉AVIOContext中原来的buffer av_free(s
->buffer);
// 将buf指针指向新的可能更大的buffer s
->buf_ptr = s->buffer = buf;
// 修改buffer_size s
->buffer_size = alloc_size;
// 修改读写位置 s
->pos = buf_size;
// 修改数据结束位置 s
->buf_end = s->buf_ptr + buf_size; s->eof_reached = 0; return 0; }

1.2、read_header

read_header其实是AVInputFormat中的方法,在所有的准备工作都做完了之后就调用该方法读取meta data,获取stream等信息。这个方法比较适合flv、mov这种有header的封装格式,对于mpeg2这种没有header的可能会无法提取出steam info。

下面以ff_mov_demuxer举个例子,代码实现路径 libavformat/mov.c:8566

代码很长,这里只看比较关键的节点,主要就是调用mov_read_default,读取box(atom)

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));

    // ......
}

 mov_read_default主要是解析出box type,然后找到对应的方法来解析box,比如trak box,那就会找到根据box type找到mov_default_parse_table中对应的处理方法mov_read_trak

static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    int64_t total_size = 0;
    MOVAtom a;
    int i;

    if (c->atom_depth > 10) {
        av_log(c->fc, AV_LOG_ERROR, "Atoms too deeply nested\n");
        return AVERROR_INVALIDDATA;
    }
    c->atom_depth ++;

    if (atom.size < 0)
        atom.size = INT64_MAX;
    while (total_size <= atom.size - 8 && !avio_feof(pb)) {
        int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL;
        a.size = atom.size;
        a.type=0;
        if (atom.size >= 8) {
            a.size = avio_rb32(pb);
            a.type = avio_rl32(pb);
            if (((a.type == MKTAG('f','r','e','e') && c->moov_retry) ||
                  a.type == MKTAG('h','o','o','v')) &&
                a.size >= 8 &&
                c->fc->strict_std_compliance < FF_COMPLIANCE_STRICT) {
                uint32_t type;
                avio_skip(pb, 4);
                type = avio_rl32(pb);
                if (avio_feof(pb))
                    break;
                avio_seek(pb, -8, SEEK_CUR);
                if (type == MKTAG('m','v','h','d') ||
                    type == MKTAG('c','m','o','v')) {
                    av_log(c->fc, AV_LOG_ERROR, "Detected moov in a free or hoov atom.\n");
                    a.type = MKTAG('m','o','o','v');
                }
            }
            if (atom.type != MKTAG('r','o','o','t') &&
                atom.type != MKTAG('m','o','o','v')) {
                if (a.type == MKTAG('t','r','a','k') ||
                    a.type == MKTAG('m','d','a','t')) {
                    av_log(c->fc, AV_LOG_ERROR, "Broken file, trak/mdat not at top-level\n");
                    avio_skip(pb, -8);
                    c->atom_depth --;
                    return 0;
                }
            }
            total_size += 8;
            if (a.size == 1 && total_size + 8 <= atom.size) { /* 64 bit extended size */
                a.size = avio_rb64(pb) - 8;
                total_size += 8;
            }
        }
        av_log(c->fc, AV_LOG_TRACE, "type:'%s' parent:'%s' sz: %"PRId64" %"PRId64" %"PRId64"\n",
               av_fourcc2str(a.type), av_fourcc2str(atom.type), a.size, total_size, atom.size);
        if (a.size == 0) {
            a.size = atom.size - total_size + 8;
        }
        if (a.size < 0)
            break;
        a.size -= 8;
        if (a.size < 0)
            break;
        a.size = FFMIN(a.size, atom.size - total_size);
        // 遍历查找box type对应的处理方法
        for (i = 0; mov_default_parse_table[i].type; i++)
            if (mov_default_parse_table[i].type == a.type) {
                parse = mov_default_parse_table[i].parse;
                break;
            }
       // ......
}

mov_read_trak

该方法会读取box中的track信息,并且创建一个AVStream,将这些信息存储到AVSTream当中

1.2.1、调用avformat_new_stream创建一个AVStream,传入参数为AVFormatContext

static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
    AVStream *st;
    MOVStreamContext *sc;
    int ret;
    // 1.2.1、创建一个AVStream
    st = avformat_new_stream(c->fc, NULL);
    if (!st) return AVERROR(ENOMEM);
// 默认id为-1 st
->id = -1;
// 创建一个MOVSTreamContext休息 sc
= av_mallocz(sizeof(MOVStreamContext)); if (!sc) return AVERROR(ENOMEM); // AVStream中的priv_data存放的每个InputFormat独有的Stream info组织形式 st->priv_data = sc;
// 默认置codecpar->codec_type为data st
->codecpar->codec_type = AVMEDIA_TYPE_DATA;
sc
->ffindex = st->index; c->trak_index = st->index; // 继续读取track box的子box,这时候就把相关track meta读到AVStream当中去了 if ((ret = mov_read_default(c, pb, atom)) < 0) return ret; c->trak_index = -1; // Here stsc refers to a chunk not described in stco. This is technically invalid, // but we can overlook it (clearing stsc) whenever stts_count == 0 (indicating no samples). if (!sc->chunk_count && !sc->stts_count && sc->stsc_count) { sc->stsc_count = 0; av_freep(&sc->stsc_data); } /* sanity checks */ if ((sc->chunk_count && (!sc->stts_count || !sc->stsc_count || (!sc->sample_size && !sc->sample_count))) || (!sc->chunk_count && sc->sample_count)) { av_log(c->fc, AV_LOG_ERROR, "stream %d, missing mandatory atoms, broken header\n", st->index); return 0; } if (sc->stsc_count && sc->stsc_data[ sc->stsc_count - 1 ].first > sc->chunk_count) { av_log(c->fc, AV_LOG_ERROR, "stream %d, contradictionary STSC and STCO\n", st->index); return AVERROR_INVALIDDATA; } fix_timescale(c, sc); avpriv_set_pts_info(st, 64, 1, sc->time_scale); mov_build_index(c, st); if (sc->dref_id-1 < sc->drefs_count && sc->drefs[sc->dref_id-1].path) { MOVDref *dref = &sc->drefs[sc->dref_id - 1]; if (c->enable_drefs) { if (mov_open_dref(c, &sc->pb, c->fc->url, dref) < 0) av_log(c->fc, AV_LOG_ERROR, "stream %d, error opening alias: path='%s', dir='%s', " "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d\n", st->index, dref->path, dref->dir, dref->filename, dref->volume, dref->nlvl_from, dref->nlvl_to); } else { av_log(c->fc, AV_LOG_WARNING, "Skipped opening external track: " "stream %d, alias: path='%s', dir='%s', " "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d." "Set enable_drefs to allow this.\n", st->index, dref->path, dref->dir, dref->filename, dref->volume, dref->nlvl_from, dref->nlvl_to); } } else { sc->pb = c->fc->pb; sc->pb_is_copied = 1; } if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (!st->sample_aspect_ratio.num && st->codecpar->width && st->codecpar->height && sc->height && sc->width && (st->codecpar->width != sc->width || st->codecpar->height != sc->height)) { st->sample_aspect_ratio = av_d2q(((double)st->codecpar->height * sc->width) / ((double)st->codecpar->width * sc->height), INT_MAX); } #if FF_API_R_FRAME_RATE if (sc->stts_count == 1 || (sc->stts_count == 2 && sc->stts_data[1].count == 1)) av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, sc->time_scale, sc->stts_data[0].duration, INT_MAX); #endif } // done for ai5q, ai52, ai55, ai1q, ai12 and ai15. if (!st->codecpar->extradata_size && st->codecpar->codec_id == AV_CODEC_ID_H264 && TAG_IS_AVCI(st->codecpar->codec_tag)) { ret = ff_generate_avci_extradata(st); if (ret < 0) return ret; } switch (st->codecpar->codec_id) { #if CONFIG_H261_DECODER case AV_CODEC_ID_H261: #endif #if CONFIG_H263_DECODER case AV_CODEC_ID_H263: #endif #if CONFIG_MPEG4_DECODER case AV_CODEC_ID_MPEG4: #endif st->codecpar->width = 0; /* let decoder init width/height */ st->codecpar->height= 0; break; } // If the duration of the mp3 packets is not constant, then they could need a parser if (st->codecpar->codec_id == AV_CODEC_ID_MP3 && sc->stts_count > 3 && sc->stts_count*10 > st->nb_frames && sc->time_scale == st->codecpar->sample_rate) { ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL; } /* Do not need those anymore. */ av_freep(&sc->chunk_offsets); av_freep(&sc->sample_sizes); av_freep(&sc->keyframes); av_freep(&sc->stts_data); av_freep(&sc->stps_data); av_freep(&sc->elst_data); av_freep(&sc->rap_group); return 0; }

1.2.1、avformat_new_stream

下面看下关键的创建stream的方法avformat_new_stream

方法实现位于 libavformat/utils.c:768,主要有以下几个步骤:

1.2.1.1、给AVStream开辟空间

1.2.1.2、给AVStream创建一个上下文FFStream

1.2.1.3、调用avcodec_parameters_alloc创建AVStream中的codecpar

1.2.1.4、调用avcodec_alloc_context3给FFStream创建一个AVCodecContext,FFStream作为AVStream的上下文,起到一个管理作用,保存有Stream对应的信息,也保存有stream对应的Codec

1.2.1.5、给FFStream中的info成员分配内存并初始化

这里比较重要的是1.2.1.4

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{
    FFFormatContext *const si = ffformatcontext(s);
    FFStream *sti;
    AVStream *st;
    AVStream **streams;
    // 检查stream的个数
    if (s->nb_streams >= s->max_streams) {
        av_log(s, AV_LOG_ERROR, "Number of streams exceeds max_streams parameter"
               " (%d), see the documentation if you wish to increase it\n",
               s->max_streams);
        return NULL;
    }
// 1.2.1.1、重新给AVFormatContext中的stream数组开辟空间 streams
= av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams)); if (!streams) return NULL; s->streams = streams;   // 1.2.1.2、创建一个AVStream的上下文 FFStream sti = av_mallocz(sizeof(*sti)); if (!sti) return NULL;
// 将FFStream和AVStrem相关联 st
= &sti->pub; #if FF_API_AVSTREAM_CLASS st->av_class = &stream_class; #endif // 1.2.1.3、调用avcodec_parameters_alloc来初始化codecpar st->codecpar = avcodec_parameters_alloc(); if (!st->codecpar) goto fail; // 1.2.1.4、调用avcodec_alloc_context3来创建一个AVCodecContext对象 sti->avctx = avcodec_alloc_context3(NULL); if (!sti->avctx) goto fail; if (s->iformat) {
// 1.2.1.5、给info结构体分配内存,存储的是codec相关信息 sti
->info = av_mallocz(sizeof(*sti->info)); if (!sti->info) goto fail; #if FF_API_R_FRAME_RATE sti->info->last_dts = AV_NOPTS_VALUE; #endif sti->info->fps_first_dts = AV_NOPTS_VALUE; sti->info->fps_last_dts = AV_NOPTS_VALUE; /* default pts setting is MPEG-like */ avpriv_set_pts_info(st, 33, 1, 90000); /* we set the current DTS to 0 so that formats without any timestamps * but durations get some timestamps, formats with some unknown * timestamps have their first few packets buffered and the * timestamps corrected before they are returned to the user */ sti->cur_dts = RELATIVE_TS_BASE; } else { sti->cur_dts = AV_NOPTS_VALUE; } // AVStream的index对应于AVFormatContext中的nb_streams索引 st->index = s->nb_streams;
// 初始化AVStream和FFStream中的相关内容 st
->start_time = AV_NOPTS_VALUE; st->duration = AV_NOPTS_VALUE; sti->first_dts = AV_NOPTS_VALUE; sti->probe_packets = s->max_probe_packets; sti->pts_wrap_reference = AV_NOPTS_VALUE; sti->pts_wrap_behavior = AV_PTS_WRAP_IGNORE; sti->last_IP_pts = AV_NOPTS_VALUE; sti->last_dts_for_order_check = AV_NOPTS_VALUE;
// 初始化pts_buffer
for (int i = 0; i < MAX_REORDER_DELAY + 1; i++) sti->pts_buffer[i] = AV_NOPTS_VALUE; st->sample_aspect_ratio = (AVRational) { 0, 1 }; sti->inject_global_side_data = si->inject_global_side_data; sti->need_context_update = 1; // 将创建的AVStream保存到AVFormatContext中 s->streams[s->nb_streams++] = st; return st; fail: free_stream(&st); return NULL; }

1.2.1.4、avcodec_alloc_context3

该方法用于创建一个AVCodecContext对象,方法实现位于libavcodec/options.c:141

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
    AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));

    if (!avctx)
        return NULL;

    if (init_context_defaults(avctx, codec) < 0) {
        av_free(avctx);
        return NULL;
    }

    return avctx;
}

 

posted @ 2022-05-26 21:50  青山渺渺  阅读(803)  评论(0编辑  收藏  举报