ffurl_alloc主要包括url_find_protocol和url_alloc_for_protocol两个函数,前者根据filename查找匹配的url协议类型,后者根据找到的URLProtocol创建对应的URLContext。
url_find_protocol 函数根据文件名filename (准确来说可以叫做url),函数如下
static const struct URLProtocol *url_find_protocol(const char *filename)
{
const URLProtocol **protocols;
char proto_str[128], proto_nested[128], *ptr;
size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
int i;
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)));
if ((ptr = strchr(proto_str, ',')))
*ptr = '\0';
av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
if ((ptr = strchr(proto_nested, '+')))
*ptr = '\0';
protocols = ffurl_get_protocols(NULL, NULL); //获得已经注册的所有url protocols
if (!protocols)
return NULL;
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;
}
这个函数比较简单,首先是截取到url开头的字符串获得url类型。比如 rtp://xxxxx/yyyyy就会截取到”rtp”,于是可以获得协议的类型,将描述协议类型的字符串如“rtp”,“http”等拷贝到proto_str中,接下来进行比对。需要注意的是,如果输入的是普通的磁盘上的文件,这里的filename就是文件的路径和文件名了,此时提取不到url协议的类型,ffmpeg将文件也当做一种特殊的url协议,即“file”协议。如果是普通的磁盘上的文件输入,则把proto_str赋值为”file”。 接下来就是遍历所有的ffmpeg支持的url协议类型,将和我们的proto_str匹配的协议类型的URLProtocol指针返回。 找到对应的输入url的协议类型后,调用url_alloc_for_protocol函数创建对应的URLContex,并进行初始化,如下
static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
URLContext *uc;
int err;
#if CONFIG_NETWORK
if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
return AVERROR(EIO);
#endif
if ((flags & AVIO_FLAG_READ) && !up->url_read) {
av_log(NULL, AV_LOG_ERROR,
"Impossible to open the '%s' protocol for reading\n", up->name);
return AVERROR(EIO);
}
if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
av_log(NULL, AV_LOG_ERROR,
"Impossible to open the '%s' protocol for writing\n", up->name);
return AVERROR(EIO);
}
uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
if (!uc) {
err = AVERROR(ENOMEM);
goto fail;
}
uc->av_class = &ffurl_context_class;
uc->filename = (char *)&uc[1];
strcpy(uc->filename, filename);
uc->prot = up;
uc->flags = flags;
uc->is_streamed = 0; /* default = not streamed */
uc->max_packet_size = 0; /* default: stream file */
if (up->priv_data_size) {
uc->priv_data = av_mallocz(up->priv_data_size);
if (!uc->priv_data) {
err = AVERROR(ENOMEM);
goto fail;
}
if (up->priv_data_class) {
int proto_len= strlen(up->name);
char *start = strchr(uc->filename, ',');
*(const AVClass **)uc->priv_data = up->priv_data_class;
av_opt_set_defaults(uc->priv_data);
// subfile协议,比如下面一行所描述的filename
/*
subfile,,start,153391104,end,268142592,,:/media/dvd/VIDEO_TS/VTS_08_1.VOB
*/
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;
}
// subfile:/media/dvd/VIDEO_TS/VTS_08_1.VOB
memmove(start, key+1, strlen(key));
}
}
}
if (int_cb)
uc->interrupt_callback = *int_cb;
*puc = uc;
return 0;
fail:
*puc = NULL;
if (uc)
av_freep(&uc->priv_data);
av_freep(&uc);
#if CONFIG_NETWORK
if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
ff_network_close();
#endif
return err;
}
函数首先判断相关的flag所描述的需求是否满足,比如flag有AVIO_FLAG_READ,那么对应找到的URLProtocol必须要有url_read函数。后面是对创建的URLContext进行初始化,如av_class 、filename、 prot等等,其中prot就是之前我们找到的匹配的URLProtocol。接下来的if (up->priv_data_size){xx}是对priv_data的初始化,设定默认的参数值。后面if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start) 这个if语句块是对于“subfile”协议进行start和end值得设置,并且将正确的文件名前移,形成类似“subfile:abc.vob” 这样格式的filename。
subfile协议url的一个例子我上面注释中给了一个,具体的subfile协议可以参考subfile协议