本节指定实时消息传递协议区块流(RTMP区块流)。它为更高级的多媒体流协议提供多路复用和分组服务。
虽然RTMP区块流设计用于与实时消息协议(第6节)配合使用,但它可以处理发送消息流的任何协议。每条消息都包含时间戳和有效负载类型标识。RTMP Chunk Stream和RTMP一起适用于各种音频视频应用,从一对一和一对多直播到视频点播服务再到交互式会议应用。
当与可靠的传输协议(如TCP[RFC0793])一起使用时,
RTMP区块流可以横跨多个流,并且保证所有消息按照时间戳的顺序有序地在端到端之间传递。RTMP区块流不提供任何优先级或类似形式的控制,但可由更高级别的协议用于提供此类优先级。例如,实时视频服务器可能会选择删除慢速客户端的视频消息,以确保根据发送时间或确认每条消息的时间及时接收音频消息。
RTMP区块流包含其自己的带内协议控制消息,并且还为更高级别的协议嵌入用户控制消息提供了一种机制。
消息格式是否可以被拆分为块以便支持多路复用取决于更高级的协议。但是,消息格式应包含创建块所需的以下字段。
RTMP连接从握手开始。握手与协议的其他部分不同;它由三个静态大小的块组成,而不是由带有标题的可变大小的块组成。
客户端(启动连接的端点)和服务器分别发送相同的三个数据块。为了方便说明,当客户端发送时,这些块将被指定为C0、C1和C2;而服务器发送时这些块被指定为S0、S1和S2。
握手从客户端发送C0和C1块开始。
客户端必须等到收到S1后再发送C2。在发送任何其他数据之前,客户端必须等到收到S2。
服务器在发送S0和S1之前必须等到收到C0,也可以等到C1之后。服务器必须等到收到C1后再发送S2。在发送任何其他数据之前,服务器必须等待收到C2。
C0和S0数据包是一个八进制数字,被视作为一个8bit的整数字段:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
以下是C0/S0数据包中的字段:
C1 和 S1 包长度为 1536 字节,包含以下字段:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C1 and S1 bits
C2和S2数据包的长度为1536个字节,几乎是S1和C1的拷贝,由以下字段组成:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time2 (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C2 and S2 bits
+-------------+ +-------------+
| Client | TCP/IP Network | Server |
+-------------+ | +-------------+
| | |
Uninitialized | Uninitialized
| C0 | |
|------------------->| C0 |
| |-------------------->|
| C1 | |
|------------------->| S0 |
| |<--------------------|
| | S1 |
Version sent |<--------------------|
| S0 | |
|<-------------------| |
| S1 | |
|<-------------------| Version sent
| | C1 |
| |-------------------->|
| C2 | |
|------------------->| S2 |
| |<--------------------|
Ack sent | Ack Sent
| S2 | |
|<-------------------| |
| | C2 |
| |-------------------->|
Handshake Done | Handshake Done
| | |
Pictorial Representation of Handshake
以下描述了握手图中提到的状态:
在经历握手后,这个连接会多路复用一个或者多个块流。每个区块流携带来自一个消息流的一种类型的消息。创建的每个区块都有一个与之关联的唯一ID,称为区块流ID(chunk stream ID)。块通过网络传输。传输时,必须在下一个数据块之前完整发送每个数据块。在接收端,根据区块流ID将区块组装成消息。
分块允许将高级协议中的大型消息分解为较小的消息,例如,防止大型低优先级消息(如视频)阻止较小的高优先级消息(如音频或控制)。
分块还允许以较少的开销发送小消息,因为分块头包含信息的压缩形式,否则必须包含在消息本身中。
区块大小是可配置的。它可以使用 Set Chunk Size control message 设置。较大的块大小可以减少CPU使用,但也会导致较大的写操作,从而延迟低带宽连接上的其他内容。较小的数据块不适用于高比特率流。块大小在每个方向上都是独立保持的。
每个块由一个头部以及数据组成。头部本身有三个部分:
+--------------+----------------+--------------------+--------------+
| Basic Header | Message Header | Extended Timestamp | Chunk Data |
+--------------+----------------+--------------------+--------------+
| |
|<------------------- Chunk Header ----------------->|
Chunk Format
Chunk Basic Header 对区块流ID和区块类型进行编码(由下图中的fmt字段表示)。区块类型确定编码消息头的格式。区块基本标头字段可以是1、2或3字节,具体取决于区块流ID。
一个好的实现将会以最小的表达形式来存储 ID。
该协议最多支持65597个ID为3-65599的流。ID 0、1和2被保留。值0表示2字节形式和64-319范围内的ID(第二个字节+64)。值1表示3字节形式和64-65599范围内的ID((第三个字节)*256+第二个字节+64)。范围为3-63的值表示完整的流ID。值为2的区块流ID保留用于底层协议控制消息和命令。区块基本标头中的位0-5(least significant)表示区块流ID。块流 ID 2-63 在这个字段中可以以1字节的形式编码。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
Chunk basic header 1
区块流ID 64-319可以以头的2字节形式编码。ID计算为(第二个字节+64)。
0 1
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk basic header 2
块流ID 64-65599可以在此字段的3字节版本中进行编码。ID计算为((第三个字节)*256+(第二个字节)+64)。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk basic header 3
块消息头(Chunk Message Header)有四种不同的格式,由块基本头部(Chunk Basic Header)中的“fmt”字段选择。
实现应该为每个块消息头部使用尽可能紧凑的表示。
类型0块头的长度为11字节。此类型必须在块流的开始以及流时间戳向后时使用(例如,由于向后搜索)。
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 (cont) |message type id| msg stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message stream id (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header - Type 0
类型1块头的长度为7字节。不包括消息流ID(message stream ID);此块采用与前一块相同的流ID(stream ID)。具有可变大小消息(例如,许多视频格式)的流应在第一个消息之后的每个新消息的第一个块中使用此格式。
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 (cont) |message type id|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header - Type 1
类型2块头的长度为3字节。不包括流ID(stream ID)和消息长度(message length);此区块与前一区块具有相同的流ID和消息长度。具有恒定大小消息的流(例如,某些音频和数据格式)应在第一个消息之后的每个消息的第一个块中使用此格式。
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header - Type 2
类型3块没有消息头。流ID、消息长度和时间戳增量字段不存在;此类型的块从前面的块中获取相同块流ID的值。当单个消息拆分为块时,除第一个块外的所有消息块都应使用此类型,参考示例2(Example 2)。由大小、流ID和时间间隔完全相同的消息组成的流应在类型2的块之后的所有块中使用此类型,参考示例1(Example 1)。如果第一条消息和第二条消息之间的增量与第一条消息的时间戳相同,则类型3的块可以立即跟随类型0的块,因为不需要类型2的块来注册增量。如果类型3块跟随类型0块,则此类型3块的时间戳增量与类型0块的时间戳相同。
块消息头(chunk message header)每个字段的描述:
扩展时间戳字段用于对大于16777215(0xFFFFFF)的时间戳或时间戳增量进行编码;也就是说,对于不适合0、1或2类块的24位字段的时间戳或时间戳增量。 也就是说,24位的bit可能不适合于编码时间戳(timestamp)或时间戳增量(timestamp deltas)(此字段存在于 Type 0、Type 1、Type 2 的块中)。此字段对完整的32位时间戳或时间戳增量进行编码。通过将 Type 0 的时间戳字段或 Type 1或 Type 2 的时间戳增量字段设置为16777215(0xFFFFFF)来指示此字段的存在。当同一块流ID(chunk stream ID)最近更新的 Type 0、Type 1、Type 2 块指示存在扩展时间戳字段时,此字段存在于类型3块中。
此示例显示了一个简单的音频消息流。此示例演示了信息的冗余。
+---------+-----------------+-----------------+-----------------+
| |Message Stream ID| Message TYpe ID | Time | Length |
+---------+-----------------+-----------------+-------+---------+
| Msg # 1 | 12345 | 8 | 1000 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 2 | 12345 | 8 | 1020 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 3 | 12345 | 8 | 1040 | 32 |
+---------+-----------------+-----------------+-------+---------+
| Msg # 4 | 12345 | 8 | 1060 | 32 |
+---------+-----------------+-----------------+-------+---------+
Sample audio messages to be made into chunks
下一个表显示了此流中生成的块。从消息3开始,数据传输得到优化。在此点之后,每条消息只有1字节的开销。
+--------+---------+-----+------------+------- ---+------------+
| | Chunk |Chunk|Header Data |No.of Bytes|Total No.of |
| |Stream ID|Type | |After |Bytes in the|
| | | | |Header |Chunk |
+--------+---------+-----+------------+-----------+------------+
|Chunk#1 | 3 | 0 | delta: 1000| 32 | 44 |
| | | | length: 32,| | |
| | | | type: 8, | | |
| | | | stream ID: | | |
| | | | 12345 (11 | | |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
|Chunk#2 | 3 | 2 | 20 (3 | 32 | 36 |
| | | | bytes) | | |
+--------+---------+-----+----+-------+-----------+------------+
|Chunk#3 | 3 | 3 | none (0 | 32 | 33 |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
|Chunk#4 | 3 | 3 | none (0 | 32 | 33 |
| | | | bytes) | | |
+--------+---------+-----+------------+-----------+------------+
Format of each of the chunks of audio messages
此示例演示了一条消息,该消息太长,无法放入128字节的块中,并且被分成了几个块。
+-----------+-------------------+-----------------+-----------------+
| | Message Stream ID | Message TYpe ID | Time | Length |
+-----------+-------------------+-----------------+-----------------+
| Msg # 1 | 12346 | 9 (video) | 1000 | 307 |
+-----------+-------------------+-----------------+-----------------+
Sample Message to be broken to chunks
以下是生成的块:
+-------+------+-----+-------------+-----------+------------+
| |Chunk |Chunk|Header |No. of |Total No. of|
| |Stream| Type|Data |Bytes after| bytes in |
| | ID | | | Header | the chunk |
+-------+------+-----+-------------+-----------+------------+
|Chunk#1| 4 | 0 | delta: 1000 | 128 | 140 |
| | | | length: 307 | | |
| | | | type: 9, | | |
| | | | stream ID: | | |
| | | | 12346 (11 | | |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
|Chunk#2| 4 | 3 | none (0 | 128 | 129 |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
|Chunk#3| 4 | 3 | none (0 | 51 | 52 |
| | | | bytes) | | |
+-------+------+-----+-------------+-----------+------------+
Format of each of the chunks
块1的头部数据(header data)指定整个消息为307字节。
请注意,从这两个示例中,Type 3 的块可以以两种不同的方式使用。第一个是用于指定消息的延续。第二种方法是明确一个新消息的开头,该消息的头部可以从现有状态数据派生。
RTMP块流使用消息类型ID(message type IDs)1、2、3、5和6进行协议信息控制。这些消息包含RTMP区块流协议所需的信息。
这些协议控制消息必须具有消息流(message stream)ID 0(称为控制流,control stream),并以区块(chunk stream)流ID 2的形式发送。协议控制消息在收到后立即生效;它们的时间戳被忽略。
协议控制消息1(设置块大小)用于通知对等方新的最大块大小。
最大区块大小默认为128字节,但客户端或服务器可以更改此值,并使用此消息更新其对等方。例如,假设一个客户端想要发送131字节的音频数据,而数据块大小是128。在这种情况下,客户端可以将此消息发送到服务器,通知它数据块大小现在是131字节。然后,客户端可以在单个块中发送音频数据。
最大块大小应至少为128字节,并且必须至少为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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0| chunk size (31 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Set Chunk Size’ protocol message
协议控制消息2,中止消息,用于通知对等方如果对等方正在等待块完成消息,然后通过块流丢弃部分接收的消息。对等方接收区块流ID作为此协议消息的有效负载。应用程序可在关闭时发送此消息,以指示不需要进一步处理消息。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chunk stream id (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Abort Message’ protocol message
客户端或服务器必须在收到等于窗口大小的字节后向对等方发送确认。窗口大小是发送方在未收到接收方确认的情况下发送的最大字节数。此消息指定序列号,即到目前为止接收到的字节数。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sequence number (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Acknowledgement’ protocol message
客户端或服务器发送此消息以通知对等方在发送确认之间要使用的窗口大小。在发送方发送窗口大小字节后,发送方希望得到对等方的确认。自上次确认发送后,接收端必须在收到指定字节数后发送确认,如果尚未发送确认,则从会话开始发送确认。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload for the ‘Window Acknowledgement Size’ protocol message
客户端或服务器发送此消息以限制其对等服务器的输出带宽。接收此消息的对等方通过将已发送但未确认的数据量限制在此消息中指示的窗口大小来限制其输出带宽。如果窗口大小与上次发送给此消息发送方的窗口大小不同,则接收此消息的对等方应使用窗口确认大小消息(Window Acknowledgement Size)进行响应。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Limit Type |
+-+-+-+-+-+-+-+-+
Payload for the ‘Set Peer Bandwidth’ protocol message
Limit Type是以下值之一: