5.3 分块
握手完成后,连接复用一个或多个块流,每个块流承载来自同一个消息流的同一类消息。创建的每个块都有一个唯一的块流ID,这些块通过网络进行传输。 在传输过程中,必须一个块发送完毕之后再发送下一个块。在接收端,将所有块根据块中的块流ID组装成消息。
分块将上层协议的大消息分割成小的消息,保证大的低优先级消息(比如视频)不阻塞小的高优先级消息(比如音频或控制消息)。
分块还能降低消息发送的开销,它在块头中包含了压缩的原本需要在消息中所包含的信息。
块大小是可配置的,这个可以通过一个设置块大小控制消息进行设定修改,详细信息在5.4.1章描述。越大的块CPU使用率越低,但是在低带宽的情况下,大的写入会阻塞其他内容的写入;而小一些的块不适合高比特率的流。块大小在每个方向上保持独立。
5.3.1 块格式
块由块头和数据组成,块头包含3部分:基本头、消息头和扩展时间戳。
- 基本头(1-3字节): 该部分编码块流ID和块类型,块类型决定了消息头的编码格式。该部分的长度取决于块流ID,块流ID是一个变长字段。
- 消息头(0,3,7或11字节): 该部分编码所发送消息的描述信息(无论是整个消息还是一部分)。该部分的长度取决于基本头中指定的块类型。
- 扩展时间戳(0或4字节): 该部分只有在某些特殊情况下才会使用,是否使用取决与块消息头中的时间戳或时间戳增量,详细信息请参阅5.3.1.3。
- 块数据(变长): 块承载的有效数据,长度最大为配置的块大小。
5.3.1.1 块基本头
块基本头编码块流ID和块类型(在下图中用fmt字段表示),块类型决定了消息头的编码格式,块基本头长度可能是1,2或3字节,这取决于块流ID的长度。
协议实现方应该用能够承载块流ID的最短表示法来表示块流ID。
RTMP最多支持65597个流,ID在3-65599范围内。ID0,1,2为保留值。0表示块基本头为2个字节,并且块流ID范围在64-319之间(第二个字节+64);1表示块基本头为3个字节,并且ID范围在64-65599之间(第三个字节*256 + 第二个字节 + 64);取值在3-63之间的ID表示完整的流ID。值2是为低版本协议保留的,用于协议控制消息和命令,第0-5位(不重要的)表示块流ID。
2-63范围内的块流ID可以用1个字节来编码。
64-319范围内的块流ID可以用2个字节来编码,块流ID为计算所得,公式为:第二个字节值 + 64.
64-65599范围内的块流ID可以用3个字节来编码,块流ID为计算所得,公式为:第三个字节值*255 + 第二个字节值 + 64.
- fmt: 该字段表明了块消息头可以使用的4种格式的哪一种,每种块类型的消息头详情,在下一节描述。
- cs id(6位): 该字段承载了完整的块流ID,取值在2-63之间。0,1两个值是保留值,用来表示块基本头是2字节还是3字节长度。
- cs id - 64(8位或16位): 该字段承载块流ID,取值在64-63399之间。 例如:ID365应该在cs id字段位置取值为1,并在该16位字段位置取值301。
64-319范围内的块流ID可以用2字节长度来编码,也可以用3字节长度来编码。
5.3.1.2 块消息头
块消息头共有4种不同的格式,根据块基本头中的"fmt"字段值来选择。协议实现方应该用最紧凑的格式来表示块消息头。
5.3.1.2.1 块类型0
0类型的块消息头占11个字节长度,该类型必须用在一个块流的开头, 和每当块流时间戳后退的时候(例如向后搜索的操作)。
- timestamp(3字节): 对于0类型的消息块, 消息的绝对时间戳在这里发送。 如果时间戳大于或等于16777215(0xFFFFFF),改字段值必须为16777215,并且必须设置扩展时间戳来共同编码32位的时间戳。否则该字段就是完整的时间戳。
其他字段描述请参考5.3.1.2.5节
5.3.1.2.2 块类型1
1类型的块消息头占用7个字节长度,不包含消息流ID,该块沿用上一个消息的消息流ID。对于传输大小可变消息的流(如多数视频格式),在发送第一个消息之后的每个消息,第一个块都应该使用该类型格式。
5.3.1.2.3 块类型2
2类型的块消息头占用3个字节长度,不包含消息流ID和消息长度,沿用上一个块的消息流ID和消息长度。对于传输固定大小消息的流(如音频和数据格式),在发送第一个消息之后的每一个消息,第一个块都应该使用该类型格式。
5.3.1.2.4 块类型3
3类型的块没有消息头,消息流ID、消息长度和时间戳增量都不指定,该类型的块都使用上一个块相同的块流ID。当一个消息被分割成块时,除了第一个块,其他块都应该使用该类型。示例请参考5.3.2.2节。由相同大小、消息流ID和时间间隔的消息组成的流,在类型2的块之后所有块都应该使用该类型格式。示例请参考5.3.2.1节。如果第一个消息和第二消息之间的时间增量与第一个消息的时间戳相同,则0类型的块之后可以马上发送3类型的块,而不必使用2类型的块来注册时间增量。如果类型3的块跟在类型0的块后面,那么3类型块的时间戳增量与0类型块的时间戳相同。
5.3.1.2.5 公共头字段
描述块消息头的其他字段:
timestamp delta(3字节): 时间戳增量。类型1和类型2的块包含此字段,表示前一个块的timestamp字段和当前块timestamp间的差值。 如果时间戳增量大于或等于16777215(0xFFFFFF),该字段必须为16777215,并且必须设置扩展时间戳,来共同表示32位的时间戳增量,否则该字段值就是实际的时间戳增量。 message length(3字节): 消息长度,类型0和类型1的块包含此字段,表示消息的长度。要注意的是,通常该长度与块负载长度并不相同。块负载长度除了最后一个块,都与块最大长度相同。 message type id(3字节): 消息类型id,类型0和类型1的块包含此字段,表示消息的类型。 message stream id(4字节): 消息流ID,类型0的块包含此字段,表示消息流ID。消息流ID以小字节序存储。通常,相同块流中的消息属于用一个消息流。虽然,不同的消息流复用相同的块流会导致消息头无法有效压缩,但是当一个消息流已关闭,才打开另外一个消息流,就可以通过发送一个新的0类型块来实现复用。
5.3.1.3 扩展时间戳
扩展时间戳用来辅助编码超过16777215(0xFFFFFF)的时间戳或时间戳增量,也就说0,1或2类型的块,无法用24位数字来表示时间戳或时间戳增量时,既0类型块的时间戳字段或1,2类型的时间戳增量字段值为16777215(0xFFFFFF)时。当最近的属于相同块流ID的0类型块、1类型块或2类型块有此字段时有此字段时,3类型块也应该有此字段。
5.3.2 示例
5.3.2.1 示例1
这是一个简单的音频流消息,这是示例示范了信息冗余。
下图展示该消息流分割成的块。从3类型块开始了数据传输优化,之后的块只附加了一个字节。
5.3.2.2 示例2
该示例展示了一个超过128字节长度的消息,消息被分割成了数个块。
下图是被分割成的块
第一个块的头信息指明了消息总大小为307字节。
注意这两个示例,3类型块可以在两种情况下使用。第一种情况是指明消息还未结束;另一种情况是指明新消息开始时,可以根据当前状态信息推导出新消息的头信息。