FFmpeg源码:url_find_protocol函数分析

一、url_find_protocol函数的定义

url_find_protocol函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/avio.c中:

cpp 复制代码
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中:

cpp 复制代码
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结构为:

cpp 复制代码
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结构为:

cpp 复制代码
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结构为:

cpp 复制代码
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",表示是"文件"协议类型:

cpp 复制代码
    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函数分析》:

cpp 复制代码
    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';

返回指向全局数组url_protocols开头的二级指针:

cpp 复制代码
    protocols = ffurl_get_protocols(NULL, NULL);
    if (!protocols)
        return NULL;

ffurl_get_protocols函数的定义如下。可以看到其内部遍历了全局数组url_protocols:

cpp 复制代码
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等):

cpp 复制代码
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函数的返回值返回:

cpp 复制代码
    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:

cpp 复制代码
    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;
相关推荐
Antonio9158 小时前
【音视频】ffmpeg命令提取像素格式
ffmpeg·音视频
虾球xz17 小时前
游戏引擎学习第146天
学习·ffmpeg·游戏引擎
.m21 小时前
使用格式工厂提取视频中的音频
ffmpeg
ihmhm123452 天前
2025-03-06 ffmpeg提取SPS/PPS/SEI ( extradata )
ffmpeg
StudyWinter2 天前
FFmpeg-chapter7和chapter8-使用 FFmpeg 解码视频(原理篇和实站篇)
ffmpeg·音视频
T风呤2 天前
ffmpeg windows 基本命令
windows·ffmpeg
firstime_tzjz2 天前
windows下使用msys2编译ffmpeg
windows·ffmpeg
AJi2 天前
FFmpeg学习(五):音视频数据转换
ffmpeg·音视频开发·视频编码
挣扎与觉醒中的技术人2 天前
OpenCV视频解码全流程详解
人工智能·深度学习·opencv·计算机视觉·ffmpeg·音视频
曦月合一3 天前
SSM架构 +Nginx+FFmpeg实现rtsp流转hls流,在前端html上实现视频播放
nginx·架构·ffmpeg·摄像头·实时预览