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

news2024/10/6 3:27:22

目录

  • 1.URLProtocol类
  • 2.协议匹配的基础接口
  • 3. URLContext类
  • 4. 综合调用流程图
  • 5.rtsp拉流协议匹配流程图及对象图
    • 5.1 rtsp拉流协议调用流程图
    • 5.2 rtsp拉流协议对象图
  • 6.本地文件调用流程图及对象图
    • 6.1 本地文件调用流程图
    • 6.2 本地文件对象图
  • 7.内存数据调用流程图及对象图
    • 7.1 内存数据调用流程图
    • 7.2 内存数据对象图
  • 9 filename取值规则
  • 8.小结

如果让你写个拉流程序,输入拉流地址,可以是本地文件路径,可以是内存数据,可以网络流媒体传输协议比如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统一输入格式的方式一样。这个可以说是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。
再遍历比较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 是个关键调用——它是内存数据、本地文件或者流媒体协议最终都会调用的

具体如何调用的,见如下综合调用流程。

在这里插入图片描述
到此,协议匹配探索到源头,算是结束。

但是这个综合图把所有情况都考虑到了,但是这样很不好,很不清晰,鼻子眉毛一把抓,非常不清晰,不易于理解。应该分解下,根据业务功能单独画。

对于庞大代码,业务功能融合多的代码,要用业务线索来看——从单一的业务功能点为聚焦点,这样别的业务代码不会迷了眼,就像《ffmpeg面向对象——参数配置机制及其设计模式探索》一样,只聚焦于参数流向,无关代码,让开。

因此,按业务功能点可以拆分成网络流媒体协议匹配、本地文件路径匹配和内存数据匹配等3大块。网络流媒体协议常用的rtsp等是个代表。

5.rtsp拉流协议匹配流程图及对象图

5.1 rtsp拉流协议调用流程图

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

在这里插入图片描述
剔除了其他业务流程,瞬间干净清爽许多。
可以看到rtsp的协议匹配触发在输入格式的方法里。

5.2 rtsp拉流协议对象图

其对象图如下:
在这里插入图片描述

图上最下面的就是这个协议管理类URLContext 了(对象图上已标注出其匹配到的全局变量了)。

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

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

6.1 本地文件调用流程图

待探索。

6.2 本地文件对象图

待探索。

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

7.1 内存数据调用流程图

待探索。

7.2 内存数据对象图

待探索。

9 filename取值规则

ffmpeg封装的基础接口是url_find_protocol的形参filename取值范围,前面没说,为了过流程,就忽略了。

拿url_protocols表格中ffmpeg支持的几个url协议上,探索下这个filename的取值范围是啥。

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

const URLProtocol ff_hls_protocol = {
    .name           = "hls",
    .url_open       = hls_open,
    .url_read       = hls_read,
    .url_close      = hls_close,
    .flags          = URL_PROTOCOL_FLAG_NESTED_SCHEME,
    .priv_data_size = sizeof(HLSContext),
};

const URLProtocol ff_tcp_protocol = {
    .name                = "tcp",
    .url_open            = tcp_open,
    .url_accept          = tcp_accept,
    .url_read            = tcp_read,
    .url_write           = tcp_write,
    .url_close           = tcp_close,
    .url_get_file_handle = tcp_get_file_handle,
    .url_get_short_seek  = tcp_get_window_size,
    .url_shutdown        = tcp_shutdown,
    .priv_data_size      = sizeof(TCPContext),
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
    .priv_data_class     = &tcp_class,
};

const URLProtocol ff_udp_protocol = {
    .name                = "udp",
    .url_open            = udp_open,
    .url_read            = udp_read,
    .url_write           = udp_write,
    .url_close           = udp_close,
    .url_get_file_handle = udp_get_file_handle,
    .priv_data_size      = sizeof(UDPContext),
    .priv_data_class     = &udp_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
};

从这些支持的协议类型看,filename的取值范围其实就是在这些协议类对象里,那么代码里怎么知道是哪个呢?不同协议类型的name是不一样的,比如上面的有"udp"、“tcp”、"file"等等,就是filename传递进来的时候肯定是有处理过的,那么在哪里处理比价合理呢?在各个业务功能模块里比较合理。比如rtsp拉流协议的输入格式AVInputFormat(网上称之解复用器,其实我觉得叫复合数据分离器比较贴切,demux我一直翻译成分离器比较容易理解,一分多,mux融合数据,多合一)里rtsp_read_header这个回调里调用的ff_rtsp_connect中,进行了处理:
在这里插入图片描述
可以看到,会把rtsp拉流url比如“rtsp://ip:port/xxx”给改成“tcp://ip:port/xxx”,这样在url_find_protocol中就能截取到“tcp”从而rtsp的拉流协议会匹配到ff_tcp_protocol。

其他类似,肯定有处理的地方。

8.小结

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

其实应该倒看,章节倒看会比较不错,比如5-4-3-2-1或者6-4-3-2-1或者7-4-3-2-1。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2191026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

李宏毅深度学习-梯度下降和Batch Normalization批量归一化

Gradient Descent梯度下降 ▽ -> 梯度gradient -> vector向量 -> 下图中的红色箭头&#xff08;loss等高线的法线方向&#xff09; Tip1: Tuning your learning rates Adaptive Learning Rates自适应lr 通常lr会越来越小 Adaptive Learning Rates中每个参数都给它不…

基于依赖注入技术的.net core WebApi框架创建实例

依赖注入&#xff08;Dependency Injection, DI&#xff09;是一种软件设计模式&#xff0c;用于实现控制反转&#xff08;Inversion of Control, IoC&#xff09;。在ASP.NET Core中&#xff0c;依赖注入是内置的核心功能之一。它允许你将应用程序的组件解耦和配置&#xff0c…

【JAVA开源】基于Vue和SpringBoot的服装生产管理系统

本文项目编号 T 066 &#xff0c;文末自助获取源码 \color{red}{T066&#xff0c;文末自助获取源码} T066&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

【LVGL进阶日记】① 开源LVGL在MCU上的移植

关注+星标公众号,不错过精彩内容 作者 | 量子君 微信公众号 | 极客工作室 【LVGL进阶日记】专栏目录 第一章 开源LVGL在MCU上的移植 文章目录 前言一、LVGL介绍1.1 LVGL的主要特性如下:1.2 LVGL对MCU的要求如下:二、移植LittlevGL到MCU2.1 LVGL源码下载和文件组织2.2 LVGL配…

【AI人工智能】文心智能体,你的情诗小助理,哄女朋友必备, 低代码工作流易上手,干货满满,不容错过哦

&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 &#x1f601; 2. 毕业设计专栏&#xff0c;毕业季咱们不慌忙&#xff0c;几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…

数字图像处理项目——基于Unet网络实现MRI图像的双肺区域分割(论文/代码)

完整的论文代码见文章末尾 以下为核心内容 摘要 在医学图像处理领域&#xff0c;肺部图像的分割是一个重要的研究方向&#xff0c;特别是针对肺部疾病的检测与诊断。传统的X射线和CT&#xff08;计算机断层扫描&#xff09;是常用的肺部成像技术&#xff0c;但MRI&#xff08…

I/O多路转接

目录 一、select 1.1、select概念 1.2、select 函数原型 1.3、理解 select 执行过程 1.4、select就绪条件 1.4.1、读就绪 1.4.2、写就绪 1.4.3、异常就绪&#xff08;了解&#xff09; 1.5、select 基本工作流程 1.6、select服务器 1.6.1、Sock.hpp 1.6.2、selectS…

python实战四:输入一个年份,判断是否是闰年

问题&#xff1a; 从键盘获取一个四位的整数年份&#xff0c;判断其是否是闰年。闰年的判断条件为︰能被4整除但不能被100整除&#xff0c;或者能被400整除。 需求方法&#xff1a; 使用 input() 函数从键盘获取输入。输入的年份是一个字符串。检查输入是否为四位数&#xf…

Elasticsearch学习笔记(四) Elasticsearch集群安全配置一

继续我们的实验。先谈一下我对Elasticsearch粗浅的一些认识&#xff0c;首先Elasticsearch是一个非常宏大的技术栈&#xff0c;发展到今天围绕着Elasticsearch已经产生了更多的组件、套件。因此在看官方文档或者别人的一些教程的时候经常会遇到ELK,elastic stack等。elastic st…

如何在电脑上浏览手机界面

联想浏览器中&#xff0c;点击右键-》检查&#xff0c;进入开发者工具&#xff1a; 点击如上&#xff0c;红色框框选中的手机浏览模式即可。

【微服务】服务注册与发现、分布式配置管理 - Nacos

概述 Nacos是阿里巴巴旗下的一个开源产品&#xff0c;目前市场使用率还是比较高的。在最初开源时&#xff0c;Nacos选择内部三个产品合并并统一开源&#xff0c;这三个产品分别是&#xff1a;非持久化注册中心&#xff08;Configserver&#xff09;、持久化注册中心&#xff0…

InnoDB 事务模型

文章目录 InnoDB 事务模型事务ACID特性事务隔离级别 事务操作事务并发问题事务数据读写类型Consistent Nonlocking Reads 快照读Locking Reads 加锁读 MVCC 并发控制实现原理InnoDB 隐藏列Read ViewUndo log实现过程 MVCC与隔离级别MVCC和辅助索引 幻读可重复读MVCC会出现幻读的…

腾讯自研Git客户端,助力每个人都可以轻松使用Git

工具介绍 UGit是一款腾讯自研的Git客户端&#xff0c;为了让每个人都可以轻松使用Git&#xff0c;从而提高开发效率和团队协作的流畅性。支持工蜂MR/CR&#xff0c;工蜂议题管理&#xff0c;另外对于Git的原生特性有着深度支持。 支持的系统 支持macOS 10.11、Apple Silicon和…

【数据结构】什么是哈希表(散列表)?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;哈希表的概念 &#x1f4cc;哈希函数的构造方法 &#x1f38f;直接定址法 &#x1f38f;除留余数法 &#x1f38f;平方取中法 &#x1f38f;折叠法 &#x…

自动驾驶的技术实现及原理

自动驾驶技术是现代科技领域中一项引人注目的创新&#xff0c;它具有变革运输行业并提升道路安全的潜力。随着人工智能、传感器技术以及数据处理能力的不断提升&#xff0c;自动驾驶车辆已经从实验室研究逐渐走向现实应用。 自动驾驶的技术实现及原理 1. 自动驾驶技术的核心…

【深度学习】— 多层感知机介绍、 隐藏层、从线性到非线性、线性模型的局限性

【深度学习】— 多层感知机介绍 4.1 多层感知机4.1.1 隐藏层线性模型的局限性引入隐藏层 4.2 从线性到非线性线性组合的局限性引入非线性堆叠更多隐藏层 4.1 多层感知机 在第 3 节中&#xff0c;我们介绍了 softmax 回归&#xff0c;并实现了其从零开始的实现和基于高级 API 的…

UART通信协议

什么是UART UART ( Universal Asynchronous Receiver/Transmitter&#xff0c; 通用异步收发器) 是一种常用的串行通信协议&#xff0c;用于在 计算机和外部设备之间传输数据。它是一种异步通信协议&#xff0c;也就是说数据的传输不需要事先建立好同步时钟信号。 UART&#xf…

Unity MVC框架演示 1-1 理论分析

本文仅作学习笔记分享与交流&#xff0c;不做任何商业用途&#xff0c;该课程资源来源于唐老狮 1.一般的图解MVC 什么是MVC我就不说了&#xff0c;老生常谈&#xff0c;网上有大量的介绍&#xff0c;想看看这三层都起到什么职责&#xff1f;那就直接上图吧 2.我举一个栗子 我有…

深入理解 JavaScript 事件循环机制:单线程中的异步处理核心

深入理解 JavaScript 事件循环机制&#xff1a;单线程中的异步处理核心 JavaScript 是一门单线程的编程语言&#xff0c;也就是说它在同一时间只能执行一个任务。然而&#xff0c;现代 Web 应用经常需要处理大量的异步操作&#xff0c;如用户输入、网络请求、定时器等。为了确…

Vue的基本用法及模板语法

Vue.js使用了基于 HTML 的模板语法&#xff0c;允许开发者声明式地将 DOM 绑定至底层 Vue实例的数据。所有 Vue.js的模板都是合法的 HTML&#xff0c;所以能被遵循规范的浏览器和 HTML 解析器解析。 在底层的实现上&#xff0c;Vue将模板编译成虚拟 DOM 渲染函数。结合响应系…