RTMP协议

参考
带你吃透RTMP
RTMP规范简单分析

RTMP(Real Time Message Protocol)实时信息传输协议,由Adobe公司提出的用于解决多媒体数据传输流的多路复用和分包问题。
RTMP是应用层协议,传输层通过TCP来保证信息传输的可靠性。
也要通过握手来建立基于传输层链接上的RTMP Connection链接。

在connection之上会传输一些控制信息,比如SetChunkSizeSetACKWindowSize
CreatSteam会创建一个Steam链接,用于传输具体的数据和控制这些信息传输的命令信息。

RTMP协议中基本的数据单元称为消息(Message)。不同种类的消息包含不同的Message Type ID,代表不同的功能。
当RTMP协议在互联网中传输数据的时候,为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message IDChunk
每个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
  • Type=1
    此时占用7字节,省去了msg stream id4字节,表示此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=16

  • Audio 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(暂停)

客户端告知服务端停止或恢复播放

代表流程

推流流程

16305798-567406fe7f615e35

播流流程

16305798-57cb5d2f85c9a6a7

RTMP流媒体播放流程

  1. 握手
    a) 握手开始于客户端发送C0、C1块。服务器收到C0或C1后发送S0和S1
    b) 当客户端收齐S0和S1后,开始发送C2。当服务器收齐C0和C1后,开始发送S2。
    c) 当客户端和服务器分别收到S2和C2后,握手完成。
  2. 建立网络连接
    a) 客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接
    b) 服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序
    c) 服务器发送设置带宽(Set Peer Bandwidth)协议消息到客户端
    d) 客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到服务器端
    e) 服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端
    f) 服务器发送命令消息中的“结果”(_result),通知客户端连接的状态
  3. 建立网络流
    a) 客户端发送命令消息中的“创建流”(createStream)命令到服务器端
    b) 服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态
  4. 播放
    a) 客户端发送命令消息中的“播放”(play)命令到服务器。
    b)接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息
    c) 服务器发送用户控制消息中的“streambegin”,告知客户端流ID
    d) 播放命令成功的话,服务器发送命令消息中的“响应状态”NetStream.Play.Start & NetStream.Play.reset,告知客户端“播放”命令执行成功。
    e) 服务器发送客户端要播放的音频和视频数据