Welcome to RTMP’s documentation!

简介

本仓库主要用于介绍RTMP,包括协议实现等各个方面。

背景

RTMP基本介绍

RTMP:Real Time Messaging Protocol(实时消息传输协议)。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

RTMP基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。

  • RTMPT:封装在HTTP之上,可以更好地穿透防火墙。
  • RTMPS:类似RTMPT,增加了TLS/SSL的安全功能。
  • RTMPE:在RTMP的基础上增加了加密功能。

协议部分

应用部分

RTMP协议

基础定义部分

简介

本文档规定实时消息协议块流(RTMP 块流)。分块为更高层的多媒体流协议提供复用和分组服务。

虽然RTMP块流是为协同RTMP协议工作而设计的,但是它依然可以处理任何发送消息流的协议。每个消息包含时间戳负载类型标志。RTMP块流和RTMP共同适用于各种形式的音视频应用,从点到点和点到多的实时直播,到vod服务,到交互式视频会议。

当配合像TCP这样的可靠传输协议使用时,RTMP块流保证跨流的所有消息按时间戳序列一个接一个地传输。RTMP块流不提供优先级或类似的控制,但是可以通过更高层的协议提供类似的服务。例如,视频直播服务可以基于每个消息的发送时间和答复时间选择丢弃视频消息,使慢的客户端能及时接受到音频消息。

RTMP块流包含自己的带内协议控制消息,并且提供了让更高层的协议嵌入用户控制消息的机制。

术语

本文档中的关键词“必须”、“一定不”、“要求”、“可以”、“不可以”、“应该”、“不应该”、“建议”、“可能”和“可选”的解释参考文档 1, 2

定义

  • 负载:

    分组中所包含的数据。例如音频样本和压缩视频数据。负载格式和解释不在本文档的描述范围之内。

  • 分组:

    一个数据分组由固定的头和负载数据组成。一些底层协议可能需要定义分组的封装。

  • 端口:

    在一个给定计算机中区分不同目标的抽象。在TCP/IP协议中用一个小的整数来表示端口。OSI中的传输选择器等同于端口的概念。

  • 传输地址:

    用于表示一个传输层终端的网络地址和端口的组合。例如IP地址和TCP端口。分组从源地址传输到目标地址。

  • 消息流:

    允许消息流动的逻辑上的通讯通道。

  • 消息流ID:

    每个消息所关联的ID,用于区分其所在的消息流。

  • 块:

    一个消息片段。消息通常在被放到网络上传输之前被分成小的部分并且被交错存取。分块确保跨流的所有消息按时间戳顺序被不断的传输。

  • 块流:

    允许块按照某一方向流动的逻辑上的通讯通道。块流可以从客户端流向服务端,也可以从服务端流向客户端。

  • 块流ID:

    每个块所关联的用于区分其所在块流的ID。

  • 复用:

    把分开的音视频数据整合到一个数据流,让多个音视频流可以同步地传输的过程。

  • 解复用:

    复用的反向过程。交互的音视频数据被收集成原始的音频数据和视频数据。

字节序、对齐和时间格式

所有完整的字段都是按网络字节序被承载的,即,零字节是第一个字节,零位是一个字或字段中最显著的位。这种字节序就是所谓的“big-endian”。这种传输顺序的详细描述见 3。除非另行说明,本文档中的数字都是十进制数。

在没有特别说明的情况下RTMP块流中的所有数据都是按字节对齐的。例如,一个16位字段可能在奇数字节偏移的位置。在标有延拓的地方,延拓字节应赋予零值。

RTMP块流中的时间戳是用整数表示的,以毫秒为单位的相对时间,相对于一个未规定的起始时间。一般,每个块流的时间戳都从0开始,但是只要通讯的双方用统一的起始时间,可以不使用这种方法。要注意的是,这样跨多个块流(特别是不同主机之间)的同步需要用另外的机制来实现。

时间戳必须是单调递增的,并且是线性增长的。这样可以使应用程序处理同步,测量带宽,注入检测和进行流控制。

因为时间戳只有32位长,所以只能在50天以内循环。但是,因为流是可以不断的运行的,潜在地可以多年才结束?所以RTMP块流应用程序必须对减法和比较使用模运算,并且能够处理这种回绕。只要通讯双方一致,任何合理的方法都可使用。例如,一个应用程序可以假设,相邻的时间戳是 \( 2^{31} \) 毫秒,那么1000在4000000000之后,3000000000在4000000000之前。

时间戳增量也是以毫秒为单位的无符号整数。时间戳增量可以是24位或32位长。

消息格式(可以参考消息格式文档的第4节)

消息格式依赖于上层协议,可以被分成多个块以支持复用。但是消息格式必须包含下面这些创建块所必须的字段。

  • 时间戳:4个字节

    消息时间戳。

  • 长度:3个字节

    消息负载的长度,如果消息头无法被消减的话,应该包含在长度里。

  • 类型ID:1个字节

    一些类型ID是为消息控制协议保留的。这些ID繁衍供RTMP块流协议和高层协议使用的信息。所有其他的ID都用于更高层协议。RTMP块流协议对这些ID做不透明处理。事实上,RTMP块流协议不需要用本字段的值来区分类型;所有消息可以是同类型的,或者应用可以使用本字段来区分同步轨道而不是区分类型。

  • 消息流ID:4个字节

    消息流ID可以是任何的值。被复用到相同的块流的消息流依靠其消息ID来解复用。除此之外,对于RTMP块流协议来说,这个值是不透明的。这个值在块头使用 小字节序


1: Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, March 1997.

2: Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, March 1997.

3: J. Postel, “Internet Protocol”, STD 5, September 1981.

握手协议

一个RTMP连接以握手开始。这里的握手和其他协议的握手不一样。这里的握手由三个固定大小的块组成,而不是可变大小的块加上头。

客户端(发起连接的一方)和服务端各自发送三个相同的块。这些块如果是客户端发送的话记为C0,C1和C2,如果是服务端发送的话记为S0,S1和S2。

握手步骤

握手开始于客户端发送C0,C1块。

在发送C2之前客户端必须等待接收S1 。

在发送任何数据之前客户端必须等待接收S2。

服务端在发送S0和S1之前必须等待接收C0,也可以等待接收C1。

服务端在发送S2之前必须等待接收C1。

服务端在发送任何数据之前必须等待接收C2。

C0和S0消息格式

C0和S0是单独的一个字节。例如:

 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|    version    |
+-+-+-+-+-+-+-+-+

下面是C0和S0包的字段说明。

  • 版本:8位

    在C0中这个字段表示客户端要求的RTMP版本 。在S0中这个字段表示服务器选择的RTMP版本。本规范所定义的版本是3;0-2是早期产品所用的,已被丢弃;4-31保留在未来使用;32-255不允许使用(为了区分其他以某一字符开始的文本协议)。如果服务无法识别客户端请求的版本,应该返回3。客户端可以选择减到版本3或选择取消握手。

C1和S1消息格式

C1和S1消息有1536字节长,由下列字段组成。

 0                 1                   2                   3
 0 1 2 3 4 5 6 7 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 (1528 bytes)            |
|                            (cont)                           |
|                              ...                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

下面是C1和S1包的字段说明。

  • 时间:4字节

    本字段包含时间戳。该时间戳应该是发送这个数据块的端点的后续块的时间起始点。可以是0,或其他的任何值。为了同步多个流,端点可能发送其块流的当前值。

  • 零:4字节

    本字段必须是全零。

  • 随机数据:1528字节

    本字段可以包含任何值。因为每个端点必须用自己初始化的握手和对端初始化的握手来区分身份,所以这个数据应有充分的随机性。但是并不需要加密安全的随机值,或者动态值。

C2和S2消息格式

C2和S2消息有1536字节长。只是S1和C1的回复。本消息由下列字段组成。

 0                 1                   2                   3
 0 1 2 3 4 5 6 7 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 (1528 bytes)             |
|                            (cont)                           |
|                              ...                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

下面是C2和S2包的字段说明。

  • 时间:4字节

    本字段必须包含对等段发送的时间(对C2来说是S1,对S2来说是C1)。

  • 时间2:4字节

    本字段必须包含先前发送的并被对端读取的包的时间戳。

  • 随机回复:1528字节

    本字段必须包含对端发送的随机数据字段(对C2来说是S1,对S2来说是C1)。

每个对等端可以用时间和时间2字段中的时间戳来快速地估计带宽和延迟。但这样做可能并不实用。

握手时序图

+-----------------------------------------------------------+ 
| +-------------+                           +-------------+ | 
| |   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 |
|       |                    |                     |        |
+-----------------------------------------------------------+

下表描述握手中的状态机。

状态 描述
未初始化
在这个状态中发送双方的版本。
此时客户端和服务端都未初始化。
客户端在C0包中发送版本号。
如果服务端支持那个版本,则发送S0和S1作为响应;
否则,服务端采用适当的行为作为响应,在RTMP规范中应终止连接。
版本已发送
在未初始化状态之后客户端和服务端都进入版本已发送状态。
客户端等待S1包,服务端等待C1包。
在接收到所等待的包后客户端发送C2包,服务端发送S2包,进入发送确认状态。
确认发送
客户端和服务端依次等待S2和C2。
握手完成
客户端和服务端发送消息。

分块协议

握手之后,连接开始复用一个或多个块流。每个块流承载来自一个消息流的一类消息。每个被创建的块都关联到一个唯一的块流ID。所有的块都通过网络传输。在传输过程中,必须一个块发送完之后再发送下一个块。在接收端,每个块都根据块ID被收集成消息。

分块使高层协议的大消息分割成小的消息,保证大的低优先级消息不阻塞小的高优先级消息。

分块把原本应该消息中包含的信息压缩在块头中减少了小块消息发送的开销。

块大小是可配置的。这个可以在协议控制消息中描述的块消息中完成。最大块是65535字节,最小块是128字节。块越大CPU使用率越低,但是也导致大的写入,在低带宽下产生其他内容的延迟。块大小对每个方向都保持独立。

块格式

块由头和数据组成。块头由三部分组成:

+-------------+----------------+-------------------+--------------+ 
| Basic header|Chunk Msg Header|Extended Time Stamp|  Chunk Data  | 
+-------------+----------------+-------------------+--------------+
  • 块基本头:1到3字节

    本字段包含块流ID和块类型。块类型决定编码的消息头的格式。长度取决于块流ID。块流ID是可变长字段。

  • 块消息头:0,3,7或11字节

    本字段编码要发送的消息的信息。本字段的长度,取决于块头中指定的块类型。

  • 扩展时间戳:0个或4字节

    本字段必须在发送普通时间戳(普通时间戳是指块消息头中的时间戳)设置为0xffffff时发送,正常时间戳为其他值时都不应发送本值。当普通时间戳的值小于0xffffff时,本字段不用出现,而应当使用正常时间戳字段。

块基本头

块基本头由块类型FMT和块流ID组成。块类型FMT决定编码消息头的格式。块基本头字段可能是1,2或3个字节,取决于块流ID的大小。

一个实现应该用最少的数据来表示ID。

本协议支持65597种流,ID从2 - 65599。其中ID中0,1,2为保留字段,主要用于区分不同字节版本:

  • 0:使用2字节版本
  • 1:使用3字节版本
  • 2:使用1字节版本

所以协议组成部分如下:

  • FMT:2位

    本字段标识块消息头的4种格式。每种流类型的块消息头在下一节中表示。

  • cs id:6位

    本字段表示范围在2 - 63的块流ID。值0和1表示2或3字节版本。

  • cs id(>= 64):8 - 16位

    本字段包含块流ID减去64的值。例如365,使用cs id表示为1,并用cs id(>= 64)的16位表示301。

1字节版本
 0 1 2 3 4 5 6 7 
+-+-+-+-+-+-+-+-+ 
|fmt|   cs id   |
+-+-+-+-+-+-+-+-+

可以表示ID的范围:2 ~ 63。

2字节版本
 0                   1 
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
|fmt|     0     |   cs id - 64  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

可以表示ID的范围:64 ~ 319(64 + 255)。

3字节版本
 0                   1                   2
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
|fmt|     1     |           cs id - 64          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

可以表示ID的范围:64 ~ 65599(64 + 65535)。