RTMP协议
RTMP(Real Time Message Protocol)
实时信息传输协议,由Adobe公司提出的用于解决多媒体数据传输流的多路复用和分包问题。
RTMP是应用层协议,传输层通过TCP来保证信息传输的可靠性。
也要通过握手来建立基于传输层链接上的RTMP Connection
链接。
在connection之上会传输一些控制信息,比如SetChunkSize
和SetACKWindowSize
。
CreatSteam
会创建一个Steam链接,用于传输具体的数据和控制这些信息传输的命令信息。
RTMP协议中基本的数据单元称为消息(Message)
。不同种类的消息包含不同的Message Type ID,代表不同的功能。
当RTMP协议在互联网中传输数据的时候,为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID
的Chunk
。
每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,Message ID
和Message的长度把chunk还原成完整的Message,从而实现信息的收发。
链接建立 -- 握手
建立有效的RTMP Connection需要握手:其中客户端向服务器发送C0、C1、C2三个chunk,而服务器需要向客户端发送S0、S1、S2三个chunk
- 客户端要等到收到S1后才能发送C2
- 客户端等到收到S2才能发送其它信息(音频信息和控住信息)
- 服务端收到C0后才发送S1
- 服务端等收到C1后才发送S2
- 服务端在收到C2后才发送其它信息
如果每次发送一个握手chunk的话握手顺序会是这样:
16305798-1ec2bd472f5c1650
一般为了减少通次次数,发送顺序为
|client | Server |
|---C0+C1 —->|
|<--S0+S1+S2– |
|---C2----> |
Chunk Steam
Message
- Timestamp(时间戳):4个字节
- Length:
Message Payload
即音视频等信息的数据长度,3个字节 - MessageTypeId: 消息的类型ID,1个字节
- Message Stream ID:每个消息的唯一标识,划分成Chunk和还原Chunk为Message的时候都是根据这个ID来辨识是否是同一个消息的Chunk的,4个字节,并且以小端格式存储
Chunking
RTMP收发数据将message拆分为Chunk,并在一个chunk发送完成后才能发送下一个chunk。
Chunk中包括了message id来代表属于哪个message。
Chunk默认大小为128字节,在传输过程中,可以通过Set Chunk Size
的控制信息设置Chunk数据量的最大值,在发送端和接收端各自维护了一个Chunk Size
,可以通过设置此值来改变你自己这方发送的Chunk的最大大小。
拆分发送的原因:可以将数据量较大的message拆分为较小的,这样可以避免优先级低的消息持续发送而阻塞了优先级高的消息。同时对于数据量小的message可以通过对Chunk Header字段来压缩信息,减少信息的传输量。
调整Chunk大小: 比较大的Chunk减少了计算每个chunk的时间从而减少CPU的占用率,但是会占用更多时间在发送上,这在低带宽的情况下有可能会阻塞后面重要消息的传输。小的chunk虽然会减少阻塞问题,但是会引用过多的额外信息(在chunk的header),这种少量多次的传输也不能充分利用高带宽的有事,因此不适合在高比特率的流中传输。而在实际发送时,对要发送的数据用不同的chunk size 尝试,通过抓包等得到合适的chunk大小,并在实际传输中根据实际情况调整chunk大小,来提高CPU的利用率并减少信息的阻塞几率
Chunk Format
Basic Header(基本的头信息)
包含了chunk stream ID
(CSID)和chunk type
CSID
用来唯一标识一个特定的流通道,chunk type
决定了后面Message Header的格式.
Basic Header
可能为1或2或3字节,其中chunk type
占用2bit,因此长度取决于CSID的大小
RTMP协议中CSID可以为[3, 65599],其中0、1、2为保留的特殊信息。0代表Basic Header总共要占用2个字节,CSID在[64,319]之间,1代表占用3个字节,CSID在[64,65599]之间,2代表该chunk是控制信息和一些命令信息,
chunk type的长度固定为2位(fmt),因此CSID长度为(6=8-2)、(14=16-2)、(22=24-2)中的一个
- 当Basic Header为1个字节时,CSID占6位,6位最多可以表示64个数,因此这种情况下CSID在[0,63]之间,其中用户可自定义的范围为[3,63]
- 当Basic Header为2个字节时,CSID占14位,此时协议将与chunk type所在字节的其他位都置为0,剩下的一个字节来表示CSID-64,这样共有8个二进制位来存储CSID,8位可以表示[0,255]共256个数,因此这种情况下CSID在[64,319],其中319=255+64。
- 当Basic Header为3个字节时,CSID占22位,此时协议将[2,8]字节置为1,余下的16个字节表示CSID-64,这样共有16个位来存储CSID,16位可以表示[0,65535]共65536个数,因此这种情况下CSID在[64,65599],其中65599=65535+64
注意:是采用小端存储的
Message header(消息的头信息)
包含了实际要发送的信息的描述信息。其格式和长度取决于Basic Header中的2bit的chunl type
共有4中不同格式
- Type=0
Message Header占用11个字节,可以表示其他3种type能表示的数据。但是在chunk stream开始的第一个chunk和头信息的时间戳后退(在回退播放时)必须采用这种格式- timestamp:占用3字节,但是当时间戳的值大于最大值时,将这3个字节都置为1,此时实际的timeStamp会转存到
Extended Timestamp
字段中,接收端在判断3字节都为1时,就去Extended timestamp
中解析实际的时间戳 - message length: 3字节,表示实际发送的消息数据的长度,单位为字节。注意,这里为message的长度,而非chunk本身的data长度
- message type id:1字节,表示实际发送的数据类型,例如8,9的消息分别用于传输音频和视频数据
- msg stream id:4字节,表示所在流的id 与CSID一样,采用小端存储 16305798-d36311e7b9b95cf1
- timestamp:占用3字节,但是当时间戳的值大于最大值时,将这3个字节都置为1,此时实际的timeStamp会转存到
- Type=1
此时占用7字节,省去了msg stream id
4字节,表示此chunk和上次chunk的stream相同。timestamp delta
:占用3字节,这里和type=0不同,存储的是和上个chunk的时间差。 16305798-62f87b48ba48e542 - Type=2
3字节,相对于type=1
省去了表示消息长度的3字节和消息类型1字节,表示和上次发送的chunk相同。余下的3字节表示timestamp delta
16305798-2c8ee6c1e845a046 - Type=0
0字节,表示和上个chunk完全相同。
跟在Type=0的chunk后面时,表示和前一个chunk连时间戳都是相同的。当一个Message拆分为多个chunk,这个chunk和上个chunk同属一个message时,就会完全相同。
跟在type=1或者2后面时,表示和前一个chunk的时间戳的差是相同的。
Extended Timestamp(扩展时间戳)
4字节,当启用这个字段时,需要将timestamp
或者timestamp delta
全置为1,此时表示从扩展时间戳字段提取真正的时间戳。
扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值。
Chunk Data
用户真正要发送的和协议无关的数据,长度在(0、chunkSize)
之间
协议控制消息
chunk流会用特殊的值表示协议的控制消息。此时Message Stream ID
必须为0(表示控制流信息),CSID
必须为2,Message Type ID
可以为1、2、3、4、5、6,控制消息的接收端会忽略chunk中的时间戳,收到后就会立即生效
Set Chunk Size(Message Type ID=1):
默认128字节,通过该消息设置 chunk size大小。Abort Message(Message Type ID=2):
当Mesaage被切分了多个chunk,接收端只收到部分chunk时,发送该消息表示发送端不再传输同Message的chunk,接收端收到消息丢弃不完整的chunk。
Data数据中只需要一个CSID,表示丢弃该CSID的所有已接收到的chunk。Acknowledgement(Message Type ID=3):
当接收到的消息大小等于窗口大小(window size),接收端回馈一个ACK给发送端告知可以继续发送数据。
窗口大小window size
就是指收到接受端返回的ACK前最多可以发送的字节数量,返回的ACK中会带有从发送上一个ACK后接收到的字节数。Window Acknowledgement Size(Message Type ID=5):
发送端在接收到接受端返回的两个ACK间最多可以发送的字节数。Set Peer Bandwidth(Message Type ID=6):
限制端的输出带宽。接收端收到该消息后可以通过设置消息的Window ACK Size来限制已发送但未收到反馈的消息大小来限制发送端的发送带宽。如果消息中的Window ACK Size
与上一次发送给发送端的size不同的话要回馈一个Window Acknowledgement Size
的控制消息。
RTMP Message Type
Message Type ID
在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据。Message Type ID
为8,9的消息分别用于传输音频和视频数据。Message Type ID
为15-20的消息用于发送AMF
编码的命令,负责用户与服务器之间的交互,比如播放,暂停等等。
Command Message(命令消息,Message Type ID=17或20):表示客户端和服务器传递的对端执行某些操作的命令消息。
connect
表示连接对端
publish
表示开始向对方推流
当信息使用AMF0编码时,Message Type ID=20,AMF3编码时Message Type ID=17.Data Message(数据消息 Message Type ID=15或18):传递一些元数据(视频名字,分辨率或用户自定义消息等)。
当信息使用AMF0编码时,Message Type ID=18,AMF3编码时Message Type ID=15.Shared Object Message(共享消息):表示一个Flash类型的对象
当信息使用AMF0编码时,Message Type ID=19,AMF3编码时Message Type ID=16Audio Message(音频信息,Message Type ID=8):音频数据。
Video Message(视频信息,Message Type ID=9):视频数据。
Aggregate Message (聚集信息,Message Type ID=22):多个RTMP子消息的集合
User Control Message Events(用户控制消息,Message Type ID=4):告知对方执行该信息中包含的用户控制事件 比如
Stream Begin
事件告知对方流信息开始传输。
和前面提到的协议控制信息(Protocol Control Message)
不同,这是在RTMP协议层的,而不是在RTMP chunk流协议层
的,这个很容易弄混。该信息在chunk流中发送时,Message Stream ID=0,Chunk Stream Id=2,Message Type Id=4
。
Command Message
1. NetConnection Commands(连接层的命令)
NetConnection Commands
用于管理连接状态
connect
用于客户端向服务器发送连接请求
Call
用于在对端执行某函数,即常说的RPC:远程进程调用
Create Stream
创建传递具体信息的通道,从而可以在这个流中传递具体信息,传输信息单元为Chunk
2. NetStream Commands(流连接上的命令)
NetStream
是建立在NetConnection
之上用create steam
命令创建的,用于传输音视频信息的。只有一个NetConnection
,但是一个NetConnection
可以建立多个NetStream
建立不同的流通道传输数据
服务端在收到客户端的stream commands后,会通过onStatus
命令来响应客户端,表示当前NetStream
的状态
play(播放)
由客户端向服务器发起请求从服务器端接受数据(如果传输的信息是视频的话就是请求开始播流),可以多次调用,这样本地就会形成一组数据流的接收者。注意其中有一个reset字段,表示是覆盖之前的播流(设为true)还是重新开始一路播放(设为false)
play2(播放)
play2命令可以将当前正在播放的流切换到同样数据但不同比特率的流上,服务器端会维护多种比特率的文件来供客户端使用play2命令来切换
deleteStream(删除流)
用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流
deleteStream(删除流)
用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流
receiveAudio(接收音频)
通知服务器端该客户端是否要发送音频
receiveVideo(接收视频)
通知服务器端该客户端是否要发送视频
publish(推送数据)
由客户端向服务器发起请求推流到服务器
seek(定位流的位置)
定位到视频或音频的某个位置,以毫秒为单位
pause(暂停)
客户端告知服务端停止或恢复播放
代表流程
推流流程
播流流程
RTMP流媒体播放流程
- 握手
a) 握手开始于客户端发送C0、C1块。服务器收到C0或C1后发送S0和S1
b) 当客户端收齐S0和S1后,开始发送C2。当服务器收齐C0和C1后,开始发送S2。
c) 当客户端和服务器分别收到S2和C2后,握手完成。 - 建立网络连接
a) 客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接
b) 服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序
c) 服务器发送设置带宽(Set Peer Bandwidth)协议消息到客户端
d) 客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端
e) 服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端
f) 服务器发送命令消息中的“结果”(_result),通知客户端连接的状态 - 建立网络流
a) 客户端发送命令消息中的“创建流”(createStream)命令到服务器端
b) 服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态 - 播放
a) 客户端发送命令消息中的“播放”(play)命令到服务器。
b)接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息
c) 服务器发送用户控制消息中的“streambegin”,告知客户端流ID
d) 播放命令成功的话,服务器发送命令消息中的“响应状态”NetStream.Play.Start & NetStream.Play.reset
,告知客户端“播放”命令执行成功。
e) 服务器发送客户端要播放的音频和视频数据