FFRMPEG4.1源码分析之 io_open_default() && io_close_default()

目录

1 io_open_default()

1.1 ffio_open_whitelist()

1.1.1 ffurl_open_whitelist()

         1.1.1.1 ffurl_alloc()

                    1.1.1.1.1 url_find_protocol()

                                   1.1.1.1.1.1 ffurl_get_protocols()

                                   1.1.1.1.2 url_alloc_for_protocol()

                    1.1.1.2 ffurl_connect()

                               1.1.1.2.1 url_open2() && url_open()

                               1.1.1.2.2 ffurl_seek()

1.1.2 ffio_fdopen

         1.1.2.1 avio_alloc_context()

1.1.3 ffurl_close()

2 io_close_default()


1 io_open_default()


io_open_default() 源码:

  • 所属库:libavformat(lavf
  • 头文件:无,静态函数
  • 源文件:libavformat/options.c
  • 作用:打开URL,创建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");
    
    #if FF_API_OLD_OPEN_CALLBACKS
    FF_DISABLE_DEPRECATION_WARNINGS
        if (s->open_cb)
            return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
    FF_ENABLE_DEPRECATION_WARNINGS
    #endif
    
        return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
    }
  1.  输出一条打开某个url日志。注意一点:此处为啥要对image2图片封装格式做特殊处理呢?目前还不明白这点。
  2.  若AVFormatContext.open_cb非空,使用该函数打开URL。注意avformat_alloc_context()函数创建并初始化AVFormatContext对象时,并未初始化AVFormatContext.open_cb成员。
    该成员的声明以及使用情况如下,但是注意这么几点:
    1)该成员已经被标注为deprecated;
    2)av_format_set_open_cb()函数和av_format_set_open_cb()函数均能找到函数的声明,但是源码中找不到定义,这一点令人奇怪,不知道是否是因为已标记为deprecated缘故;
    3)源码中有专门定义一个AVOpenCallback,open_cb就是该类型的,个人觉得open_cb字段的声明以AVOpenCallback作为类型进行声明更合适,可能是因为该函数已经是被标注为deprecated的缘故吧。
        /**
         * 该函数用来打开AVIOContext对象,如果在解封装的时候需要使用I/O来读文件的话。
         * Called to open further IO contexts when needed for demuxing.
         *
         * 这个函数可以被用户程序设置,在打开URLs之前对URL进行安全性验证。
         * This can be set by the user application to perform security checks on
         * the URLs before opening them.
         * 这个函数的行为应该像avio_open2()一样。AVFormatContext来提供上下文信息, 
         * 提供AVFormatContext.opaque
         * The function should behave like avio_open2(), AVFormatContext is provided
         * as contextual information and to reach AVFormatContext.opaque.
         *
         * 如果该函数指针为空,那么avio_open2会做一些简单的安全检查
         * If NULL then some simple checks are used together with avio_open2().
         *
         * 该函数指针不应该通过AVFormatContext来访问,而应该使用av_format_set_open_cb()来设置;
         * 使用av_format_get_open_cb()来获取。
         * Must not be accessed directly from outside avformat.
         * @See av_format_set_open_cb()
         *
         * Demuxing: Set by user.
         *
         * @deprecated Use io_open and io_close.
         */
        attribute_deprecated
        int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
    
    #if FF_API_OLD_OPEN_CALLBACKS
    attribute_deprecated AVOpenCallback av_format_get_open_cb(const AVFormatContext *s);
    attribute_deprecated void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback);
    #endif
    
    typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags,
                                  const AVIOInterruptCB *int_cb, AVDictionary **options);
    
    
  3. 若AVFormatContext.open_cb为空,使用ffio_open_whitelist()函数打开URL。

1.1 ffio_open_whitelist()


ffio_open_whitelist() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/avio_internal.h  如其名,是IO内部使用的函数,不是public api
  • 声明:
int ffio_open_whitelist(AVIOContext **s, const char *url, int flags,
                         const AVIOInterruptCB *int_cb, AVDictionary **options,
                         const char *whitelist, const char *blacklist);

ffio_open_whitelist() 源码:

  • 源文件:libavformat/aviobuf.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;
    
        err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
        if (err < 0)
            return err;
        err = ffio_fdopen(s, h);
        if (err < 0) {
            ffurl_close(h);
            return err;
        }
        return 0;
    }

1.1.1 ffurl_open_whitelist()


ffurl_open_whitelist() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/url.h
  • 功能说明:根据URL来创建一个URLContext对象,用来访问URL所定位的资源,并打开之。对于该函数的入参以及返回值说明如下源码:
    /**
     * Create an URLContext for accessing to the resource indicated by
     * url, and open it.
     * 
     * puc指向成功打开后的URLContext对象
     * @param puc pointer to the location where, in case of success, the
     * function puts the pointer to the created URLContext
     *
     * flags参数控制如何打开url指定的资源
     * @param flags flags which control how the resource indicated by url
     * is to be opened
     *
     * int_cb 中断函数,供URLContext使用,可空
     * @param int_cb interrupt callback to use for the URLContext, may be
     * NULL
     *
     * options参数,传入协议私有的选项。生效的选项,对应条目将被销毁,未生效的选项在本函数执
     * 行之后仍保留在该参数中。该参数可空。
     * @param options  A dictionary filled with protocol-private options. On return
     * this parameter will be destroyed and replaced with a dict containing options
     * that were not found. May be NULL.
     *
     * parent参数代表创建成功的URLContext的父URLContext对象,父URLContext对象的通用选项
     * 也应用到创建的URLContext对象中。
     * @param parent An enclosing URLContext, whose generic options should
     *               be applied to this URLContext as well.
     * 成功返回0或正数,失败返回负数,并且是由AVERROR宏返回的错误码
     * @return >= 0 in case of success, a negative value corresponding to an
     * AVERROR code in case of failure
     */
    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);

ffurl_open_whitelist() 源码:

  • 源文件:avio.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)
    {
        AVDictionary *tmp_opts = NULL;
        AVDictionaryEntry *e;
        int ret = ffurl_alloc(puc, filename, flags, int_cb);  // 创建URLContext
        if (ret < 0)
            return ret;
        if (parent)
            av_opt_copy(*puc, parent); // 拷贝父URLContext的选项->子URLContext
    
        // 设置用户传入的选项到URLContext
        if (options &&
            (ret = av_opt_set_dict(*puc, options)) < 0) 
            goto fail;
    
        // 上一步中的options含有非URLContext类的直接选项信息
        // 则设置用户传入的选项到URLContext.priv_data数据中
        if (options && (*puc)->prot->priv_data_class && 
            (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
            goto fail;
        
        // 传入的options为空,options指向内部临时变量,后续操作黑白名单需要用上选项
        // 此处操作就是为了不论是否用户传入了options,还是传入NULL,后续操作能统一使用一个变量。 
        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));
    
        // 将白名单放到options中
        if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)
            goto fail;
    
        // 将黑名单放到options中
        if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)
            goto fail;
    
        // 将options的黑白名单应用到URLContext
        if ((ret = av_opt_set_dict(*puc, options)) < 0)
            goto fail;
    
        ret = ffurl_connect(*puc, options);
    
        if (!ret)
            return 0;
    fail:
        ffurl_close(*puc);
        *puc = NULL;
        return ret;
    }
  1.  使用 ffurl_alloc() 创建一个与URL匹配的URLContext,并初始化;
     1)根据URL中包含的协议名来查找对应的URLProtocol对象;
     2)根据URLProtocol对象以及ffurl_alloc()传入的参数来构建URLContext上下文对象。
  2.  使用 av_opt_copy() 拷贝父URLContext(如果存在的话)的选项到新创建的URLContext;该函数的详细分析见FFMPEG源码分析之 av_opt_copy()
  3.  使用 av_opt_set_dict() 应用用户传入的选项options到URLContext对象:注意,若是options存在URLContext中不存在的选项,则该选项还会留存在options中,而那些应用成功的选项将会从options删除。该函数的详细分析见 FFMPEG源码分析之 av_opt_set_dict()
  4. 使用av_opt_set_dict() 应用用户传入的选项options到URLContext.priv_data对象:上一步遗留的options,应用到URLContext.priv_data,应用不成功的选项仍会遗留在options中;
  5. 对黑白名单内容进行断言:断言保证用户传入的黑白名单为空,要么与options中的保持一致。FFMPEG中的断言实现见 FFMPEG4.1源码分析之 断言
  6. 使用 av_dict_set() 将黑白名单复制到options,然后使用 av_opt_set_dict() 将黑白名单应用到URLContext:av_dict_set()详细分析见 FFMPEG源码分析之 av_dict_set()
  7. 使用 ffurl_connect() 打开URL,如果是需要网络的话,将建立连接;
  8. 出错将使用ffurl_close() 进行清理。

1.1.1.1 ffurl_alloc()


ffurl_alloc() 声明:

  • 所属库:libavformat(lavf
  • 头文件:ibavformat/url.h
  • 功能说明:根据URL创建一个可以访问URL的URLContext对象,但是没有初始化连接。
    /**
     * Create a URLContext for accessing to the resource indicated by
     * url, but do not initiate the connection yet.
     *
     * puc参数指向创建成功的URLContext
     * @param puc pointer to the location where, in case of success, the
     * function puts the pointer to the created URLContext
     *
     * flags控制如何打开url
     * @param flags flags which control how the resource indicated by url
     * is to be opened
     *
     * int_cb 是中断函数,供URLContext使用,可空
     * @param int_cb interrupt callback to use for the URLContext, may be
     * NULL
     *
     * 该函数成功返回非负值,失败返回负值,为AVERROR宏作用后的错误码
     * @return >= 0 in case of success, a negative value corresponding to an
     * AVERROR code in case of failure
     */
    int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                    const AVIOInterruptCB *int_cb);

ffurl_alloc() 源码:

  • 源文件:libavformat/avio.c
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    const URLProtocol *p = NULL;

    p = url_find_protocol(filename);
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    *puc = NULL;
    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 AVERROR_PROTOCOL_NOT_FOUND;
}
  1.  使用url_find_protocol()查找URLProtocol对象

  2.  url_find_protocol() 查找成功:使用url_alloc_for_protocol() 来创建URLContext上下文对象,并初始化其成员;

  3.  url_find_protocol() 查找失败:那么如果URL(或者说filename)是https协议或者是tls协议,打印WARNING级别日志告知需要重新编译ffmpeg并使能https/tls协议所依赖的openssl库,gnutls库或者是securetransport库;返回AVERROR_PROTOCOL_NOT_FOUND宏定义指代的值,该值的四个字节分别就是0xF8,'P','R','O',并且由FFERRTAG定义可知是负值。

    #define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') ///< Protocol not found
    #define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d))
    #define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24))

1.1.1.1.1 url_find_protocol()


url_find_protocol() 源码:

  • 所属库:libavformat(lavf
  • 头文件:无,静态函数
  • 源文件:libavformat/avio.c
  • 功能:根据URL(filename)字符串来查找适配的URLContext对象。
    static const struct URLProtocol *url_find_protocol(const char *filename)
    {
        const URLProtocol **protocols;
        char proto_str[128], proto_nested[128], *ptr;
    
        // 获取scheme长度
        size_t proto_len = strspn(filename, URL_SCHEME_CHARS); 
        int i;
    
        // 判断是否为file协议,若是file协议,则proto_str设置为file
        // 若不是file协议,则proto_str设置为filename中截取的scheme
        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)));
    
        // 处理nested protocol scheme
        av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
        if ((ptr = strchr(proto_nested, '+')))
            *ptr = '\0';
    
        // 获取所有支持的URLProtocol
        protocols = ffurl_get_protocols(NULL, NULL);
        if (!protocols)
            return NULL;
    
        // 迭代匹配,返回匹配的URLProtocol
        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;
            }
        }
        av_freep(&protocols);
    
        return NULL;
    }
  1.  计算filename字符串属于协议scheme的长度proto_len:URL_SCHEME_CHARS宏定义如下所示,其包含了协议scheme所有可能的字符; strspn() 是c语言字符串操作基础库string.h中的函数,作用是返回字符串中第一个不在指定字符串中出现的字符下标。举个例子:
    1)url为 rtmp://ns8.indexforce.com/home/mystream
    2)  strspn(url, URL_SCHEME_CHARS); 由于 ‘:’ 不属于URL_SCHEME_CHARS中的一员,因此将返回其下标,也就是4,正好是协议“rtmp”几个字符的长度
    #define URL_SCHEME_CHARS                        \
        "abcdefghijklmnopqrstuvwxyz"                \
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
        "0123456789+-."
  2. 计算filename字符串属于协议scheme的字符串proto_str:判断URL是否指向本地磁盘文件,如果是则proto_str置为"file",如果不是本地文件,则proto_str取URL前proto_len+1字符串作为协议。 对于Windows,Mac,IOS,Linux,Android等ffmpeg兼容的系统而言,URL形式不外乎有如下几种:
    1)  windows的DOS path: "c:/media/video.mp4";  该形式的URL可以由is_dos_path()方法检出,从而被判定为本地文件(该方法位于libavformat/os_support.h文件中)
    static inline int is_dos_path(const char *path)
    {
    #if HAVE_DOS_PATHS
        if (path[0] && path[1] == ':')
            return 1;
    #endif
        return 0;
    }
    2) 类unix系统的绝对路径和相对路径:"./media/video.mp4" 或 "../media/video.mp4" 或 "/root/mediafile/video.mp4" ;由于该形式的URL不包含字符 ":",所以语句filename[proto_len] != ':' 和!strchr(filename + proto_len + 1, ':') 肯定都成立,从而被判定为本地文件。

    3)注意subfile协议是个特例,其URL形似为"subfile,,start,32815239,end,0,,:video.ts",  因此通过strncmp(filename, "subfile,", 8)语句将其排除在file协议之外。(疑问之处:如果不加strncmp()这条语句,也能通过!strchr(filename + proto_len + 1, ':') 将其排除的?),该协议的详细描述见FFMPEG官网:ffmpeg-protocols.html#subfile
     
  3. 获取协议字符串proto_str可能嵌套的协议串proto_nested:以hls协议为例,其nested格式为 "hls+http://host/path/to/remote/resource.m3u8" 和 "hls+file://path/to/local/resource.m3u8",由源码中的处理可知对于上述URL的proto_nested为"hls"
  4. ffurl_get_protocols() 获取ffmpeg支持的所有URLProtocol,存储在变量 const URLProtocol **protocols中;
  5. 迭代protocols中的每个URLProtocol,进行协议名匹配:先将proto_str匹配URLProtocol.name,如果不匹配则对proto_nested匹配URLProtocol.name,只要满足其一,那么就算找到了合适的URLProtocol对象。
  6. av_freep()释放protocols所占空间:具体分析见(FFMPEG4.1源码分析之 内存分配

1.1.1.1.1.1 ffurl_get_protocols()


ffurl_get_protocols() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/url.h
  • 声明:
            1. 创建一个协议的list,list中的协议将匹配(满足)白名单和黑名单;
            2. 白名单(whitelist):该参数要么是空,要么是以逗号分隔的协议名,这些协议名为允许的协议;若不为空,那么该函数返回的协议list必须属于白名单中的列举的协议;
            3. 黑名单(blacklist):该参数要么是空,要么是以都放分隔的协议名,这些协议名为禁止的协议;若不为空,那么该函数返回的协议list中必须排除黑名单中列举的协议。
    /**
     * Construct a list of protocols matching a given whitelist and/or blacklist.
     *
     * @param whitelist a comma-separated list of allowed protocol names or NULL. If
     *                  this is a non-empty string, only protocols in this list will
     *                  be included.
     * @param blacklist a comma-separated list of forbidden protocol names or NULL.
     *                  If this is a non-empty string, all protocols in this list
     *                  will be excluded.
     *
     * @return a NULL-terminated array of matching protocols. The array must be
     * freed by the caller.
     */
    const URLProtocol **ffurl_get_protocols(const char *whitelist,
                                            const char *blacklist);
      

ffurl_get_protocols() 声明:

  • 所属库:libavformat(lavf
  • 源文件:libavformat/protocols.c
  • 源码:
    const URLProtocol **ffurl_get_protocols(const char *whitelist,
                                            const char *blacklist)
    {
        const URLProtocol **ret;
        int i, ret_idx = 0;
    
        ret = av_mallocz_array(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;
    
            ret[ret_idx++] = up;
        }
    
        return ret;
    }

    1.  url_protocols是一个定义在protocol_list.c文件中的静态数组,包含了ffmpeg所支持的所有协议,如下所示:

    static const URLProtocol *url_protocols[] = {
        &ff_async_protocol,
        ...此处省略几十行...
        &ff_libssh_protocol,
        NULL };

    2.  ret为函数内部分配的URLProtocol *数组,其空间大小与url_protocols相同
    3.  循环查找url_protocols静态数组中,协议名在白名单中,不在黑名单中的协议,并填充ret数组。
    4.  返回该数组。

1.1.1.1.2 url_alloc_for_protocol()


url_alloc_for_protocol() 源码:

  • 所属库:libavformat(lavf
  • 头文件:无,静态函数
  • 源文件:libavformat/avio.c
  • 功能:创建URLContext上下文对象,并且初始化URL各成员对象。
    static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                      const char *filename, int flags,
                                      const AVIOInterruptCB *int_cb)
    {
        URLContext *uc;
        int err;
    
        // ffmpeg配置需要使用网络,需要进行如下判断
        // 若协议是需要使用网络的,但是网络库初始化失败,则返回失败码
    #if CONFIG_NETWORK
        if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
            return AVERROR(EIO);
    #endif
        
        // 如果要求读该协议,但是对应的URLProtocol.url_read不存在,即不支持读
        // 打印错误信息,并返回错误码
        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);
        }
    
        // 如果要求写该协议,但是对应的URLProtocol.url_write不存在,即不支持写
        // 打印错误信息,并返回错误码
        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上下文对象,为啥这儿不是分配sizeof(URLContext)?
        // 仔细思考哦~
        uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
        if (!uc) {  // 分配空间失败,记住失败码,跳转fail标签处理
            err = AVERROR(ENOMEM);
            goto fail;
        }
        
        // 初始化URLContext各成员
        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 */
        
        // 特别注意URLProtocol.priv_data_size的大小
        // 以及URLContext.priv_data到底是哪种类型?AVClass吗?
        // 哈哈,再仔细追踪下代码吧~~
        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) {
                int proto_len= strlen(up->name);
                char *start = strchr(uc->filename, ',');
                *(const AVClass **)uc->priv_data = up->priv_data_class;
                // 如果没搞清楚URLContext.priv_data真正的类型
                // 这个函数的调用你就会懵逼
                av_opt_set_defaults(uc->priv_data);
                // 这儿是对subfile这个协议的特例处理
                if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == 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;
    
        *puc = uc;   // 成功,将URLConext对象地址给puc
        return 0;    // 成功,返回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.  参数合法性验证:主要是传入的标识flags与URLProtocol之间的验证,有3点:
            1)协议URLProtocol是否需要使用网络,初始化网络库,ff_network_init()源码如下所示
            2)flags中是否要求URLProtocol进行读操作,而URLProtocol是否有读函数URLProtocol.url_read;
            3)  flags中是否要求URLProtocol进行读操作,而URLProtocol是否有读函数URLProtocol.url_write;
    int ff_network_init(void)
    {
    #if HAVE_WINSOCK2_H      // windows系统的winsock2库
        WSADATA wsaData;
    
        if (WSAStartup(MAKEWORD(1,1), &wsaData)) //调用该库的初始化函数WSAStartup()
            return 0;  // 初始化失败则返回0
    #endif
        return 1;      // 其他网络库不需要初始化,因此直接返回1
    }
  2.  创建URLContext对象,分配存储空间:av_mallocz(sizeof(URLContext) + strlen(filename) + 1);  注意这儿分配的大小不是sizeof(URLContext)!!!为的是将filename这个字符串直接拷贝到紧挨着URLContext的后续内存中。
  3.  初始化URLContext成员:此处进行初始化的成员有URLContext.av_class,.filename,.prot,.flags,.is_streamed,.max_packet_size,.priv_data,.interrupt_callback 。这儿只关注字段.filename, .prot,.priv_data。其他字段见(FFMPEG4.1源码分析之 URLProtocol结构体 && URLContext以及相关函数)
             1)URLContext.filename: 注意源码实现,首先将filename指针指向(char *)&uc[1],这个操作使得其指向了uc之后的空间,然后拷贝filename字符串到这儿。与之前空间分配相呼应
             2)URLContext.prot: 将之前查找到的URLPotocol对象挂在这个成员之下,建立这么个观点:URLContext上下文持有对应的URLPotocol对象
             3)URLContext.priv_data:这个字段的初始化过程可以分几个阶段来解读
                   3.1)分配空间:uc->priv_data = av_mallocz(up->priv_data_size); 该字段的空间大小居然取决的是URLProtocol.priv_data_size字段;
                   3.2)AVClass*字段地址指派:*(const AVClass **)uc->priv_data = up->priv_data_class; 这句话是比较难以理解的事,这么来说吧,先将uc->priv_data看成是一个如同AVFormatContext这类具有AVClass*成员的结构体,该AVClass*结构体是该结构体的第一个成员,因此其地址与uc->priv_data地址相同,那么上述的语句就是将uc->priv_data->av_class指向了up->priv_data_class对象。
                  3.3)初始化.priv_data字段的成员字段: 通过调用av_opt_set_defaults(uc->priv_data);来实现这个功能,由于3.2,uc->priv_data->av_class已经被设置,那么就很好理解了,如果不知道原理可以查看 FFMPEG4.1源码分析之 av_opt_set_defaults()函数这篇文章,已经有对av_opt_set_defaults()很详细的分析了,此处就不再赘述。
                  3.4)特殊处理subfile协议:前文就已经论述过subfile协议的url格式为"subfile,,start,32815239,end,0,,:video.ts",简要的说下这个特殊处理过程所作事情:
                        3.4.1)提取url中的start,end的值,分别为32815239,0;
                        3.4.2)通过ret= av_opt_set(uc->priv_data, p, key+1, 0)将start,end值赋值给.priv_data字段所代表的结构体的start成员字段和end成员字段。
                        3.4.3)咦?!!!发现没,之前整个过程,我们根本就不知道.priv_data字段的结构体是啥,哪儿来的start成员字段,哪儿来的end成员字段?查看.priv_data的类型为void*,看到void*这个类型,同时通过上述分析得知其代表了一个具体的结构体(对于subfile协议来说该结构体具有start成员和end成员),那么为啥不直接声明成这个具体结构体类型呢?意味着这么一点:.priv_data字段需要根据不同情形成为不同的结构体类型。
                        3.4.4)正好,我们针对subfile协议进行分析:uc->priv_data 字段的大小取决于URLProtocol.priv_data_size,那么就以此为突破口吧。首先,我们需要找到subfile对应的URLProtocol,所有的协议均在libavformat/protocol_list.c文件中找到,subfile协议的URLProtocol对象为ff_subfile_protocol,定义在libavformat/subfile.c中,如下源码所示。
    const URLProtocol ff_subfile_protocol = {
        .name                = "subfile",
        .url_open2           = subfile_open,
        .url_read            = subfile_read,
        .url_seek            = subfile_seek,
        .url_close           = subfile_close,
        .priv_data_size      = sizeof(SubfileContext),
        .priv_data_class     = &subfile_class,
        .default_whitelist   = "file",
    };
    ff_subfile_protocol.priv_data_size取值为sizeof(SubfileContext),因此可以推知URLContext.priv_data结构体为SubfileContext这个结构体,如下所示。果然这个结构体存在start字段和end字段。是不是可以解惑了?
    稍微再深挖一点:之前给URLContext.priv_data的首个字段.av_class指定到了URLProtocol.priv_data_class。对于subfile协议来说,就是上面源码对应的subfile_class这个静态结构体,如下源码所示,其.option属性是subfile_options,如下源码所示。来看看,是不是又印证了之前使用av_opt_set_defaults()来给URLContext.priv_data设置默认值,使用av_opt_set()来设置URLContext.priv_data成员的start和end的逻辑? 至此,该段终于解析完毕。
    typedef struct SubfileContext {
        const AVClass *class;
        URLContext *h;
        int64_t start;
        int64_t end;
        int64_t pos;
    } SubfileContext;
    
    static const AVClass subfile_class = {
        .class_name = "subfile",
        .item_name  = av_default_item_name,
        .option     = subfile_options,
        .version    = LIBAVUTIL_VERSION_INT,
    };
    
    static const AVOption subfile_options[] = {
        { "start", "start offset", OFFSET(start), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
        { "end",   "end offset",   OFFSET(end),   AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, D },
        { NULL }
    };
  4.  出错处理:释放URLContext所占用的空间,包括其私有数据字段;ff_network_close()函数卸载网络库,其源码如下:
    void ff_network_close(void)
    {
    #if HAVE_WINSOCK2_H   // 如果网络库是winsocket库,则需要使用WSACleanup();进行清理
        WSACleanup();
    #endif
    }

1.1.1.2 ffurl_connect()


ffurl_connect() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/url.h
  • 声明:连接使用ffurl_alloc所创建的URLContext,这个说明比较抽象,具体分析见后文源码分析。
    /**
     * Connect an URLContext that has been allocated by ffurl_alloc
     *
     * @param options  A dictionary filled with options for nested protocols,
     * i.e. it will be passed to url_open2() for protocols implementing it.
     * This parameter will be destroyed and replaced with a dict containing options
     * that were not found. May be NULL.
     */
    int ffurl_connect(URLContext *uc, AVDictionary **options);

ffurl_connect() 源码:

  • 所属库:libavformat(lavf
  • 源文件:libavformat/avio.c
  • 源码:
    int ffurl_connect(URLContext *uc, AVDictionary **options)
    {
        int err;
        AVDictionary *tmp_opts = NULL;
        AVDictionaryEntry *e;
    
        if (!options)
            options = &tmp_opts;
    
        // 检查URLContext中的黑白名单是否和options中的一致
        // 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);
        }
    
        // 如果URLContext上下文中的白名单为空,并且URLProtocol中的默认白名单不为空,那么
        // 复制URLProtocol的默认白名单到URLContext上下文的白名单中
        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
    
        // 将黑白名单设置回options,仍保持二者一致
        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;
    
        // 使用URLProtocol的url_open2函数或者url_open函数来打开该url
        err =
            uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                      uc->filename,
                                                      uc->flags,
                                                      options) :
            uc->prot->url_open(uc, uc->filename, uc->flags);
    
        // 给options的黑白名单置空
        av_dict_set(options, "protocol_whitelist", NULL, 0);
        av_dict_set(options, "protocol_blacklist", NULL, 0);
    
        if (err)
            return err;
        
        // open成功则表示已连接上,设置标志位
        uc->is_connected = 1;
    
        // 如果协议支持写入或者是协议是file协议,即本地文件
        /* 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"))
            // 协议是非流协议并且不能seek,那么也当作流协议来处理,is_streamed标志置位。
            if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
                uc->is_streamed = 1;
        return 0;
    }

    1. 验证参数的有效性
        1)验证入参URLContext和选项options中的黑白名单是否一整;
        2)验证入参URLContext对应的协议是否在黑白名单中(若白名单存在,则必须在白名单中,若黑名单存在则一定不能在黑名单中);
       3)如果白名单不存在,那么复制URLProtocol的默认白名单到URLContext的白名单中
       4)由于3)的存在会修改白名单,因此,将黑白名单从URLContext再复制回options中,后续还要使用
    2. 打开url
        这个操作是通过调用URLProtocol的url_open2或者是url_open函数真正的处理url打开,对于不同的协议URLProtocol实体是不一样的,因此,后续将针对某个协议,比如常见的rtmp协议进行分析。
    3. 修改URLContext的成员值
        1)修改URLContext.is_connected = 1
        2)  修改URLContext.is_streamed = 1,这种修改是在满足如下几个条件下才设置的:协议支持写入或者协议是file协议(即本地文件),但不支持按字节seek(ffurl_seek函数内部是按字节seek,而非按时间戳seek)

1.1.1.2.1 url_open2() && url_open()


url_open2()或者是url_open()函数是URLProtocol的成员,不同协议的URLProtocol实体不同,因此url_open函数的内容不尽相同,但是作用是类似的。接下来,以常见的RTMP协议来进行分析。
rtmp协议的URLProtocol是名为ff_rtmp_protocol的常量,其定义在rtmpproto.c文件中,详见如下源码:

#define RTMP_PROTOCOL(flavor)                    \
static const AVClass flavor##_class = {          \
    .class_name = #flavor,                       \
    .item_name  = av_default_item_name,          \
    .option     = rtmp_options,                  \
    .version    = LIBAVUTIL_VERSION_INT,         \
};                                               \
                                                 \
const URLProtocol ff_##flavor##_protocol = {     \
    .name           = #flavor,                   \
    .url_open2      = rtmp_open,                 \
    .url_read       = rtmp_read,                 \
    .url_read_seek  = rtmp_seek,                 \
    .url_read_pause = rtmp_pause,                \
    .url_write      = rtmp_write,                \
    .url_close      = rtmp_close,                \
    .priv_data_size = sizeof(RTMPContext),       \
    .flags          = URL_PROTOCOL_FLAG_NETWORK, \
    .priv_data_class= &flavor##_class,           \
};


RTMP_PROTOCOL(rtmp)
RTMP_PROTOCOL(rtmpe)
RTMP_PROTOCOL(rtmps)
RTMP_PROTOCOL(rtmpt)
RTMP_PROTOCOL(rtmpte)
RTMP_PROTOCOL(rtmpts)

1.  RTMP_PROTOCOL宏相当于定义了协议相关的两个结构体实例,以rtmp为例:一个是AVClass类型的rtmp_class;一个是URLProtocol类型的ff_rtmp_protocol
2. rtmp协议存在多个变种协议:
    rtmpe:rtmp的加密版本,基于Adobe私有加密协议。
    rtmps:rtmp的加密版本,基于TLS/SSL加密。
    rtmpt:将rtmp协议包封装在http协议之上,用以穿越firewall,通常传输接口也变为80或者是443
    rtmpte:rtmp协议+Adobe私有加密协议+http协议。
    rtmpts:rtmp协议+TLS/SSL加密协议+http协议。

当前,我们只关注url_open2() && url_open()函数,由上源码可知,其实就是rtmp_open()函数。由于rtmp_open函数的代码太长,本文不在此处解析。rtmp_open() 函数中将试图与服务器建立链接,验证url是否可以推送流或者拉取流

1.1.1.2.2 ffurl_seek()


ffurl_seek() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/url.h
  • 声明:功能:将改变URLContext访问的资源中的偏移量,下次的read/write操作将使用该偏移量。
               参数pos:指定新的偏移量
               参数whence:与unix的文件io含义是一样的,可取SEEK_SET,SEEK_END,SEEK_CUR三个。不同之处在于还可有取值AVSEEK_SIZE,此时,函数返回资源的文件大小,而不进行seek操作。
               返回值:失败返回负的错误码,成功返回相对于文件起始位置的偏移量,以字节数来度量。
    ​
    /**
     * Change the position that will be used by the next read/write
     * operation on the resource accessed by h.
     *
     * @param pos specifies the new position to set
     * @param whence specifies how pos should be interpreted, it must be
     * one of SEEK_SET (seek from the beginning), SEEK_CUR (seek from the
     * current position), SEEK_END (seek from the end), or AVSEEK_SIZE
     * (return the filesize of the requested resource, pos is ignored).
     * @return a negative value corresponding to an AVERROR code in case
     * of failure, or the resulting file position, measured in bytes from
     * the beginning of the file. You can use this feature together with
     * SEEK_CUR to read the current file position.
     */
    int64_t ffurl_seek(URLContext *h, int64_t pos, int whence);
    
    ​

ffurl_seek() 源码:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/avio.c
  • 源码:
    int64_t ffurl_seek(URLContext *h, int64_t pos, int whence)
    {
        int64_t ret;
    
        if (!h->prot->url_seek)
            return AVERROR(ENOSYS);
        ret = h->prot->url_seek(h, pos, whence & ~AVSEEK_FORCE);
        return ret;
    }

    由上可见,最终还是调用URLProtocol的 url_seek()函数,对于rtmp协议而言,参考上节内容可知:url_seek是没有被赋值的,是个空指针。因此,对于RTMP协议来说,直接返回错误码AVERROR(ENOSYS)。这儿重点提一下:
    URLProtocol由两个seek函数,分别如下声明:
    1) int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
          表示该资源可以按照字节seek,其参数列表与本地文件IO的seek函数类似;
    2)int64_t (*url_read_seek)(URLContext *h, int stream_index, int64_t timestamp, int flags);
          表示该资源可以按照时间戳seek;
    那么URLContext.is_streamed大多数取决于URLProtocol是否支持按字节seek。

1.1.2 ffio_fdopen


ffio_fdopen() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/avio_internal.h
  • 声明:创建并初始化AVIOContext对象,用以访问URLContext所引用的资源
    /**
     * Create and initialize a AVIOContext for accessing the
     * resource referenced by the URLContext h.
     * @note When the URLContext h has been opened in read+write mode, the
     * AVIOContext can be used only for writing.
     *
     * @param s Used to return the pointer to the created AVIOContext.
     * In case of failure the pointed to value is set to NULL.
     * @return >= 0 in case of success, a negative value corresponding to an
     * AVERROR code in case of failure
     */
    int ffio_fdopen(AVIOContext **s, URLContext *h);

ffio_fdopen() 源码:

  • 所属库:libavformat
  • 源文件:libavformat/aviobuf.c
  • 源码:
    int ffio_fdopen(AVIOContext **s, URLContext *h)
    {
        AVIOInternal *internal = NULL;
        uint8_t *buffer = NULL;
        int buffer_size, max_packet_size;
    
        // 根据max_packet_size来创建缓冲
        // 若用户设置了max_packet_size,则使用用户设置的
        // 若为0(创建URLContext时默认设置为0),则使用
        // 默认的IO缓冲大小->IO_BUFFER_SIZE为32768
        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;
        }
        buffer = av_malloc(buffer_size);
        if (!buffer)
            return AVERROR(ENOMEM);
    
        // 创建AVIOInternal对象
        internal = av_mallocz(sizeof(*internal));
        if (!internal)
            goto fail;
        
        // 将URLContext挂到AVIOInternal上
        internal->h = h;
    
        // 创建AVIOContext对象
        *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,
                                internal, io_read_packet, io_write_packet, io_seek);
        if (!*s)
            goto fail;
    
        // 拷贝URLContext黑白名单到AVIOContext黑白名单
        (*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);
        if (!(*s)->protocol_whitelist && h->protocol_whitelist) {
            avio_closep(s);
            goto fail;
        }
        (*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);
        if (!(*s)->protocol_blacklist && h->protocol_blacklist) {
            avio_closep(s);
            goto fail;
        }
    
        // 是否直接调用底层接口而不使用buffer
        (*s)->direct = h->flags & AVIO_FLAG_DIRECT;
    
        // 根据URLContext.is_streamed来判定是否支持seek操作
        // 注意AVIO_SEEKABLE_NORMAL表征的是可以像对本地文件那样按字节进行seek
        (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
        
        // 设置包最大值和包最小值
        (*s)->max_packet_size = max_packet_size;
        (*s)->min_packet_size = h->min_packet_size;
    
        // 如果URLProtocol存在,那么给如下几个函数指针赋值
        // AVIOContext.read_pause,AVIOContext.read_seek
        // 注意上述两个函数指针,最终还是需要调用URLProtocol的
        // url_read_pause和url_read_seek函数。
        if(h->prot) {
            (*s)->read_pause = io_read_pause;
            (*s)->read_seek  = io_read_seek;
    
            // 当URLProtocol.url_read_seek存在,那么说明可以按照时间戳进行seek
            // 因此AVIOContext.seekable加上可按时间戳进行seek的标记
            if (h->prot->url_read_seek)
                (*s)->seekable |= AVIO_SEEKABLE_TIME;
        }
    
        // 设置short_seek回调 
        (*s)->short_seek_get = io_short_seek;
    
        // 设置AVIOContext的AVClass
        (*s)->av_class = &ff_avio_class;
        return 0;
    fail:
        av_freep(&internal);
        av_freep(&buffer);
        return AVERROR(ENOMEM);
    }

    上述源码可以分三个步骤来看
    1. 为创建AVIOContext——IO上下文对象对象做准备
            1)创建缓冲区:根据URLContext .max_packet_size来判断缓存区大小,由上文的URLContext创建过程可知,max_packet_size默认为0,如无意外,将使用IO_BUFFER_SIZE作为要创建的缓冲区的大小,该值为32768,我们知道linux系统blocksize一般为4096,缓冲区32748 = 8 * blocksize。创建这个大小的缓冲区可达到比较高效的读写性能,见<<unix环境高级编程>> 3.9节 I/O的效率。
            2)创建AVIOInternal对象:该对象只是简单的持有URLContext指针,源码如下

    typedef struct AVIOInternal {
        URLContext *h;
    } AVI

    2. 使用avio_alloc_context()函数创建AVIOConext对象:后文分析该函数的实现。
    3. 修改AVIOConext成员值:
         
    1)AVIOContext.whitelist && AVIOContext.blacklist:拷贝URLContext的黑白名单
         2)AVIOContext.direct:
                   2.1)首先,看下这个字段代表的含义:该字段将影响avio_read(),avio_write(),avio_seek(),这几个函数将使得这3个函数不会使用AVIOContext内部的buffer,并且avio_seek将直接调用底层seek函数而不再在AVIOContext的buffer中seek;
                   2.2)其次,direct取值取决于URLContext .flags,而这个flags参数是由最开始io_open_default()函数一路传递进来赋值给URLContext构建函数的。

        /**
         * avio_read and avio_write should if possible be satisfied directly
         * instead of going through a buffer, and avio_seek will always
         * call the underlying seek function directly.
         */
        int direct;

         3)AVIOContext.protocol_whitelist && protocol_blacklist:取自URLContext.protocol_whitelist && protocol_blacklist
         4)AVIOContext.seekable:该字段表征该资源是否可seek,可seek分两种,按字节seek,按时间戳seek
                   4.1)若不可seek则为0;
                   4.2)可按字节seek,seekable的AVIO_SEEKABLE_NORMAL置位:标志支持按字节seek,就像对本地文件进行seek一样。取决于URLContext.is_streamed字段是否为1,而该值如前文分析,又取决于:协议支持写入或者协议是file协议(即本地文件),但不支持按字节seek(最终却决于URLProtocol.url_seek函数指针是否为空)。

    /**
     * Seeking works like for a local file.
     */
    #define AVIO_SEEKABLE_NORMAL (1 << 0)

                  4.3)可按时间戳seek,seekable的AVIO_SEEKABLE_TIME置位:标志支持按时间戳seek。取决于URLProtocol.url_read_seek是否存在。

    /**
     * Seeking by timestamp with avio_seek_time() is possible.
     */
    #define AVIO_SEEKABLE_TIME   (1 << 1)

                  4.4)注意这两个宏定义并不冲突,即一个资源即可以支持按字节seek,也可以按照时间戳seek。
         5)AVIOContext.read_pause:赋值io_read_pause,该函数源码如下,可见最终还是调用的URLProtocol的url_read_pause函数。

    static int io_read_pause(void *opaque, int pause)
    {
        AVIOInternal *internal = opaque;
        if (!internal->h->prot->url_read_pause)
            return AVERROR(ENOSYS);
        return internal->h->prot->url_read_pause(internal->h, pause);
    }

         6)AVIOContext.read_seek:赋值io_read_seek,该函数源码如下,可以最终还是调用URLProtocol的url_read_seek()函数,该函数表征的是该协议是否支持按时间戳seek

    static int64_t io_read_seek(void *opaque, int stream_index, int64_t timestamp, int flags)
    {
        AVIOInternal *internal = opaque;
        if (!internal->h->prot->url_read_seek)
            return AVERROR(ENOSYS);
        return internal->h->prot->url_read_seek(internal->h, stream_index, timestamp, flags);
    }

         7)  AVIOContext.short_seek_get:赋值io_short_seek,最终还是调用的URLProtocol的url_get_short_seek函数。当前没有从基本概念上理解short_seek_get是什么,做什么用。

    static int io_short_seek(void *opaque)
    {
        AVIOInternal *internal = opaque;
        return ffurl_get_short_seek(internal->h);
    }
    
    int ffurl_get_short_seek(URLContext *h)
    {
        if (!h || !h->prot || !h->prot->url_get_short_seek)
            return AVERROR(ENOSYS);
        return h->prot->url_get_short_seek(h);
    }

1.1.2.1 avio_alloc_context()


avio_alloc_context() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/avio.h
  • 声明:
            功能:创建并初始化AVIOContext,并且是带缓冲的io,并且需要使用avio_context_free()来释放该对象。
            入参buffer:AVIOContext的输入输出操作都通过该缓冲区进行,该缓冲区必须使用av_malloc()函数族进行创建,使用 av_free()函数进行释放。该缓冲区可能被释放或者是被一个新的缓冲区替代。当前使用的缓冲区由AVIOContext.buffer字段持有。
            入参buffer_size:缓冲区大小对于IO性能非常重要,对于那些由固定块大小的协议,必须设置为使用该缓冲区大小;一般的典型的大小为缓存页大小4kb
           入参write_flag:如果缓冲区支持写入,则该参数设置为1。
           入参read_packet ,write_packet,seek 将在后文分析。
    /**
     * Allocate and initialize an AVIOContext for buffered I/O. It must be later
     * freed with avio_context_free().
     *
     * @param buffer Memory block for input/output operations via AVIOContext.
     *        The buffer must be allocated with av_malloc() and friends.
     *        It may be freed and replaced with a new buffer by libavformat.
     *        AVIOContext.buffer holds the buffer currently in use,
     *        which must be later freed with av_free().
     * @param buffer_size The buffer size is very important for performance.
     *        For protocols with fixed blocksize it should be set to this blocksize.
     *        For others a typical size is a cache page, e.g. 4kb.
     * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
     * @param opaque An opaque pointer to user-specific data.
     * @param read_packet  A function for refilling the buffer, may be NULL.
     *                     For stream protocols, must never return 0 but rather
     *                     a proper AVERROR code.
     * @param write_packet A function for writing the buffer contents, may be NULL.
     *        The function may not change the input buffers content.
     * @param seek A function for seeking to specified byte position, may be NULL.
     *
     * @return Allocated AVIOContext or NULL on failure.
     */
    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));

avio_alloc_context() 源码:

  • 所属库:libavformat(lavf)
  • 源文件:aviobuf.c
  • 源码: 函数调用av_malloc()创建AVIOContext之后,简单的调用ffio_init_context()对AVIOContext进行初始化,此处不再赘述
    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))
    {
        AVIOContext *s = av_malloc(sizeof(AVIOContext));
        if (!s)
            return NULL;
        ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
                      read_packet, write_packet, seek);
        return s;
    }

1.1.2.1.1 ffio_init_context()


ffio_init_context() 声明:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/avio_internal.h
  • 声明:此处就不多说了,该函数主要是对s的各项成员进行初始化工作
    int ffio_init_context(AVIOContext *s,
                      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));

ffio_init_context() 源码:

  • 所属库:libavformat(lavf
  • 头文件:libavformat/aviobuf.c
  • 源码:
    int ffio_init_context(AVIOContext *s,
                      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))
    {
        memset(s, 0, sizeof(AVIOContext));
    
        s->buffer      = buffer;
        s->orig_buffer_size =
        s->buffer_size = buffer_size;
        s->buf_ptr     = buffer;
        s->buf_ptr_max = buffer;
        s->opaque      = opaque;
        s->direct      = 0;
    
        url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);
    
        s->write_packet    = write_packet;
        s->read_packet     = read_packet;
        s->seek            = seek;
        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;
        s->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;
        s->current_type          = AVIO_DATA_MARKER_UNKNOWN;
        s->last_time             = AV_NOPTS_VALUE;
        s->short_seek_get        = NULL;
        s->written               = 0;
    
        return 0;
    }

    1. 使用memset()将分配的AVIOContext 空间全初始化为0;
    2. 设置buffer相关的5个成员:
              2.1)AVIOContext.buffer:指向buffer的指针,由该成员持有当前使用的缓冲区,该缓冲区有可能会被其他缓冲区替换掉,但是.buffer成员会一直指向当前有效的缓冲区(即替换后的缓冲区);
              2.2)AVIOContext.buffer_size:缓冲区大小;
              2.3)AVIOContext.orig_buffer_size:缓冲区的原始大小,设置为buffer_size大小,作用在于进行文件格式探测时,buffer_size的大小会被改变,文件格式探测结束后需要恢复原始的缓冲区大小,那么.orig_buffer_size就起作用了,因为其保存着该原始值。当然,该成员只在libavformat库内部使用,非公共接口,因此,用户层面不要访问该值。
              2.4)AVIOContext.buf_ptr:buffer中的当前位置;该指针在读,写过程中均起作用;
              2.5)AVIOContext.buf_ptr_max: 仅有用在写过程中,保存着缓存中已经处理过的数据的位置,调用flush函数将使得buffer指针到buf_ptr_max之前的内容被从缓冲中flush掉;
              2.6)与buffer相关的还有两个成员:.pos 与 .buffer_end,在后续过程中会初始化。
    3. AVIOContext.opaque && AVIOContext.direct:已经在前文进行了分析,此处不再赘述
    4. 调用url_resetbuf重置buffer:根据是读还是写,来修改AVIOContext.buffer_end与.write_flag,源码如下:注意一点的是buffer_end这个成员在读,写中含义不一样。

    static int url_resetbuf(AVIOContext *s, int flags)
    {
        av_assert1(flags == AVIO_FLAG_WRITE || flags == AVIO_FLAG_READ);
    
        if (flags & AVIO_FLAG_WRITE) {
            s->buf_end = s->buffer + s->buffer_size;
            s->write_flag = 1;
        } else {
            s->buf_end = s->buffer;
            s->write_flag = 0;
        }
        return 0;
    }

    5.  AVIOContext.write_packet: 这个参数取值为io_write_packet,该函数是aviobuf.c文件中的静态函数。将在后文分析。
    6.  AVIOContext.read_packet:  这个参数取值为io_read_packet,该函数是aviobuf.c文件中的静态函数。将在后文分析。
    7.  AVIOContext.seek:这个参数取值为io_seek,该函数是aviobuf.c文件中的静态函数。将在后文分析。
    8. 后续的字段就不再一一赘述,以后会写文章专门分析AVIOContext结构及其各字段的含义,后续主要分析5,6,7三个字段的含义。

    此处特别提出一点:由ffio_fdopen(),avio_alloc_context(),ffio_init_context()分析可以得知,AVIOContext结构体的几个函数指针成员read_packet,write_packet,seek,read_pause,read_seek,short_seek_get赋值的都是在libavformat/aviobuf.c 文件中的对应的以io_开头的静态函数:io_read_packet,io_write_packet,io_seek,io_read_pause,io_read_seek,io_short_seek。这些函数最终会调用到URLProtocol中对应的url_开头的函数。
    这些函数将在 
    FFMPEG4.1源码分析之 IO操作相关Structures && APIs 进行分析。

1.1.3 ffurl_close()


ffurl_close() 声明:

  • 所属库:libavformat(lavf)
  • 头文件:libavformat/url.h
  • 声明:这两个函数所起的作用是关闭URLContext所访问的资源;释放URLContext所占用的内存空间,并且将指向URLContext的指针置为空。
    /**
     * Close the resource accessed by the URLContext h, and free the
     * memory used by it. Also set the URLContext pointer to NULL.
     *
     * @return a negative value if an error condition occurred, 0
     * otherwise
     */
    int ffurl_closep(URLContext **h);
    int ffurl_close(URLContext *h);

ffurl_close() 源码:

  • 所属库:libavformat(lavf)
  • 源文件:libavformat/avio.c
  • 源码:ffurl_close函数简简单单就调用了ffurl_closep函数,注意传入的参数是&h,可以解决悬空指针问题,即最终h=NULL
    int ffurl_close(URLContext *h)
    {
        return ffurl_closep(&h);
    }
    
    int ffurl_closep(URLContext **hh)
    {
        URLContext *h= *hh;
        int ret = 0;
        if (!h)
            return 0;     /* can happen when ffurl_open fails */
    
        // 如果is_connected被置位,即url_open2或者url_open函数执行成功
        // 并且URLProtocol的url_close函数存在,则调用url_close关闭URLContext
        // 访问的资源
        if (h->is_connected && h->prot->url_close)
            ret = h->prot->url_close(h);
    
        // 如果配置了访问访问,并且协议是需要网络访问的,那么意味着初始化了网络库
        // 因此需要调用ff_network_close()函数来关闭网络库
    #if CONFIG_NETWORK
        if (h->prot->flags & URL_PROTOCOL_FLAG_NETWORK)
            ff_network_close();
    #endif
    
        // 释放URLContext所占用的空间,并且使用av_freep函数来将指针置空
        if (h->prot->priv_data_size) {
            if (h->prot->priv_data_class)
                av_opt_free(h->priv_data);
            av_freep(&h->priv_data);
        }
        av_opt_free(h);
        av_freep(hh);
        return ret;
    }

    上述函数还是比较简单的,源码上进行了注释,过程就不再赘述。此处再关注下网络库清理函数 ff_network_close()。源码如下:发现,如果是windows环境,使用的是winsock网络库,那么需要调用WSACleanup();的windows api来卸载网络库

    void ff_network_close(void)
    {
    #if HAVE_WINSOCK2_H
        WSACleanup();
    #endif
    }

2 io_close_default()


io_close_default() 源码:

  • 所属库:libavformat(lavf
  • 头文件:无,静态函数
  • 源文件:libavformat/options.c
  • 作用:打开URL,创建AVIOContext对象
static void io_close_default(AVFormatContext *s, AVIOContext *pb)
{
    avio_close(pb);
}

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值