FFmpeg源码:url_find_protocol函数分析
一、url_find_protocol函数的定义
url_find_protocol函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/avio.c中:
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)));
av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
if ((ptr = strchr(proto_nested, '+')))
*ptr = '\0';
protocols = ffurl_get_protocols(NULL, NULL);
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);
if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
"openssl, gnutls or securetransport enabled.\n");
return NULL;
}
该函数在avformat_open_input函数内部被调用。其作用是:根据形参filename查找并返回匹配的流媒体协议类型 ;如果没有匹配的协议但filename符合“file”协议的url格式,那就返回“file”协议类型(在FFmpeg源码中,“文件”也被当作一种协议类型);如果都不符合则返回NULL。
形参filename:输入型参数,包含流媒体协议的名称信息。比如RTP对应的filename为:rtp://XXX,UDP对应的filename为:udp://XXX。
返回值:匹配到的流媒体协议类型,为URLProtocol类型。该结构体声明在libavformat/url.h中:
typedef struct URLProtocol {
const char *name;
int (*url_open)( URLContext *h, const char *url, int flags);
/**
* This callback is to be used by protocols which open further nested
* protocols. options are then to be passed to ffurl_open_whitelist()
* or ffurl_connect() for those nested protocols.
*/
int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
int (*url_accept)(URLContext *s, URLContext **c);
int (*url_handshake)(URLContext *c);
/**
* Read data from the protocol.
* If data is immediately available (even less than size), EOF is
* reached or an error occurs (including EINTR), return immediately.
* Otherwise:
* In non-blocking mode, return AVERROR(EAGAIN) immediately.
* In blocking mode, wait for data/EOF/error with a short timeout (0.1s),
* and return AVERROR(EAGAIN) on timeout.
* Checking interrupt_callback, looping on EINTR and EAGAIN and until
* enough data has been read is left to the calling function; see
* retry_transfer_wrapper in avio.c.
*/
int (*url_read)( URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
int (*url_read_pause)(void *urlcontext, int pause);
int64_t (*url_read_seek)(void *urlcontext, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int (*url_get_multi_file_handle)(URLContext *h, int **handles,
int *numhandles);
int (*url_get_short_seek)(URLContext *h);
int (*url_shutdown)(URLContext *h, int flags);
const AVClass *priv_data_class;
int priv_data_size;
int flags;
int (*url_check)(URLContext *h, int mask);
int (*url_open_dir)(URLContext *h);
int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
int (*url_close_dir)(URLContext *h);
int (*url_delete)(URLContext *h);
int (*url_move)(URLContext *h_src, URLContext *h_dst);
const char *default_whitelist;
} URLProtocol;
URLProtocol是FFmpeg协议解析器的注册结构体。每个流媒体协议都有一个与之对应的URLProtocol结构,其定义了协议的名称、流媒体url的打开、读取、写入等操作的函数指针。
比如RTP这种流媒体协议,其对应的URLProtocol结构为:
const URLProtocol ff_rtp_protocol = {
.name = "rtp",
.url_open = rtp_open,
.url_read = rtp_read,
.url_write = rtp_write,
.url_close = rtp_close,
.url_get_file_handle = rtp_get_file_handle,
.url_get_multi_file_handle = rtp_get_multi_file_handle,
.priv_data_size = sizeof(RTPContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &rtp_class,
};
“file”协议对应的URLProtocol结构为:
const URLProtocol ff_file_protocol = {
.name = "file",
.url_open = file_open,
.url_read = file_read,
.url_write = file_write,
.url_seek = file_seek,
.url_close = file_close,
.url_get_file_handle = file_get_handle,
.url_check = file_check,
.url_delete = file_delete,
.url_move = file_move,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &file_class,
.url_open_dir = file_open_dir,
.url_read_dir = file_read_dir,
.url_close_dir = file_close_dir,
.default_whitelist = "file,crypto,data"
};
“rtmp”协议对应的URLProtocol结构为:
const URLProtocol ff_librtmp_protocol = {
.name = "rtmp",
.url_open = rtmp_open,
.url_read = rtmp_read,
.url_write = rtmp_write,
.url_close = rtmp_close,
.url_read_pause = rtmp_read_pause,
.url_read_seek = rtmp_read_seek,
.url_get_file_handle = rtmp_get_file_handle,
.priv_data_size = sizeof(LibRTMPContext),
.priv_data_class = &librtmp_class,
.flags = URL_PROTOCOL_FLAG_NETWORK,
};
二、url_find_protocol函数的内部实现分析
url_find_protocol函数中首先通过下面代码块将形参filename中的流媒体协议名称拷贝到数组proto_str中。如果没有匹配的协议但filename符合“file”协议的url格式,那就拷贝“file”,表示是“文件”协议类型:
if (filename[proto_len] != ':' &&
(strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
is_dos_path(filename))
strcpy(proto_str, "file");
else
av_strlcpy(proto_str, filename,
FFMIN(proto_len + 1, sizeof(proto_str)));
拷贝流媒体协议名称到数组proto_nested中。关于av_strlcpy函数的用法可以参考:《FFmpeg源码:av_strlcpy函数分析》:
av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
if ((ptr = strchr(proto_nested, '+')))
*ptr = '\0';
返回指向全局数组url_protocols开头的二级指针:
protocols = ffurl_get_protocols(NULL, NULL);
if (!protocols)
return NULL;
ffurl_get_protocols函数的定义如下。可以看到其内部遍历了全局数组url_protocols:
const URLProtocol **ffurl_get_protocols(const char *whitelist,
const char *blacklist)
{
const URLProtocol **ret;
int i, ret_idx = 0;
ret = av_calloc(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));
if (!ret)
return NULL;
for (i = 0; url_protocols[i]; i++) {
const URLProtocol *up = url_protocols[i];
if (whitelist && *whitelist && !av_match_name(up->name, whitelist))
continue;
if (blacklist && *blacklist && av_match_name(up->name, blacklist))
continue;
ret[ret_idx++] = up;
}
return ret;
}
全局数组url_protocols定义在libavformat/protocol_list.c中,该数组中的每个元素都对应一种URLProtocol结构,也就是说数组中的每个元素都对应一种流媒体协议(RTP、FILE、RTMP等):
static const URLProtocol * const url_protocols[] = {
&ff_async_protocol,
&ff_cache_protocol,
&ff_concat_protocol,
&ff_concatf_protocol,
&ff_crypto_protocol,
&ff_data_protocol,
&ff_fd_protocol,
&ff_ffrtmphttp_protocol,
&ff_file_protocol,
&ff_ftp_protocol,
&ff_gopher_protocol,
&ff_hls_protocol,
&ff_http_protocol,
&ff_httpproxy_protocol,
&ff_icecast_protocol,
&ff_mmsh_protocol,
&ff_mmst_protocol,
&ff_md5_protocol,
&ff_pipe_protocol,
&ff_prompeg_protocol,
&ff_rtmp_protocol,
&ff_rtmpt_protocol,
&ff_rtp_protocol,
&ff_srtp_protocol,
&ff_subfile_protocol,
&ff_tee_protocol,
&ff_tcp_protocol,
&ff_udp_protocol,
&ff_udplite_protocol,
&ff_unix_protocol,
NULL };
url_find_protocol函数中,通过遍历全局数组url_protocols,比较协议名称,查找出该流媒体协议匹配的URLProtocol结构。如果查找到了,那就赋值给指针up,作为url_find_protocol函数的返回值返回:
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;
}
}
如果查找不到匹配的流媒体协议,那就打印警报日志,返回NULL:
av_freep(&protocols);
if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
"openssl, gnutls or securetransport enabled.\n");
return NULL;