RTMP

RTMP 块流

握手

一个 RTMP 连接以握手开始。RTMP 的握手不同于其他协议;RTMP 握手由三个固定长度的块组成,而不是像其他协议一样的带有报头的可变长度的块。客户端 (发起连接请求的终端) 和服务器端各自发送相同的三块。便于演示,当发送自客户端时这些块被指定为 C0、C1 和 C2;当发送自服务器端时这些块分别被指定为 S0、S1 和 S2。

握手顺序

  1. 握手以客户端发送 C0 和 C1 块开始。
  2. 客户端必须等待接收到 S1 才能发送 C2。
  3. 客户端必须等待接收到 S2 才能发送任何其他数据。
  4. 服务器端必须等待接收到 C0 才能发送 S0 和 S1,也可以等待接收到 C1 再发送 S0 和 S1。服务器端必须等待接收到 C1 才能发送 S2。服务器端必须等待接收到 C2 才能发送任何其他数据。

C0 和 S0 的格式

C0 和 S0 包都是一个单一的八位字节,以一个单独的八位整型域进行处理,版本号 (八位):在 C0 中,这一字段指示出客户端要求的 RTMP 版本号。在 S0 中,这一字段指示出服务器端选择的 RTMP 版本号。本文档中规范的版本号为 3。

C1 和 S1 的格式

C1 和 S1 数据包的长度都是 1536 字节,包含以下字段:

  • Time (四个字节):这个字段包含一个 timestamp,用于本终端发送的所有后续块的时间起点。这个值可以是 0,或者一些任意值。要同步多个块流,终端可以发送其他块流当前的 timestamp 的值。
  • Zero (四个字节):这个字段必须都是 0。
  • Random data (1528 个字节):这个字段可以包含任意值。终端需要区分出响应来自它发起的握手还是对端发起的握手,这个数据应该发送一些足够随机的数。这个不需要对随机数进行加密保护,也不需要动态值。

C2 和 S2 的格式

  • Time (四个字节):这个字段必须包含终端在 S1 (给 C2) 或者 C1 (给 S2) 发的 timestamp。
  • Time2 (四个字节):这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。
  • Random echo (1528 个字节):这个字段必须包含终端发的 S1 (给 C2) 或者 S2 (给 C1) 的随机数。两端都可以一起使用 time 和 time2 字段再加当前 timestamp 以快速估算带宽和/或者连接延迟,但这不太可能是有多大用处。

分块

握手之后,连接开始对一个或多个块流进行合并。创建的每个块都有一个唯一 ID 对其进行关联,这个 ID 叫做 chunk stream ID (块流 ID)。这些块通过网络进行传输。传递时,每个块必须被完全发送才可以发送下一块。在接收端,这些块被根据块流 ID 被组装成消息。

分块允许上层协议将大的消息分解为更小的消息,例如,防止体积大的但优先级小的消息 (比如视频) 阻碍体积较小但优先级高的消息 (比如音频或者控制命令)。分块也让我们能够使用较小开销发送小消息,因为块头包含包含在消息内部的信息压缩提示。块的大小是可以配置的。它可以使用一个设置块大小的控制消息进行设置 (参考 5.4.1)。更大的块大小可以降低 CPU 开销,但在低带宽连接时因为它的大量的写入也会延迟其他内容的传递。更小的块不利于高比特率的流化。所以块的大小设置取决于具体情况。

块格式

每个块包含一个头(Chunk Header)和数据体(Chunk Data)。

块头包含三个部分:

  • Basic Header (基本头,1 到 3 个字节):这个字段对块流 ID 和块类型进行编码。块类型决定了消息头的编码格式。(这一字段的) 长度完全取决于块流 ID,因为块流 ID 是一个可变长度的字段。
  • Message Header (消息头,0,3,7,或者 11 个字节):这一字段对正在发送的消息 (不管是整个消息,还是只是一小部分) 的信息进行编码。这一字段的长度可以使用块头中定义的块类型进行决定。
  • Extended Timestamp (扩展 timestamp,0 或 4 字节):这一字段是否出现取决于块消息头中的 timestamp 或者 timestamp delta 字段。更多信息参考 5.3.1.3 节。

Chunk Data (有效大小):当前块的有效负载,相当于定义的最大块大小。

Basic Header

块基本头对块类型(”fmt” 字段)和块流ID进行编码。块基本头字段可能会有 1,2 或者 3 个字节,取决于块流 ID。一个 (RTMP) 实现应该使用能够容纳这个 ID 的最小的容量进行表示。RTMP 协议最多支持 65597 个流,流 ID 范围 3 - 65599。ID 0、1、2 被保留。

块类型: 0 值表示二字节形式,并且 ID 范围 64 - 319 (第二个字节 + 64)。

块类型: 1 值表示三字节形式,并且 ID 范围为 64 - 65599 ((第三个字节) * 256 + 第二个字节 + 64)。

块类型: 2 值的块流 ID 被保留,用于下层协议控制消息和命令。

一个字节,块基本头中的 0 - 5 位 (最低有效) 代表块流 ID,范围3-63。

Message Header

块消息头又四种不同的格式,由块基本头中的 “fmt” 字段进行选择。一个 (RTMP) 实现应该为每个块消息头使用最紧凑的表示。

类型 0 块头的长度是 11 个字节。这一类型必须用在块流的起始位置,和流 timestamp 重来的时候 (比如,重置)。

类型 1 块头长为 7 个字节。不包含消息流 ID;这一块使用前一块一样的流 ID。可变长度消息的流 (例如,一些视频格式) 应该在第一块之后使用这一格式表示之后的每个新消息。

类型 2 块头长度为 3 个字节。既不包含流 ID 也不包含消息长度;这一块具有和前一块相同的流 ID 和消息长度。具有不变长度的消息 (例如,一些音频和数据格式) 应该在第一块之后使用这一格式表示之后的每个新消息。

类型 3 的块没有消息头。流 ID、消息长度以及 timestamp delta 等字段都不存在;这种类型的块使用前面块一样的块流 ID。当单一一个消息被分割为多块时,除了第一块的其他块都应该使用这种类型。

Extended Timestamp

扩展 timestamp 字段用于对大于 16777215 (0xFFFFFF) 的 timestamp 或者 timestamp delta 进行编码;也就是,对于不适合于在 24 位的类型 0、1 和 2 的块里的 timestamp 和 timestamp delta 编码。这一字段包含了整个 32 位的 timestamp 或者 timestamp delta 编码。可以通过设置类型 0 块的 timestamp 字段、类型 1 或者 2 块的 timestamp delta 字段 16777215 (0xFFFFFF) 来启用这一字段。当最近的具有同一块流的类型 0、1 或 2 块指示扩展 timestamp 字段出现时,这一字段才会在类型为 3 的块中出现。

协议控制消息

RTMP 消息格式

这一节定义了使用下层传输层 (比如 RTMP 块流协议) 传输的 RTMP 消息的格式。RTMP 协议设计使用 RTMP 块流,可以使用其他任意传输协议对消息进行发送。RTMP 块流和 RTMP 一起适用于多种音频 - 视频应用,从一对一和一对多直播到点播服务,再到互动会议应用。

消息头

消息头包含以下:

  • Message Type (消息类型):一个字节的字段来表示消息类型。类型 ID 1 - 6 被保留用于协议控制消息。
  • Length (长度):三个字节的字段来表示有效负载的字节数。以大端格式保存。
  • Timestamp:四个字节的字段包含了当前消息的 timestamp。四个字节也以大端格式保存。
  • Message Stream Id (消息流 ID):三个字节的字段以指示出当前消息的流。这三个字节以大端格式保存。

消息有效载荷

消息的另一个部分就是有效负载,这是这个消息所包含的实际内容。例如,它可以是一些音频样本或者压缩的视频数据。有效载荷格式和解释不在本文档范围之内。