自学内容网 自学内容网

ffmpeg面向对象——拉流协议匹配机制探索

如果让你写个拉流程序,输入拉流地址,可以是本地文件路径,可以是内存数据,可以网络流媒体传输协议比如http或者rtsp等,那么不同拉流地址 ,调用底层的读写函数不一样,如何统一操作呢?探索下ffmpeg是怎么统一这种问题的。

ffmpeg抽象出了url协议类——URLProtocol类——来统一这种操作。

(雷神有画出使用时的各个协议雷神相关博客,但是没有看到其匹配机制,且只有函数调用图,没有对象图。我这个探索,可以算是补充)

1.URLProtocol类


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)(URLContext *h, int pause);
    int64_t (*url_read_seek)(URLContext *h, 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;

这其实就是高级语言(如c++等)的接口类——需要各个子类实现的接口。

可以看到,这类,抽象出来的共性的接口——读写文件等。如果是本地文件,那就调用fopen等系统调用,如果是内存数据则调用用户自定义的读取函数,如果是网络流媒体协议,则是调用socket网络编程接口——这样实现了多态。

因此,ffmpeg实例化了很多它支持的URLProtocol类对象,放到了全局表格url_protocols中,比如

//在libavformat/protocol_list.c
static const URLProtocol *url_protocols[] = {
    &ff_file_protocol,
    &ff_hls_protocol,
    &ff_http_protocol,
    &ff_httpproxy_protocol,
    &ff_rtmp_protocol,
    &ff_rtmpt_protocol,
    &ff_rtp_protocol,
    &ff_srtp_protocol,
    &ff_tcp_protocol,
    &ff_udp_protocol,
    &ff_unix_protocol,
    NULL };

注意:protocol_list.c是configure生成的,也就是说下载的ffmpeg源码中是不存在的,这个是ffmpeg可裁剪的一个举措。——可以配置configure来选择支持哪些协议,和linux的menuconfig类似(不得不觉得互相学习)。

和这篇《ffmpeg面向对象-rtsp拉流相关对象》探索过的输入格式统一模式一样。

这个可以说是ffmpeg的特点的,一个开源项目有其遵循的法则,指定的规则,通一知百。

2.协议匹配的基础接口

ffmpeg封装的基础接口是url_find_protocol。

#define URL_SCHEME_CHARS                        \
    "abcdefghijklmnopqrstuvwxyz"                \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
    "0123456789+-."

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;
}

可以看到,它先判断传入的filename是文件路径还是流传输url,前者把proto_str设为“file”,后者取url的传输协议头比如“tcp”。
然后,遍历比较proto_str和各个协议类对象的name是否相同,这样就从url_protocols指针数组中匹配到对应协议类对象了。

这个匹配函数最后返回对应协议类对象的地址——这个地址给谁了呢?这得看谁调它了,调它的有两处,一个是ffurl_alloc(),一个是avio_find_protocol_name。前者取匹配到的协议对象地址,后者是取协议对象的名字,显而易见,前者是比较重要的。


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;
    return AVERROR_PROTOCOL_NOT_FOUND;
}


可以看到,协议类对象地址作为形参传给url_alloc_for_protocol函数了,看其最终到哪里了?

static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                  const char *filename, int flags,
                                  const AVIOInterruptCB *int_cb)
{
    URLContext *uc;
    int err;
……
    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 */
    ……
}

只截取和本节相关的代码,可以看到最终是放到URLContext类对象的prot成员中——看来URLContext类是URLProtocol 类的管理者。

3. URLContext类

此时对象图如下:

在这里插入图片描述

URLContext协议上下文类来管理匹配到的协议类。其URLContext类定义如下

typedef struct URLContext {
    const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */
    const struct URLProtocol *prot;
    void *priv_data;
    char *filename;             /**< specified URL */
    int flags;
    int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */
    int is_streamed;            /**< true if streamed (no seek possible), default = false */
    int is_connected;
    AVIOInterruptCB interrupt_callback;
    int64_t rw_timeout;         /**< maximum time to wait for (network) read/write operation completion, in mcs */
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int min_packet_size;        /**< if non zero, the stream is packetized with this min packet size */
} URLContext;

可以发现,它第一个成员是AVClass 的指针成员,此时(url_alloc_for_protocol函数处理后),指向了ffurl_context_class,ffmpeg面向对象——参数配置机制及其设计模式探索探索过,凡是戴上这个帽子的,它就成为了可配参数业务类,不再赘述。可见它的可配的参数就3个,
在这里插入图片描述

4. 输入格式中的私有类

那么又是谁调用ffurl_alloc的呢?在这里插入图片描述
看到被调用的地方有很多,但是 ffurl_open_whitelist 是个关键调用——它是内存数据、本地文件或者流媒体协议最终都会调用的

它的调用流程如下:

在这里插入图片描述

5.rtsp拉流协议调用流程图及对象图

5.1 rtsp拉流协议调用流程图

rtsp协议下,它匹配到的协议调用流程如下:

在这里插入图片描述

在rtsp拉流协议下,URLContext作为的输入格式AVInputFormat中私有数据的成员,比如rtsp的私有数据RTSPState。

typedef struct RTSPState {
    const AVClass *class;             /**< Class for private options. */
    URLContext *rtsp_hd; /* RTSP TCP connection handle */

    /** number of items in the 'rtsp_streams' variable */
    int nb_rtsp_streams;

    struct RTSPStream **rtsp_streams; /**< streams in this session */

    /** indicator of whether we are currently receiving data from the
     * server. Basically this isn't more than a simple cache of the
     * last PLAY/PAUSE command sent to the server, to make sure we don't
     * send 2x the same unexpectedly or commands in the wrong state. */
    enum RTSPClientState state;
……
}

5.2 rtsp拉流协议对象图

其对象图如下:
在这里插入图片描述
图上最下面的就是这个协议管理类URLContext 了(对象图上已标注出其匹配到的全局变量了)。

其实应该倒看,章节倒看会比较不错。

到此,协议匹配算是结束。但是,其中的小细节比如匹配的filename怎么变成"tcp"或者“file”的,这个这个“file”讲过了,“tcp”是在rtsp那个ff_rtsp_connect方法里给传递的,如果不是hls就变成“tcp”。

其他有待探索。暂时告一段落。

对象图还缺本地文件,内存数据等,流程图也是。

6.本地文件调用流程图及对象图

6.1 本地文件调用流程图

6.2 本地文件对象图

7.内存数据调用流程图及对象图

7.1 内存数据调用流程图

7.2 内存数据对象图

8.小结

和ffmpeg的输入格式统一到一个类和一个表格一样,这个拉流协议匹配也是统一到一个类和一个表格,且都是configure可裁剪的。及其相似,同类规则,我相信其他模块也是类似。


原文地址:https://blog.csdn.net/yhb1206/article/details/142643064

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!