RTMP规范学习

一、简述

RTMP(Real Time Messaging Protocol)即实时消息传输协议。该协议基于TCP,是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的开放协议。用来解决多媒体数据传输流的多路复用分包的问题。

  • 1.RTMP工作在TCP之上,默认使用端口1935;
  • 2.RTMPE在RTMP的基础上增加了加密功能;
  • 3.RTMPT封装在HTTP请求之上,可穿透防火墙;
  • 4.RTMPS类似RTMPT,增加了TLS/SSL的安全功能;

播放一个RTMP协议的流媒体需要经过以下几个步骤:握手建立连接建立流播放
需要了解专用词:
消息(Message):基本的数据单元称为消息
消息块(Chunk):传输数据的时候,消息会被拆分成更小的单元,称为消息块

1.1 握手

客户端要向服务器发送C0,C1,C2(按序)三个chunk,服务器向客户端发送S0,S1,S2(按序)三个chunk,然后才能进行有效的信息传输。

需要注意:

1.客户端要等收到S1之后才能发送C2
2.客户端要等收到S2之后才能发送其他信息(控制信息和真实音视频等数据)
3.服务端要等到收到C0之后发送S1
4.服务端必须等到收到C1之后才能发送S2
5.服务端必须等到收到C2之后才能发送其他信息(控制信息和真实音视频等数据)

github

但是为了让六次通信过程效率提高,就可能出现下面的方式
github

1.2 建立网络连接

  • 1.客户端发送连接命令到服务器,请求与服务应用实例建立连接
  • 2.服务器接收到连接命令后,发送消息给客户端用于确认窗口大小,同时连接到发送连接命令的应用程序
  • 3.服务器发送设置带宽消息到客户端
  • 4.客户端处理设置带宽消息后,发送确认窗口大小消息到服务器端
  • 5.服务器发送用户控制消息中的Stream Begin消息到客户端
  • 6.服务器发送命令消息中的result,通知客户端连接的状态

github

1.3 建立网络流

  • 1.客户端发送命令消息createStream命令到服务器端
  • 2.服务器接收到消息后,发送命令消息中的_result,通知客户端流的状态

github

1.4 播放

  • 1.客户端发送命令消息中play命令到服务器端
  • 2.接收到play命令后,服务器端发送设置块大小(ChunkSize)的协议消息
  • 3.服务器发送用户控制消息streambegin,告知客户端流ID
  • 4.play命令成功后,服务器发送命令消息中NetStream.Play.Start & NetStream.Play.reset,告知客户端play命令执行成功
  • 5.此后,服务器发送客户端要播放的音频数据和视频数据

github

二、基本概念

2.1 消息 Message

基本的数据单元称为消息
github

  • MessageType:消息类型(Message Type ID在1-7的消息用于协议控制;Message Type ID为8,9的消息分别用于传输音频和视频数据;15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互)
  • PayloadLength:消息体长度
  • Time Stamp:标识时间戳
  • Stream ID:标识消息所属媒体流,分成Chunk和还原Chunk为Message的时候都是根据这个ID来辨识是否是同一个消息的Chunk的

2.2 消息块 Chunk

网络收发过程中并不会Message为单位发送,jk在网络传输数据时需要将消息拆分成较小的数据块,才适合在相应网络环境下传输。拆分为消息块(Chunk),可以避免优先级低的消息持续发送阻塞优先级高的数据,并且可以提高数据压缩。
github

  • Chunk Basic Header:标识本块
  • Chunk Message Header:标识本快负载所属消息
  • Extended TimeStamp:当时间戳溢出时需要,扩展用

Chunk的默认大小是128字节,在传输过程中,通过一个叫做Set Chunk Size的控制信息可以设置Chunk数据量的最大值,在发送端和接收端会各自维护一个Chunk Size,可以分别设置这个值来改变自己这一方发送的Chunk的最大大小。大一点可以减少UPC占用率,会占用更多时间在发送上,低带宽下会占用大量传输时间,小一点虽然可以减少阻塞,但是这样会带来额外的信息,并且传输多次也会无法利用高带宽优势。因此他并不适合在高比特率的数据流传输

2.2.1 Chunk Basic Header(基本的头信息)

包含了 Chunk Stream ID(流通道ID-CSID)Chunk Type(Chunk类型-fmt)
前者用来标识一个特定流通道;后者则是类型。
该信息(基本头信息)长度可能为1、2、3个字节,ChunkType长度固定占2bit。
因此决定头信息长度就是CSID,RTMP协议支持用户自定义。0、1、2由协议保留特殊信息(0表示总共要占用2个字节)

  • 1Byte

     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |fmt| cs id |
    +-+-+-+-+-+-+-+-+

    可以看到fmt占用2位,之后6位都是CSID内容,取值范围在[0, 63]。其中用户自定义范围在[3, 63]。

  • 2Byte

     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |fmt| 0 | cs id - 64 |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    两字节时,CSID占14位,该协议将使fmt所在的字节中的其他位都置位0。剩下一个字节用来表示CSID,取值范围在[0, 255]。其中由于有6位无法读取,因此需要整体加上64,即[64, 319]。

  • 3Byte

     0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |fmt| 1 | cs id - 64 |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    三字节时,CSID占用22位,类似的,该协议将使fmt所在的字节中的其他位都置为1,剩下的2个字节来表示CSID,取值范围在[0, 65535]。理由同上,需要整体加上64,即[64, 65599]。

2.2.2 Chunk Message Header(消息的头信息)

包含了要发送实际信息的描述信息。该信息的格式和长度取决于 Basic Header 中的 Chunk Type,共有4中格式。其中第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前的Chunk的差量化的表示,因此可以数据量更小。

  • Chunk Type = 0

     0               1               2               3
    0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | timestamp |message length |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | message length (coutinue) |message type id| msg stream id |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | msg stream id |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    占用11个字节,其他三种能表示的数据都可以表示。但是在ChunkStream开始的第一个Chunk和头信息中的时间戳后退(回退播放)的适合必须采用这种格式。

    timestamp:时间戳,占用3个字节,当他超过最大值时,三个字节都置位1。这样实际的timestamp会转存到Extended Timestamp字段中,接受端会根据这个情况到Extended TimeStamp中解析实际的时间戳。
    msg length:消息数据的长度,占用3个字节,表示实际发送消息的数据(音频、视频)长度。这里是指Chunk的总长度,而不是Chunk Data的长度。
    msg type id:消息类型,占用1个字节,表示实际发送消息的类型,8表示音频,9表示视频。
    msg stream id:流媒体ID,占用4个字节,表示该Chunk所在的流的ID,与Basic Hander中的CSID一样。

  • Chunk Type = 1

     0               1               2               3
    0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | timestamp delta |message length |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | message length (coutinue) |message type id|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    占用7个字节,省略的Message Stream Id的4个字节。表示该Chunk与上一次发的Chunk所在的流相同。

    timestamp delta:占用3个字节,和type=0时不同,存储的是和上一次Chunk的时间差。相同的在满存储后依旧会转存到Extended Timestamp字段汇总。
    其他:参照上面的描述

  • Chunk Type = 2

     0               1               2               
    0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | timestamp delta |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    占用3个字节,相对与type=1时,省去了消息长度和消息类型,表示与上一次发送的Chunk都相同。

  • Chunk Type = 3
    为0字节,表示这个Chunk的MessageHeader与上一次发送的完全相同。时间上面则是当type=0时表示同一个时间戳,type=1或者2时表示同一个时间戳的差,但是比较的时候是正常比较。

2.2.3 Extended Timestamp(扩展时间戳)

这里应该就清除了为什么是在溢出时用到的时间戳信息。当Message Header中的时间戳信息全部置位1时才会在这里启用,表示时间戳信息已溢出,请到扩展时间戳内获取完整时间戳。

2.2.4 Chunk Data

数据块

2.3 消息分块

在消息被分割成多个消息块的过程中,默认每个固定块大小是128字节(最后一个数据库可小于该固定长度)。
github
在传输过程中,通过一个叫做Set Chunk Size的制信息可以设置Chunk数据量的最大值,在发送端和接受端会各自维护一个Chunk Size,可以分别设置这个值来改变自己这一方发送的Chunk的最大大小。大一点可以减少UPC占用率,会占用更多时间在发送还是那个,低带宽下会占用大量传输时间,小一点虽然可以减少阻塞,但是这样会带来额外的信息,并且传输多次也会无法利用高带宽优势。
传输过程中,发送端首先将媒体数据封装成消息,然后将消息分割成消息块,最后将分割的消息通过TCP协议发送出去。接收端通过TCP协议收到数据后,首先将消息块组合起成消息,然后对消息进行解封装就可以恢复媒体数据。