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);
}
 输出一条打开某个url日志。注意一点:此处为啥要对image2图片封装格式做特殊处理呢?目前还不明白这点。
 若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);
 
若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;
}
 使用 ffurl_alloc() 创建一个与URL匹配的URLContext,并初始化;
 1)根据URL中包含的协议名来查找对应的URLProtocol对象;
 2)根据URLProtocol对象以及ffurl_alloc()传入的参数来构建URLContext上下文对象。
 使用 av_opt_copy() 拷贝父URLContext(如果存在的话)的选项到新创建的URLContext;该函数的详细分析见FFMPEG源码分析之 av_opt_copy();
 使用 av_opt_set_dict() 应用用户传入的选项options到URLContext对象:注意,若是options存在URLContext中不存在的选项,则该选项还会留存在options中,而那些应用成功的选项将会从options删除。该函数的详细分析见 FFMPEG源码分析之 av_opt_set_dict();
使用av_opt_set_dict() 应用用户传入的选项options到URLContext.priv_data对象:上一步遗留的options,应用到URLContext.priv_data,应用不成功的选项仍会遗留在options中;
对黑白名单内容进行断言:断言保证用户传入的黑白名单为空,要么与options中的保持一致。FFMPEG中的断言实现见 FFMPEG4.1源码分析之 断言;
使用 av_dict_set() 将黑白名单复制到options,然后使用 av_opt_set_dict() 将黑白名单应用到URLContext:av_dict_set()详细分析见 FFMPEG源码分析之 av_dict_set();
使用 ffurl_connect() 打开URL,如果是需要网络的话,将建立连接;
出错将使用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;
}
 使用url_find_protocol()查找URLProtocol对象;

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

 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;
}
 计算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+-."
计算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
 
获取协议字符串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"。
ffurl_get_protocols() 获取ffmpeg支持的所有URLProtocol,存储在变量 const URLProtocol **protocols中;
迭代protocols中的每个URLProtocol,进行协议名匹配:先将proto_str匹配URLProtocol.name,如果不匹配则对proto_nested匹配URLProtocol.name,只要满足其一,那么就算找到了合适的URLProtocol对象。
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;  // 失败,返回失败码
}
 参数合法性验证:主要是传入的标识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
}
 创建URLContext对象,分配存储空间:av_mallocz(sizeof(URLContext) + strlen(filename) + 1);  注意这儿分配的大小不是sizeof(URLContext)!!!为的是将filename这个字符串直接拷贝到紧挨着URLContext的后续内存中。
 初始化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 }
};
 出错处理:释放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);
}
 
————————————————
版权声明:本文为CSDN博主「ice_ly000」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ice_ly000/article/details/90339330

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值