目录
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