一、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");elseav_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");elseav_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;