一个简单的RTMP服务器实现 --- RTMP实现要点
PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=077)
本文发布于 2019-01-08 17:41:05,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=077)
环境说明
无
背景
参考前置文章:《一个简单的RTMP服务器实现 --- RTMP与H264》https://blog.csdn.net/u011728480/article/details/85770696
前置知识
《一个简单的RTMP服务器实现 --- RTMP与H264》:https://blog.csdn.net/u011728480/article/details/85770696
《一个简单的RTMP服务器实现 --- RTMP与FLV》:https://blog.csdn.net/u011728480/article/details/85780974
RTMP简介
RTMP是Real Time Messaging Protocol的简写。RTMP是应用层协议,其是基于TCP实现的。
网上有许多介绍RTMP基础知识的地方,本文不重复介绍。但是如果有人对以下概念不熟悉的,建议去随意找一篇翻译《rtmp_specification_1.0.pdf》的文章即可。
阅读本文需要找一篇RTMP的详细知识总结,一起配合阅读。
RTMP基本知识要点
Chunk 、ChunkStream和ChunkStreamID
Chunk是RTMP的一种应用层分包结构。
ChunkStream是一种逻辑通道,代表的是RTMP Message分包为Chunk之后的传输的一种数据流。可从服务器到客户端,反之亦然。
ChunkStreamID是描述ChunkStream的一个ID,消息拆包为Chunk后,可根据此ID的标识来组合Message。其取值范围为3~65599(2^16 -1 + 2 ^6 -1)。0代表2byte形式ChunkbasicHeader,1代表3byte形式ChunkbasicHeader。2代表是控制消息和命令的流。
下图为chunkbasicheader格式图:
下图为chunk格式图:
Message、MessageStream和MessageStreamID
Message是RTMP协议的基本数据结构。绝大部分RTMP协议的数据发送都必须按照此结构来封装。
MessageStream也是一种逻辑通道,它描述的是一种消息流。根据抓包结果显示,基本的消息通信为一种消息流,音视频消息通信为另外一种流。这种逻辑流在RTMP播放过程中一定要注意。
MessageStreamID是一种表示MessageStream的ID。
Message有多种结构,由chunkbasicheader中fmt字段决定,有如下四种格式:
type0
type1
type2
type3, 没有头结构。
Message ~ Control Message
控制消息是一些设置属性的消息。他们的MessageTypeId是1-7.
- MessageTypeId==1,设置Chunk分包大小,默认为128bytes
- MessageTypeId==2,终止消息,如果一个消息正在被等待接收完毕(Chunk分包没有接收完毕),那么本消息用于放弃这个消息的等待。
- MessageTypeId==3,确认消息。本消息用于发送本客户端接收到了多少数据。
- MessageTypeId==4,用户控制报文信息。具体用户控制报文看后文
用户控制报文协议中Stream Begin是实现RTMP的播放的重要的一个报文。详情参考RTMP官方文档,及其他参考资料。
- MessageTypeId==5,发送窗口确认大小信息。用于设置窗口大小,达到这个值后,回复确认信息。
- MessageTypeId==6,设置对端带宽信息。如果,携带的确认窗口信息大小和之前不一致,要回应一个确认窗口信息大小。反之,不回应。
- MessageTypeId==7 ,保留。
Message ~ Command Message
MessageTypeId==17或者20. 17对应的格式是AMF3,20对应的格式是AMF0.
connect, createStream, publish, play, pause等命令是非常重要的。特别注意其中的事物ID,这个ID是关键。
Message ~ Data message
MessageTypeId==18或者15. 18对应的格式是AMF0。15对应的格式是AMF3.
Metadata的发送,就要靠此类型的消息。
Message ~ Shared object message
MessageTypeId==19或者16,19对应格式是AMF0,16对应的格式是AMF3.
Message ~ Audio message
MessageTypeId==8 音频数据
Message ~ Video message
MessageTypeId==9 视频数据
Message ~ Aggregate message
MessageTypeId==22 聚集消息数据
NetConnection相关命令
NetConnection相关的命令是用于处理RTMP连接方面的问题,当使用命令createstream后,就会创建成功一个流了,就会切换到NetStream相关命令下工作。
本文的服务器用了的命令为connect、createstream等等。
具体用法可看下文抓包分析,特别是这些命令的的回应,是本文的重点。
NetStream相关命令
本文的服务器用了命令为play、pause等等。
具体用法可看下文抓包分析,特别是这些命令的的回应,是本文的重点。
AMF0和AMF3格式
这种格式是用来序列化相关数据的。具体参考其他文章。
RTMP 通信流程分析(理论和抓包结合)
RTMP 简单握手(此种握手为造成一个坑爹的问题,具体看文末注意事项。)
c0和s0结构:
c0和s0实际抓包:
C1和S1结构:
zero字段必须为0.
random区域长度为1528bytes的随机数。
C1和S1实际抓包:
从图中可以看出,我服务器回应的随机字节基本都是0。
C2和S2实际抓包:
图中可以看出,S2C1。同理C2S1。
简单握手时序图:
实际过程中,C0和C1一起发送,服务器一起回应S0,S1,S2。当握手完毕后,就会进入下一阶段。
RTMP 连接及响应
首先给出连接及响应时序图:
Command Message 之 connect
从这里可以看到,connect命令携带了rtmp流地址的属性以及相关的版本。关于这个命令的重点其实是其事物ID的值是1。
作为connect的回应,这里一般来说有如下几个基本消息需要发送:
- 确认窗口大小信息
- 设置带宽信息
- 设置Chunk分包大小
最终,我们需要对connect命令进行回应,如果不回应或者回应错误,将不会走到下一步。
_result命令:
这里的一个重点是事物ID必须为1,表示对connect的回应。
其次,携带的object必须包含connect的状态回应属性,如:NetConnection.Connect.Success和Connection succeeded.等等
当客户端收到connect 的回应后,客户端发送createstream命令,服务端收到createstream命令后,发送createstream响应命令。
注意画框部分的事物ID,同时也注意回应命令中,数字为1的流ID,这个值代表的是对于本次连接,MessageStreamID必须为此值。对于本连接来说,后续所有需要发送MessageStreamID的地方必须填写此值,才能够完成相关通信。
RTMP 播放及响应命令
播放时序图:
当创建流成功后,客户端会发送一个play命令:
这里需要注意的是事物ID为0,且包含要播放那个视频流的属性。这里的test就是这个地址里面的rtmp://xxx.xxx.xxx.xxx/live/test
这个时候服务器会设置相应的属性:如设置chunk大小等等。同时服务器会回应用户控制消息streambegin。
最后服务器会回应play命令:
这个时候,其实就是可以发送媒体数据了,但是根据抓包结果显示,还需要这个数据:
RTMP传输音视频
在传输音视频之前,必须先传输onMetaData(参考flv一文)数据。
然后可以传输音视频数据了,
但是传输普通的音视频数据之前,必须传输相关的配置数据。对于h264视频来说,就是flv一文中的AVCDecoderConfigurationRecord 的videotag数据。然年即可传输普通的音视频数据。
注意:在创建成功一个流之后,发送的媒体数据中的MessageStreamID必须为上文createstream 返回的值。
注意:如果严格按照上文实现,就可以用vlc或者plotplayer等等播放对应的RTMP流。但是,Flash网页播放器一定不能够播放(具体表现为所有都工作正常,只是页面没有画面,像没有接收到数据一样)。原因是简单握手导致的。
后记
总结:
- 根据官方文档实现相关功能后,如果没有达到预期效果,别急慢慢排查。至少我是这样的,别把思路搞混了就行了。
- 还有一个教训就是:相信自己,要敢于怀疑别人的资料是错的。
参考文献
- rtmp_specification_1.0.pdf
打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。
一个简单的RTMP服务器实现 --- RTMP实现要点的更多相关文章
- 自己动手模拟开发一个简单的Web服务器
开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...
- 一个简单的web服务器
写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...
- [置顶] 在Ubuntu下实现一个简单的Web服务器
要求: 实现一个简单的Web服务器,当服务器启动时要读取配置文件的路径.如果浏览器请求的文件是可执行的则称为CGI程序,服务器并不是将这个文件发给浏览器,而是在服务器端执行这个程序,将它的标准输出发给 ...
- Tomcat剖析(二):一个简单的Servlet服务器
Tomcat剖析(二):一个简单的Servlet服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三) ...
- Tomcat剖析(一):一个简单的Web服务器
Tomcat剖析(一):一个简单的Web服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器 ...
- 自己模拟的一个简单的web服务器
首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器 ...
- java实现一个简单的Web服务器
注:本段内容来源于<JAVA 实现 简单的 HTTP服务器> 1. HTTP所有状态码 状态码 状态码英文名称 中文描述 100 Continue 继续.客户端应继续其请求 101 Swi ...
- 响应式编程笔记三:一个简单的HTTP服务器
# 响应式编程笔记三:一个简单的HTTP服务器 本文我们将继续前面的学习,但将更多的注意力放在用例和编写实际能用的代码上面,而非基本的APIs学习. 我们会看到Reactive是一个有用的抽象 - 对 ...
- 转:【专题十二】实现一个简单的FTP服务器
引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...
- 使用gitblit搭建一个简单的局域网服务器
使用gitblit搭建一个简单的局域网服务器 1.使用背景 现在很多使用github管理代码,但是github需要互联网的支持,而且私有的git库需要收费.有一些项目的代码不能外泄,所以,搭建一个局域 ...
随机推荐
- 来了!HelloGitHub 年度热门开源项目
年关将至,「HelloGitHub 月刊」也迎来了年终盘点时刻. 在过去的一年里,「HelloGitHub 月刊」一共分享了 520 个开源项目.我始终秉持着分享 GitHub 上有趣.入门级开源项目 ...
- 【AI视频教程】只需5步,AI作出鸡你太美视频
1.视频效果 黄昏见证虔诚的信徒 2.准备工作 制作视频效果,需要准备下面3个条件: 准备stable diffusion的环境 剪辑一段[鸡你太美]原版视频 stable diffusion安装sd ...
- webrtc终极版(二)搭建自己的iceserver服务,并用到RTCMultiConnection的demo中
webrtc终极版(二)搭建自己的iceserver服务,并用到RTCMultiConnection的demo中 目录 webrtc终极版(二)搭建自己的iceserver服务,并用到RTCMulti ...
- 19c RAC 告警日志报错 ORA 7445 [pevm_icd_call_common()+225]
问题现象: 在一套2节点的19c RAC 环境下,节点2 alert告警 ORA 7445,且频度固定为每分钟报一次:期间有重启实例,但故障依旧: ========================== ...
- 全脸 苦思设计了半年的注册中心,与spring cloud 的做法 基本一致
早知道不去自己思考设计了,害死了不少脑细胞,物理层的东西,所有设计者的思路 都基本一致: 没有必要每个微服务都要做一次安全校验,一个物理集群,一个网关: 网关校验token后,把用户信息 保存到 ht ...
- 【简写MyBatis】01-简单映射器
前言 新开一个坑,为了学习一下MyBatis的源码,写代码是次要的,主要为了吸收一下其中的思想和手法. 目的 关联对象接口和映射类的问题,把 DAO 接口使用代理类,包装映射操作. 知识点 动态代理 ...
- Power BI 15 DAY
业务(表结构)数据分析 1.业务理解 准确 全面 2.数据收集 了解需要用到的数据有哪些 5W2H 结构化数据 SQL.通过查询获取数据库资源 多源表结构数据 企业数据库数据 文本文件数据 Excel ...
- Google搜索操作符:让你秒变搜索专家
搜索引擎对互联网的重要性不言而喻,不过,随着ChatGPT及其类似AI工具的推出,对搜索引擎带来了前所未有的挑战. 因为ChatGPT具有自然语言处理能力,能够更好地理解用户的搜索意图,提供更准确.更 ...
- Goland 使用[临时]
环境变量 因为module模式的引入, 多个项目可以共用同一套External Libraries, 在File->Settings->Go中, 设置GOROOT为go安装目录, 例如 / ...
- Java判断是否为闰年
题目: 判断一个输入的整数是否为闰年? 1.需要对输入的数据类型进行验证 2.支持多次输入和结束符号判断,例如输入q代表退出程序. 分析: 闰年的判断规则如下: (1)若某个年份能被4整除但不能被10 ...