ffmpeg面向对象——拉流协议匹配机制探索
目录
- 1.URLProtocol类
- 2.协议匹配的核心接口
- 3. URLContext类
- 4. 综合调用流程图
- 5.rtsp拉流协议匹配流程图及对象图
- 5.1 rtsp拉流协议调用流程图
- 5.2 rtsp拉流协议对象图
- 6.本地文件调用流程图及对象图
- 6.1 本地文件调用流程图
- 6.2 本地文件对象图
- 7.内存数据调用流程图及对象图
- 7.1 内存数据调用流程图
- 7.2 内存数据对象图
- 8 filename取值规则
- 9.小结
如果让你写个拉流程序,输入拉流地址,可以是本地文件路径,可以是内存数据,可以网络流媒体传输协议比如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");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;
}
可以看到,它先判断传入的filename是文件路径还是流传输url,前者把proto_str设为“file”,后者取url的传输协议头比如“tcp”设给proto_str。
ffurl_get_protocols主要是获取url_protocols表格里的各个协议类对象地址(内部做了次拷贝,反正不用管)。
再for遍历比较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 内存数据对象图
待探索。
8 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。
其他类似,肯定有处理的地方。
9.小结
和ffmpeg的输入格式统一到一个类和一个表格一样,这个拉流协议匹配也是统一到一个类和一个表格,且都是configure可裁剪的。及其相似,同类规则,我相信其他模块也是类似。
其实应该倒看,章节倒看会比较不错,比如5-4-3-2-1或者6-4-3-2-1或者7-4-3-2-1。
相关文章:

ffmpeg面向对象——拉流协议匹配机制探索
目录 1.URLProtocol类2.协议匹配的核心接口3. URLContext类4. 综合调用流程图5.rtsp拉流协议匹配流程图及对象图5.1 rtsp拉流协议调用流程图5.2 rtsp拉流协议对象图 6.本地文件调用流程图及对象图6.1 本地文件调用流程图6.2 本地文件对象图 7.内存数据调用流程图及对象图7.1 内…...

R语言绘制柱状图
柱状图是一种数据可视化工具。由 x 轴和 y 轴构成,x 轴表示类别,y 轴为数据数值。以矩形柱子展示数据大小,便于直观比较不同类别数据差异及了解分布。广泛应用于销售分析、统计、项目管理、科学研究等领域。可定制颜色、宽度等属性࿰…...

GNU/Linux - tarball文件介绍介绍
Linux 中的 tarball 文件是将多个文件和目录归档到一个文件中的常用方法,通常用于备份、分发或打包目的。术语 “tarball ”来源于 “tar”(磁带归档的缩写)命令的使用,该命令最初设计用于将数据写入磁带等顺序存储设备。如今&…...

AppointmentController
目录 1、 AppointmentController 1.1、 删除预约单据信息 1.2、 反审核预约单 1.3、 SelectToMainten AppointmentController using QXQPS.Models; using QXQPS.Vo; using System; using System.Collections; using System.Collections.Generic; using System.L…...

网站建设完成后,切勿让公司官网成为摆设
在当今这个数字化时代,公司官网已经成为企业展示形象、传递信息、吸引客户的重要平台。然而,许多企业在网站建设完成后,往往忽视了对官网的持续运营和维护,导致官网逐渐沦为摆设,无法发挥其应有的作用。为了确保公司官…...

独孤思维:闲得蛋疼才去做副业
独孤现实中玩的要好的朋友。 他们都只在自己的社交圈,工作圈链接。 没有人知道,副业可以这么玩。 所以他们很好奇,问我,独孤,你最开始是怎么知道这些副业的? 其实,独孤最开始接触副业&#…...

vulnhub靶场之hackablell
一.环境搭建 1.靶场描述 difficulty: easy This works better with VirtualBox rather than VMware 2.靶场下载 https://download.vulnhub.com/hackable/hackableII.ova 3.靶场启动 二.信息收集 1.寻找靶场的真实ip nmap -SP 192.168.246.0/24 arp-scan -l 根据上面两个…...

《浔川社团官方通报 —— 为何明确 10 月 2 日上线的浔川 AI 翻译 v3.0 再次被告知延迟上线》
《浔川社团官方通报 —— 为何明确 10 月 2 日上线的浔川 AI 翻译 v3.0 再次被告知延迟上线》 各位关注浔川社团的朋友们: 大家好!首先,我们要向一直期待浔川 AI 翻译 v3.0 上线的朋友们致以最诚挚的歉意。原定于 10 月 2 日上线的浔川 AI 翻…...

加密与安全_HOTP一次性密码生成算法
文章目录 HOTP 的基础原理HOTP 的工作流程HOTP 的应用场景HOTP 的安全性安全性增强措施Code生成HOTP可配置项校验HOTP可拓展功能计数器(counter)计数器在客户端和服务端的作用计数器的同步机制客户端和服务端中的计数器表现服务端如何处理计数器不同步计…...

ResNet18果蔬图像识别分类
关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有:中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等,曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝,拥有2篇国家级人工智能发明专利。 社区特色…...

深度强化学习中收敛图的横坐标是steps还是episode?
在深度强化学习(Deep Reinforcement Learning, DRL)的收敛图中,横坐标选择 steps 或者 episodes 主要取决于算法的设计和实验的需求,两者的差异和使用场景如下: Steps(步数): 定义&a…...

一个真实可用的登录界面!
需要工具: MySQL数据库、vscode上的php插件PHP Server等 项目结构: login | --backend | --database.sql |--login.php |--welcome.php |--index.html |--script.js |--style.css 项目开展 index.html: 首先需要一个静态网页&#x…...

Vue中watch监听属性的一些应用总结
【1】vue2中watch的应用 ① 简单监视 在 Vue 2 中,如果你不需要深度监视,即只需监听顶层属性的变化,可以使用简写形式来定义 watch。这种方式更加简洁,适用于大多数基本场景。 示例代码 假设你有一个 Vue 组件,其中…...

MongoDB-aggregate流式计算:带条件的关联查询使用案例分析
在数据库的查询中,是一定会遇到表关联查询的。当两张大表关联时,时常会遇到性能和资源问题。这篇文章就是用一个例子来分享MongoDB带条件的关联查询发挥的作用。 假设工作环境中有两张MongoDB集合:SC_DATA(学生基本信息集合&…...

Redis数据库与GO(一):安装,string,hash
安装包地址:https://github.com/tporadowski/redis/releases 建议下载zip版本,解压即可使用。解压后,依次打开目录下的redis-server.exe和redis-cli.exe,redis-cli.exe用于输入指令。 一、基本结构 如图,redis对外有个…...

expressjs,实现上传图片,返回图片链接
在 Express.js 中实现图片上传并返回图片链接,你通常需要使用一个中间件来处理文件上传,比如 multer。multer 是一个 node.js 的中间件,用于处理 multipart/form-data 类型的表单数据,主要用于上传文件。 以下是一个简单的示例&a…...

爬虫——XPath基本用法
第一章XML 一、xml简介 1.什么是XML? 1,XML指可扩展标记语言 2,XML是一种标记语言,类似于HTML 3,XML的设计宗旨是传输数据,而非显示数据 4,XML标签需要我们自己自定义 5,XML被…...

常见排序算法汇总
排序算法汇总 这篇文章说明下排序算法,直接开始。 1.冒泡排序 最简单直观的排序算法了,新手入门的第一个排序算法,也非常直观,最大的数字像泡泡一样一个个的“冒”到数组的最后面。 算法思想:反复遍历要排序的序列…...

Golang | Leetcode Golang题解之第459题重复的子字符串
题目: 题解: func repeatedSubstringPattern(s string) bool {return kmp(s s, s) }func kmp(query, pattern string) bool {n, m : len(query), len(pattern)fail : make([]int, m)for i : 0; i < m; i {fail[i] -1}for i : 1; i < m; i {j : …...

0.计网和操作系统
0.计网和操作系统 熟悉计算机网络和操作系统知识,包括 TCP/IP、UDP、HTTP、DNS 协议等。 常见的页面置换算法: 先进先出(FIFO)算法:将最早进入内存的页面替换出去。最近最少使用(LRU)算法&am…...

探索Prompt Engineering:开启大型语言模型潜力的钥匙
前言 什么是Prompt?Prompt Engineering? Prompt可以理解为向语言模型提出的问题或者指令,它是激发模型产生特定类型响应的“触发器”。 Prompt Engineering,即提示工程,是近年来随着大型语言模型(LLM,Larg…...

滚雪球学Oracle[3.3讲]:数据定义语言(DDL)
全文目录: 前言一、约束的高级使用1.1 主键(Primary Key)案例演示:定义主键 1.2 唯一性约束(Unique)案例演示:定义唯一性约束 1.3 外键(Foreign Key)案例演示:…...

ssrf学习(ctfhub靶场)
ssrf练习 目录 ssrf类型 漏洞形成原理(来自网络) 靶场题目 第一题(url探测网站下文件) 第二关(使用伪协议) 关于http和file协议的理解 file协议 http协议 第三关(端口扫描)…...

ElasticSearch之网络配置
对官方文档Networking的阅读笔记。 ES集群中的节点,支持处理两类通信平面 集群内节点之间的通信,官方文档称之为transport layer。集群外的通信,处理客户端下发的请求,比如数据的CRUD,检索等,官方文档称之…...

【C语言进阶】系统测试与调试
1. 引言 在开始本教程的深度学习之前,我们需要了解整个教程的目标及其结构,以及为何进阶学习是提升C语言技能的关键。 目标和结构: 教程目标:本教程旨在通过系统化的学习,从单元测试、系统集成测试到调试技巧…...

多个单链表的合成
建立两个非递减有序单链表,然后合并成一个非递增有序的单链表。 注意:建立非递减有序的单链表,需要采用创建单链表的算法 输入格式: 1 9 5 7 3 0 2 8 4 6 0 输出格式: 9 8 7 6 5 4 3 2 1 输入样例: 在这里给出一组输入。例如…...

『建议收藏』ChatGPT Canvas功能进阶使用指南!
大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,专注于分享AI全维度知识,包括但不限于AI科普,AI工…...

Ollama 运行视觉语言模型LLaVA
Ollama的LLaVA(大型语言和视觉助手)模型集已更新至 1.6 版,支持: 更高的图像分辨率:支持高达 4 倍的像素,使模型能够掌握更多细节。改进的文本识别和推理能力:在附加文档、图表和图表数据集上进…...

gdb 调试 linux 应用程序的技巧介绍
使用 gdb 来调试 Linux 应用程序时,可以显著提高开发和调试的效率。gdb(GNU 调试器)是一款功能强大的调试工具,适用于调试各类 C、C 程序。它允许我们在运行程序时检查其状态,设置断点,跟踪变量值的变化&am…...

Java项目实战II基于Java+Spring Boot+MySQL的房产销售系统(源码+数据库+文档)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者 一、前言 随着房地产市场的蓬勃发展,房产销售业务日益复杂,传统的手工管理方式已难以满…...