live555关于RTSP协议交互流程
RTP在和h264
RTP在和h265
RTP载荷AAC
live555关于RTSP协议交互流程
live555的核心数据结构值之闭环双向链表
live555 rtsp服务器实战之createNewStreamSource
概要
rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议;该篇文章主要分析在live555中这些协议是什么时候被创建的,什么时候被使用的等协议相关流程。
TCP:服务器与客户端进行协商(OPTION DESCRIBE SETUP PLAY);
UDP/TCP:协议是rtsp服务器用来想客户端推流;当然rtsp向客户端推流也可以使用tcp协议;那么就rtsp而言使用udp推流和使用tcp推流有什么区别呢?
UDP推流
tcp连接进行rtsp信令交互;
创建新的udp套接字来发送rtp包;
创建新的udp套接字来发送rtcp包;
TCP推流
tcp连接进行rtsp信令交互;
复用rtsp的tcp连接发送rtp和rtcp包;
嵌入式开发一般使用udp推流,实时性相对较高;
RTP:对视频流(h264/h265)/音频流(AAC/MP3)裸流进行封装,用于网络传输;
RTCP:服务器和客户端用来管理流媒体协议;
TCP交互协商
在程序创建RTSPServer类对象时就会创建用于信令协商的TCP协议,见如下代码:
//创建RTSPServer类对象
RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
//createNew实现
RTSPServer*
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,UserAuthenticationDatabase* authDatabase,unsigned reclamationSeconds) {int ourSocketIPv4 = setUpOurSocket(env, ourPort, AF_INET);int ourSocketIPv6 = setUpOurSocket(env, ourPort, AF_INET6);if (ourSocketIPv4 < 0 && ourSocketIPv6 < 0) return NULL;return new RTSPServer(env, ourSocketIPv4, ourSocketIPv6, ourPort, authDatabase, reclamationSeconds);
}
从源码可以看出创建RTSPServer类对象的时候会创建ipv4和ipv6两种套接字,因此理论上来说live555实现的rtsp服务器支持ipv4和ipv6两种网络传输。
//RTSPServer构造函数
RTSPServer::RTSPServer(UsageEnvironment& env,int ourSocketIPv4, int ourSocketIPv6, Port ourPort,UserAuthenticationDatabase* authDatabase,unsigned reclamationSeconds): GenericMediaServer(env, ourSocketIPv4, ourSocketIPv6, ourPort, reclamationSeconds),fHTTPServerSocketIPv4(-1), fHTTPServerSocketIPv6(-1), fHTTPServerPort(0),fClientConnectionsForHTTPTunneling(NULL), // will get created if neededfTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase),fAllowStreamingRTPOverTCP(True),fOurConnectionsUseTLS(False), fWeServeSRTP(False) {
}
//GenericMediaServer构造函数
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort,unsigned reclamationSeconds): Medium(env),fServerSocketIPv4(ourSocketIPv4), fServerSocketIPv6(ourSocketIPv6),fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),fClientSessions(HashTable::create(STRING_HASH_KEYS)),fPreviousClientSessionId(0),fTLSCertificateFileName(NULL), fTLSPrivateKeyFileName(NULL) {ignoreSigPipeOnSocket(fServerSocketIPv4); // so that clients on the same host that are killed don't also kill usignoreSigPipeOnSocket(fServerSocketIPv6); // ditto// Arrange to handle connections from others:env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv4, incomingConnectionHandlerIPv4, this);env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv6, incomingConnectionHandlerIPv6, this);
}
在GenericMediaServer构造函数中会把创建的fServerSocketIPv4和fServerSocketIPv6这两个套接字插入到双向闭环链表中等待doEventLoop循环处理,对应的处理函数分别为:incomingConnectionHandlerIPv4, incomingConnectionHandlerIPv6;最终都会调用incomingConnectionHandlerOnSocket函数;
void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {struct sockaddr_storage clientAddr;SOCKLEN_T clientAddrLen = sizeof clientAddr;int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket < 0) {int err = envir().getErrno();if (err != EWOULDBLOCK) {envir().setResultErrMsg("accept() failed: ");}return;}ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill usmakeSocketNonBlocking(clientSocket);increaseSendBufferTo(envir(), clientSocket, 50*1024);#ifdef DEBUGenvir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif// Create a new object for handling this connection:(void)createNewClientConnection(clientSocket, clientAddr);
}
//createNewClientConnection函数实现
GenericMediaServer::ClientConnection*
RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_storage const& clientAddr) {return new RTSPClientConnection(*this, clientSocket, clientAddr, fOurConnectionsUseTLS);
}
在doEventLoop循环中会议中accept监视tcp连接,如果有客户端连接就会创建客户端连接类RTSPClientConnection;最终会把客户端套接字clientSocket传递给ClientConnection构造函数;
GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer,int clientSocket, struct sockaddr_storage const& clientAddr,Boolean useTLS): fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr), fTLS(envir()) {fInputTLS = fOutputTLS = &fTLS;// Add ourself to our 'client connections' table:fOurServer.fClientConnections->Add((char const*)this, this);if (useTLS) {// Perform extra processing to handle a TLS connection:fTLS.setCertificateAndPrivateKeyFileNames(ourServer.fTLSCertificateFileName,ourServer.fTLSPrivateKeyFileName);fTLS.isNeeded = True;fTLS.tlsAcceptIsNeeded = True; // call fTLS.accept() the next time the socket is readable}// Arrange to handle incoming requests:resetRequestBuffer();envir().taskScheduler().setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}
//incomingRequestHandler函数最终调用
void GenericMediaServer::ClientConnection::incomingRequestHandler() {if (fInputTLS->tlsAcceptIsNeeded) { // we need to successfully call fInputTLS->accept() first:if (fInputTLS->accept(fOurSocket) <= 0) return; // either an error, or we need to try again laterfInputTLS->tlsAcceptIsNeeded = False;// We can now read data, as usual:}int bytesRead;if (fInputTLS->isNeeded) {bytesRead = fInputTLS->read(&fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft);} else {struct sockaddr_storage dummy; // 'from' address, meaningless in this casebytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);}handleRequestBytes(bytesRead);//该函数实现了对 OPTION DESCRIBE SETUP等各种信令的处理逻辑
}
在构造函数中setBackgroundHandling会把客户端套接字fOurSocket和对应的处理函数incomingRequestHandler添加到闭环双链表中,在doEventLoop中循环遍历,客户端有信令交互就调用相关的处理函数;至此用于协商的TCP协议处理流程就结束了。
关于live555的闭环双向链表参考我的另一篇文章:live555的核心数据结构值之闭环双向链表-CSDN博客
UDP流媒体传输
UDP流媒体传输服务器需要创建两个四个UDP套接字,用于传输音频RTP,音频RTCP,视频RTP,视频RTCP;该文档是以H264的传输为例所以只介绍视频RTP端口,视频RTCP端口的创建过程,音频类似;
RTP,RTCP端口是在SETUP信令处理函数handleCmd_SETUP中被创建,该函数最终调用了getStreamParameters函数:
subsession->getStreamParameters(fOurSessionId, fOurClientConnection->fClientAddr,clientRTPPort, clientRTCPPort,fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,&fOurClientConnection->fTLS,destinationAddress, destinationTTL, fIsMulticast,serverRTPPort, serverRTCPPort,fStreamStates[trackNum].streamToken);
该函数将客户端的RTP端口:clientRTPPort和RTCP端口:clientRTCPPort都进行了处理;这两个端口是客户端发送SETUP信令时携带的消息;告诉服务器RTP RTCP包改往哪里发;getStreamParameters也创建了服务器的RTP RTCP端口:serverRTPPort, serverRTCPPort;
getStreamParameters内部调用了createGroupsock函数:
void OnDemandServerMediaSubsession ::getStreamParameters(...)
{...if (clientRTPPort.num() != 0 || tcpSocketNum >= 0){ // Normal case: Create destinationsportNumBits serverPortNum;if (clientRTCPPort.num() == 0){// We're streaming raw UDP (not RTP). Create a single groupsock:NoReuse dummy(envir()); // ensures that we skip over ports that are already in usefor (serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() >= 0)break; // success}udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);}else{// Normal case: We're streaming RTP (over UDP or TCP). Create a pair of// groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even).// (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.)NoReuse dummy(envir()); // ensures that we skip over ports that are already in usefor (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;//创建RTP端口(rtp的UDP套接字)rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() < 0){delete rtpGroupsock;continue; // try again}if (fMultiplexRTCPWithRTP){// Use the RTP 'groupsock' object for RTCP as well:serverRTCPPort = serverRTPPort;rtcpGroupsock = rtpGroupsock;}else{// Create a separate 'groupsock' object (with the next (odd) port number) for RTCP://RTCP端口号在RTP端口号的基础上加1serverRTCPPort = ++serverPortNum;//创建RTCP端口(rtcp的UDP套接字)rtcpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTCPPort);if (rtcpGroupsock->socketNum() < 0){delete rtpGroupsock;delete rtcpGroupsock;continue; // try again}}break; // success}unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamicrtpSink = mediaSource == NULL ? NULL: createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);if (rtpSink != NULL){if (fParentSession->streamingUsesSRTP){rtpSink->setupForSRTP(fMIKEYStateMessage, fMIKEYStateMessageSize);}if (rtpSink->estimatedBitrate() > 0)streamBitrate = rtpSink->estimatedBitrate();}}...
}
由代码可以看出serverRTPPort的初始值是fInitialPortNum;而fInitialPortNum在创建OnDemandServerMediaSubsession对象时有个默认值6970;如果没有设置端口号则使用默认端口号;
上面代码可以看出而RTCP端口号是在RTP的端口号的基础上加1
OnDemandServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource,portNumBits initialPortNum = 6970,Boolean multiplexRTCPWithRTP = False);
当第二个客户端连接时,依然是从6970开始创建所需的RTP RTCP端口号,但是createGroupsock会发现6970 6971端口号被占用,于是返回-1;继续for循环将端口号累加;
for (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum){serverRTPPort = serverPortNum;rtpGroupsock = createGroupsock(nullAddress(destinationAddress.ss_family), serverRTPPort);if (rtpGroupsock->socketNum() < 0){delete rtpGroupsock;continue; // try again}...}
//fInitialPortNum为基数6970;
第一个客户端:rtp:6970 rtcp:6971
第二个客户端:6970 6971 被占用createGroupsock返回-1;因此for循环continue继续累加++serverPortNum; rtp:6972 rtcp:6973
......
那么怎么自定义端口号呢?
我们在做rtsp服务器的时候都会创建一个类用于实现createNewStreamSource虚函数该类继承于OnDemandServerMediaSubsession;而类的构造函数里会执行OnDemandServerMediaSubsession的构造函数;所以如果你想要自己定义服务器的RTP端口号只需在执行OnDemandServerMediaSubsession构造函数是传入参数即可:
H264LiveVideoServerMediaSubssion::H264LiveVideoServerMediaSubssion(UsageEnvironment &env, Boolean reuseFirstSource): OnDemandServerMediaSubsession(env, reuseFirstSource, 1234) {}
TCP流媒体传输使用的时信令交互的套接字,这里不做解释;关于流媒体裸流怎么打包成RTP的参考上面的文章;
该文章在持续更新,望持续关注;
相关文章:
live555关于RTSP协议交互流程
RTP在和h264 RTP在和h265 RTP载荷AAC live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource 概要 rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议;该篇文章主要分析在live555中这些…...

Centos7 安装私有 Gitlab
在 CentOS 7上,下面的命令也会在系统防火墙中打开 HTTP、HTTPS 和 SSH 访问。这是一个可选步骤,如果您打算仅从本地网络访问极狐GitLab,则可以跳过它。 sudo yum install -y curl policycoreutils-python openssh-server perl sudo systemct…...

浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用(AI智能体)
浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用 ygluu 卢益贵 关键词:UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂:AI(AIGC/ChatGPT)与流程式游戏开发》讨论之后就…...

第T5周:使用TensorFlow实现运动鞋品牌识别
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 一、前期工作1.设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据 二、数据预处理1、加载数据2、数据可视化3、再…...

网络编程学习之tcp
按下*(星号)可以搜索当前光标下的单词。 Tcp编程的过程 打开网络设备 Bind:给服务地址把ip号和端口号连接进去 Tcp是有状态的 Listen是进入监听状态,看有没有客户端来连接服务器 Tcp比udp消耗过多资源 Upd类似于半双工&#…...
前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法
前言 本文总结Web应用开发中文件上传、下载的方法,即从前端表单输入文件并封装表单数据,然后请求后端服务器的处理过程;从基础的JavaScript中XmlHttpRequest对象、Fetch API实现上传、下载进行说明,并给出了前端常用的axios库的请…...

STM32智能交通监测系统教程
目录 引言环境准备智能交通监测系统基础代码实现:实现智能交通监测系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景:交通监测与管理问题解决方案与优化收尾与总结 1. 引言 智能交通监测系统通…...
【利用Selenium+autoIt实现文件上传】
利用Selenium+autoIt实现文件上传 利用Selenium+autoIT实现文件上传autoIt脚本制作转换成exe文件java代码运行部分利用Selenium+autoIT实现文件上传 当你看到这篇文章时,证明你遇到了和我一样的难题。正常情况下我们利用selenium完全可以实现表单的提交和文件上传等操作。但当…...
python join
1、join函数 *.join(seq) 以*作为分隔符,将seq所有的元素合并为一个新的字符串 seq ABDWDPO new_seq list(.joint(seq)) # ABDWDPO #[A, B, D, W, D, P, O]...
cython加速python代码
python这个语言在使用的层面上看几乎没有缺点,简单易学,语法简单,唯一的弱点就是慢, 当然了万能的python社区是给了解决方法的,那就是cython 使用Cython可以显著提升Python代码的执行效率,特别是在涉及到数…...

React@16.x(60)Redux@4.x(9)- 实现 applyMiddleware
目录 1,applyMiddleware 原理2,实现2.1,applyMiddleware2.1.1,compose 方法2.1.2,applyMiddleware 2.2,修改 createStore 接上篇文章:Redux中间件介绍。 1,applyMiddleware 原理 R…...

level 6 day1 Linux网络编程之网络基础
v1 网络的历史和分层 TCP 是可靠传输,IP协议是不可靠传输 网络的体系结构 网络分层的思想: OSI体系结构 两层交换机是指数据链路层的交换 三层交换是指网络层这边的交换 四层模型 蓝色的字 是由手机发给PC机,由传输层来决定应该交给哪一…...
PostgreSQL UPDATE 命令
PostgreSQL UPDATE 命令 PostgreSQL 是一种功能强大的开源对象关系型数据库管理系统(ORDBMS),它使用并扩展了SQL语言。在处理数据库时,我们经常需要更新现有的记录。在PostgreSQL中,UPDATE命令用于修改表中的现有记录…...

什么? CSS 将支持 if() 函数了?
CSS Working Group 简称 CSSWG, 在近期的会议中决定将 if() 添加到 CSS Values Module Level 5 中。 详情可见:css-meeting-bot 、[css-values] if() function 当我看到这个消息的时候,心中直呼这很逆天了,我们知道像 less 这些 css 这些预…...
function calling实现调用理杏仁api获取数据
LLM是不存在真正逻辑的且并不是知晓万事万物的(至少目前是这样)在很多更垂直的环境下LLM并不能很好的赋能。 function calling的实现使LLM可以对接真正的世界以及真正有逻辑的系统,这将很大程度上改变LLM的可用范围(当然安全问题依…...
Excel中用VBA实现Outlook发送当前工作簿
Excel中用VBA实现Outlook发送当前工作簿,首先按AltF11打开VBA编辑器,插入模块,并在工具-引用中勾选 Microseft Outlook .0 Object Library(其中为你Microseft Outlook的版本号。 Sub 发送邮件() 保存当前excel ThisWorkbook.Save让excel连接…...

从 ArcMap 迁移到 ArcGIS Pro
许多 ArcMap 用户正在因 ArcGIS Pro 所具有的现代 GIS 桌面工作流优势而向其迁移。 ArcGIS Pro 与其余 ArcGIS 平台紧密集成,使您可以更有效地共享和使用内容。 它还将 2D 和 3D 组合到一个应用程序中,使您可以在同一工程中使用多个地图和多个布局。 Arc…...

WSL2 的安装与运行 Linux 系统
前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能,允许开发人员在 Windows 系统上直接安装并使用 Linux 发行版。不用进行任何修改,也无需承担传统虚拟机或双启动设置的开销。 可以将 WSL 看作也是一个虚拟机,但是它更为便…...

业务终端动态分配IP-DHCP技术、DHCP中继技术
一、为什么需要DHCP? 1、许多设备(主机、无线WiFi终端等)需要动态地址的分配; 2、人工手工配置任务繁琐、容易出错,比如:IP地址冲突; 3、网络规模扩大、复杂度提高,网络配置越来越复杂,计算机的位置变化和数量超过可分配IP地址的数量,造成IP地址变法频繁以及IP地址…...

新一代大语言模型 GPT-5 对工作与生活的影响及应对策略
文章目录 📒一、引言 📒二、GPT-5 的发展背景 🚀(一)GPT-4 的表现与特点 🚀(二)GPT-5 的预期进步 📒三、GPT-5 对工作的影响 🚀(一…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...

《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...

GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...