简介
RTMP协议是Real Time Message Protocal(实时传输协议的缩写),同时Adobe公司提供的一种应用层协议,用来解决多没意思数据流传输的
多路复用和分包问题。
- RTMP是应用层协议,采用TCP来保证可靠的传输
- 在TCP完成握手连接建立后,还会进行RTMP的一些自己握手的动作,在建立RTMP进行连接
- 在Connetion连接上会传输一些控制信息,例如设置SetChunkSize,SetACKWindosSize。CreatStream命令回创建一些stream的连接,用于传输具体的音视频数据和控制这些信息传输命令信息
- RTMP在发送的时候会将数据格式化为Message,然后将一个个Message分为带有Message ID的Chunk,每个Chunk可能是整个Message的一部分。在接受端会根据Message ID,长度与data将chunk还原为Message。
握手方式
建立一个有效的RTMP Connetion连接,首先要握手:首先客户端需要向服务器发送C0,C1,C2(按顺序)三个Chunk,服务器向向客户端发送S0,S1,S2(按顺序)三个chunk,然后才能进行有效的信息传输,RTMP协议本身并没有规定6个Message的顺序传输顺序,但是RTMP协议的实现的需要注意一下几点。
- 客户端需要等受到S1之后才能发送C2
- 客户端必须受到S2消息,然后发送其他数
- 服务端必须接收到C0或者C1消息,然后才能S0,S1消息
- 服务端只有收到C2消息,然后发送其他数据
RTMP Chunk Stream
Message 是指满足协议格式的、可以分割成的Chunk发送的消息,消息主要包含下面几部分,
- Timestamp(时间戳)
消息时间戳(并不一定保证为当前的时间)4字节 - Length(长度)
指的是Message Payload 消息负载)音视频等信息的长度,3字节 - TYpeId(类型id)
消息类型的id,1字节 - Message Stream ID(消息流ID)
每个消息流中的唯一标识符,划分为chunk和还原为Messag的时候都是根据这个ID来表示同一个消息属于同一个chunk,同一个消息的chunk的4个字节,并且以小端个数存储。
RTMP中传输的过程中将Message拆分为一个个chunk来传输,必须在一个Chunk传输完毕后在下一个传输chunk,chunk中携带的MessageID来辨识属于哪个Message。
通过拆分,数据量较大的Message可以被划分为小的Message,这样就可以避免优先级低的问题。持续发送阻塞优先级高的数据,比如在视频的传输的过程中,或包括视频帧,音频帧以及RTMP控制信息,如果持续发送音频数据会导致导致视频数据发生阻塞。
对于数据量较小的Message,可以通过对Chunk Header的字段来压缩信息,从而减少信息的传输量。(具体的压缩方式会在后面介绍)
Chunk的默认大小是128字节,在传输过程中,通过一个叫做Set Chunk Size的控制信息可以设置Chunk数据量的最大值,在发送端和接受端会各自维护一个Chunk Size,可以分别设置这个值来改变自己这一方发送的Chunk的最大大小。
Chunk大了减少了计算每个Chunk的时间,省了CPU,但是发送时间长,在第带宽情况下,可能会阻塞后面的消息。Chunk小了会映入额外的信息Header,少量多次不能充分利用高带宽,不适合在高比特流的传输。实际要根据情况具体来制定chunk大小。
网络连接的建立(NetConnection):
- 客户端发送命令消息中连接到服务器中,请求与服务器实例建立连接
- 服务器受到连接命令消息后,发送确认窗口大小协议消息个客户端,同时连接到连接命理中提到的应用程序中
- 服务器发送设置带宽()协议消息给客户端
- 客户处理设置带宽协议消息后,发送确认窗口大小协议消息到服务端中
- 服务器发送用户控制消息中的流开始消息到客户端中
- 服务器发哦送你个命令消息中的结果通知客户端连接状态。
建立网络流: - 客户端发送消息中的创建流命令到服务器中
- 服务器受到客户端的命令流消息后,发送命令消息中的结果,通知客户端流的状态。
播放(play): - 客户端发送命令消息中播放命令道服务器中
- 受到拨打命令后,服务器发送设置块的大小的消息协议
- 服务器发送用户控制消息中的streambegin,告诉客户端流的ID
- 播放命令成功后,服务器发送命令消息的中的响应状态
- 在此之后发送客户端需要发送的数据
基本架构
RTMP真个内容,除了握手接下来就是问容易些流的消息typeid 的Message。
RTMP数据中发送的Messa个如上图所示,其基本结构:
- header:header部分用来表示不同的typeID,告诉客户端相应的数据类型,另外还有一个功能是用于的多路分发。
- Body:Body内容就是相关发送的数据。这个根据不同的tyoeID来说,格式不同。
具体Message的格式块的结构如下所示:
.
+--------------+----------------+--------------------+--------------+
| Basic Header | Message Header | Extended Timestamp | Chunk Data |
+--------------+----------------+--------------------+--------------+
| |
|<------------------- Chunk Header ----------------->|
Chunk Format
块的基本头(1-3):这个字节包含块流ID和块的类型,块类型决定了编码过的消息头的个数。这个字段是一个变量长字段,长度取决于块流ID。
消息头(0,3,7,11):这个字段包含被发送的消息信息。字段长度由块头中的块的类型决定。
扩展时间戳(0,4字节):这个字节时候存在取决于块消息头中编码的时间戳。
块数据(可变大小):当前块有效数据,上限位配置的块的最大大小。
Basic Header(基本的头信息):包含流ID和chunk类型,chunk stream ID一般写为CSID,用来唯一标识一个特定的流通道,chunk type决定后面的Message Header的格式。basic Header的长度为1,2,3字节,其中的chunk type是固定的占2位,Basic Header的长度取决于CSID的大小,在足够存储这两个字段的前提下最后用尽量少的字节从而减少的由于引入Header增加的数据量。
RTMP协议支持用户自定义【3,65599】之间的CSID,0,1,2由协议保留特殊消息信息,0表示Basic Header总共要占用2字节,CSID在【64,319】之间,1代表用3个字节,CSID在【64,65599】之间,2表示该chunk是控制信息和一些命令信息。3表示Basic为1个字节
- 当Basic Header为1个字节时
CSID为6位,6位智能表示64个数,这种情况下只能在【0,63】之间,其中用户可选则的范围为【3,63】
- 当Basic Header为2个字节时
CSID占14位,此时协议将于chunk type所在字节的其他位置为0,剩下的一个字节来表示CSID-64,这样共有8个二进制位来存储CSID,8位可以表述为【0,255】共有256个数,因此这种情况下【64,319】,其中319=255+63
- 当Basic Header为2个字节时
CSID占22位,此时协议将[2,8]字节置为1,余下的16个字节表示CSID-64,这样共有16个位来存储CSID,16位可以表示[0,65535]共65536个数,因此这种情况下CSID在[64,65599],其中65599=65535+64,需要注意的是,Basic Header是采用小端存储的方式,越往后的字节数量级越高,因此通过这3个字节每一位的值来计算CSID时,应该是:<第三个字节的值>x256+<第二个字节的值>+64
- Message Header(消息的头信息)
包含了要发送的实际信息的描述信息,Message Header的格式和长度取决于Basic Header的chunl type(fmt:0,1,2,3)共有4种不同的格式,其由上面所提到的Basic Header中的fmt字段控制。其中第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。以下按照字节数从多到少的顺序分别介绍这4种格式的
fmt=0:(type =0)
当时type=0时,Message视同11个字节表示,其他三种数据他都能表示。但在chunk streams的开始的第一个chunk和头信息中的时间戳后(值与上一个chunk相比减少,通常在回退播放时的时候会出像这种情况)的时候必须采用用这种格式
timestamp(时间戳):占用3个字节,因此它最多能表示到16777215=0xFFFFFF=2^24-1,当它的值超过这个最大值时,这三个字节都置为1,这样实际的timestamp会转存到Extended Timestamp字段中,接受端在判断timestamp字段24个位都为1时就会去Extended timestamp中解析实际的时间戳。
message length(消息数据的长度):占用3个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总数据长度,而不是chunk本身Data的数据的长度。
message type id(消息的类型id):占用1个字节,表示实际发送的数据的类型,如8代表音频数据、9代表视频数据。
msg stream id(消息的流id):占用4个字节,表示该chunk所在的流的ID,和Basic Header的CSID一样,它采用小端存储的方式
fmt=1:(type =1)
type=1时Message Header占用7个字节,省去了表示msg stream id的4个字节,表示此chunk和上一次发的chunk所在的流相同,如果在发送端只和对端有一个流链接的时候可以尽量去采取这种格式。
timestamp delta:占用3个字节,注意这里和type=0时不同,存储的是和上一个chunk的时间差。类似上面提到的timestamp,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际的时间戳差值就会转存到Extended
Timestamp字段中,接受端在判断timestamp delta字段24个位都为1时就会去Extended timestamp中解析时机的与上次时间戳的差值。
fmt=2:(TYPE=2)
type=2时Message Header占用3个字节,相对于type=1格式又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此chunk和上一次发送的chunk所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示timestamp delta,使用同type=1。
mt=3:(TYPE=3):
0字节,表示这个chunk的Message Header和上一个是完全相同的,自然就不用再传输一遍了。
当它跟在Type=0的chunk后面时,表示和前一个chunk的时间戳都是相同的。什么时候连时间戳都相同呢?就是一个Message拆分成了多个chunk,这个chunk和上一个chunk同属于一个Message。
而当它跟在Type=1或者Type=2的chunk后面时,表示和前一个chunk的时间戳的差是相同的。比如第一个chunk的Type=0,timestamp=100,第二个chunk的Type=2,timestamp delta=20,表示时间戳为100+20=120,第三个chunk的Type=3,表示timestamp delta=20,时间戳为120+20=140
- Extended Timestamp(扩展时间戳)
上面提到在chunk中会有时间戳timestamp和时间戳差timestamp delta,并且它们不会同时存在,只有这两者之一大于3个字节能表示的最大数值0xFFFFFF=16777215时,才会用这个字段来表示真正的时间戳,否则这个字段为0。扩展时间戳占4个字节,能表示的最大数值就是0xFFFFFFFF=4294967295。当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。
- Chunk Data(块数据)
用户层面上真正想要发送的与协议无关的数据,长度在(0,chunkSize]之间