一.简介
Adobe 公司的实时消息传输协议 (RTMP) 通过一个可靠地流传输提供了一个双向多通道消息服务,比如 TCP [RFC0793],意图在通信端之间传递带有时间信息的视频、音频和数据消息流。实现通常对不同类型的消息分配不同的优先级,当运载能力有限时,这会影响等待流传输的消息的次序。本文档将对实时流传输协议 (Real Time Messaging Protocol) 的语法和操作进行描述。
二.名词解释
**Payload (有效载荷):**包含于一个数据包中的数据,例如音频采样或者压缩的视频数据。payload 的格式和解释,超出了本文档的范围。
**Packet (数据包):**一个数据包由一个固定头和有效载荷数据构成。一些个底层协议可能会要求对数据包定义封装。
Port (端口):"传输协议用以区分开指定一台主机的不同目的地的一个抽象。TCP/IP 使用小的正整数对端口进行标识。" OSI 传输层使用的运输选择器 (TSEL) 相当于端口。
**Transport address (传输地址):**用以识别传输层端点的网络地址和端口的组合,例如一个 IP 地址和一个 TCP 端口。数据包由一个源传输地址传送到一个目的传输地址。
**Message stream (消息流):**通信中消息流通的一个逻辑通道。
**Message stream ID (消息流 ID):**每个消息有一个关联的 ID,使用 ID 可以识别出流通中的消息流。
**Chunk (块):**消息的一段。消息在网络发送之前被拆分成很多小的部分。块可以确保端到端交付所有消息有序 timestamp,即使有很多不同的流。
**Chunk stream (块流):**通信中允许块流向一个特定方向的逻辑通道。块流可以从客户端流向服务器,也可以从服务器流向客户端。
**Chunk stream ID (块流 ID):**每个块有一个关联的 ID,使用 ID 可以识别出流通中的块流。
**Multiplexing (合成):**将独立的音频/视频数据合成为一个连续的音频/视频流的加工,这样可以同时发送几个视频和音频。
**DeMultiplexing (分解):**Multiplexing 的逆向处理,将交叉的音频和视频数据还原成原始音频和视频数据的格式。
**Remote Procedure Call (RPC 远程方法调用):**允许客户端或服务器调用对端的一个子程序或者程序的请求。
**Metadata (元数据):**关于数据的一个描述。一个电影的 metadata 包括电影标题、持续时间、创建时间等等。
**Application Instance (应用实例):**服务器上应用的实例,客户端可以连接这个实例并发送连接请求。
**Action Message Format (AMF 动作消息格式协议):**一个用于序列化 ActionScript 对象图的紧凑的二进制格式。AMF 有两个版本:AMF 0 [AMF0] 和 AMF 3 [AMF3]。
三.字节序、对齐、时间格式
字节序:所有整数型属性以网络字节顺序传输,字节 0 代表第一个字节,零位是一个单词或字段最常用的有效位。字节序通常是大端排序。关于传输顺序的更多细节描述参考 IP 协议[RFC0791]。除非另外注明,本文档中的数值常量都是十进制的 (以 10 为基础)。
对齐:除非另有规定,RTMP 中的所有数据都是字节对准的;例如,一个十六位的属性可能会在一个奇字节偏移上。填充后,填充字节应该有零值。
**时间格式:**RTMP 中的 Timestamps 以一个整数形式给出,表示一个未指明的时间点。典型地,每个流会以一个为 0 的 timestamp 起始,但这不是必须的,只要双端能够就时间点达成一致。注意这意味着任意不同流 (尤其是来自不同主机的) 的同步需要 RTMP 之外的机制。
因为 timestamp 的长度为 32 位,每隔 49 天 17 小时 2 分钟和 47.296 秒就要重来一次。因为允许流连续传输,有可能要多年,RTMP 应用在处理 timestamp 时应该使用序列码算法 [RFC1982],并且能够处理无限循环。例如,一个应用假定所有相邻的 timestamp 都在 2^31 - 1 毫秒之内,因此 10000 在 4000000000 之后,而 3000000000 在 4000000000 之前。
timestamp 也可以使用无符整数定义,相对于前面的 timestamp。timestamp 的长度可能会是 24 位或者 32 位。
四.RTMP块流
本节介绍实时消息传输协议的块流 (RTMP 块流)。 它为上层多媒体流协议提供合并和打包的服务。
当设计 RTMP 块流使用实时消息传输协议时,它可以处理任何发送消息流的协议。每个消息包含 timestamp 和 payload 类型标识。RTMP 块流和 RTMP 一起适合各种音频-视频应用,从一对一和一对多直播到点播服务,到互动会议应用。
当使用可靠传输协议时,比如 TCP [RFC0793],RTMP 块流能够对于多流提供所有消息可靠的 timestamp 有序端对端传输。RTMP 块流并不提供任何优先权或类似形式的控制,但是可以被上层协议用来提供这种优先级。例如,一个直播视频服务器可能会基于发送时间或者每个消息的确认时间丢弃一个传输缓慢的客户端的视频消息以确保及时获取其音频消息。
RTMP 块流包括其自身的带内协议控制信息,并且提供机制为上层协议植入用户控制消息。
4.1 消息格式
可以被分割为块以支持组合的消息的格式取决于上层协议。消息格式必须包含以下创建块所需的字段。
**Timestamp:**消息的 timestamp。这个字段可以传输四个字节。
**Length:**消息的有效负载长度。如果不能省略掉消息头,那它也被包括进这个长度。这个字段占用了块头的三个字节。
**Type Id:**一些类型 ID 保留给协议控制消息使用。这些传播信息的消息由 RTMP 块流协议和上层协议共同处理。其他的所有类型 ID 可用于上层协议,它们被 RTMP 块流处理为不透明值。事实上,RTMP 块流中没有任何地方要把这些值当做类型使用;所有消息必须是同一类型,或者应用使用这一字段来区分同步跟踪,而不是类型。这一字段占用了块头的一个字节。
**Message Stream ID:**message stream (消息流) ID 可以使任意值。合并到同一个块流的不同的消息流是根据各自的消息流 ID 进行分解。除此之外,对 RTMP 块流而言,这是一个不透明的值。这个字段以小端格式占用了块头的四个字节。
4.2握手
RTMP 的握手过程非常独特,它是一个固定的、不加密的、对称的 三次消息交换过程。它的主要目的不是为了安全 ,而是为了建立连接、确认双方都能正常收发数据,并测量初始的往返延迟 (RTT)。
4.2.1握手过程
整个握手过程分为三个步骤,交换三个"数据包":C0, C1, C2 (Client 发送) 和 S0, S1, S2 (Server 发送)。实际上,它们是连续发送的,通常在网络包中表现为三次交换。
数据包结构
- C0 和 S0: 1 个字节。
- C1 和 S1: 1536 个字节。
- C2 和 S2: 1536 个字节。
握手流程 (三次信息交换)
第一步:客户端发送 C0 + C1
客户端(比如你的 OBS)主动发起连接后,会立即向服务器发送两个紧挨着的数据块:C0 和 C1。总共发送 1 + 1536 = 1537 个字节。
- C0 (Client Version) :
- 大小: 1 个字节。
- 内容 : 指定客户端请求的 RTMP 版本号。在当前的标准中,这个值通常是 0x03 (代表版本 3)。
- 作用: 告诉服务器:"你好,我想用 RTMP 版本 3 和你通信"。如果服务器不支持这个版本,可以拒绝连接。
- C1 (Client Packet) :
- 大小: 1536 个字节。
- 内容 : 这是一个结构化的数据块:
- Time (4 字节) : 客户端本地的时间戳。记录了客户端发送 C1 时的本地时间(通常是毫秒)。
- Zero (4 字节): 必须是 0x00000000。这是协议的保留字段。
- Random Data (1528 字节) : 剩下的是一长串随机生成的字节数据。
- 作用 :
- 唯一性: 随机数据使得每个 C1 包都独一无二。
- 延迟测量 : 服务器收到 C1 后,会把它原封不动地在 S2 中返回。客户端收到 S2 后,用当前时间减去 C1 中记录的初始时间戳,就能得到第一次的 RTT (Round-Trip Time)。
第二步:服务器发送 S0 + S1 + S2
服务器在收到客户端的 C0+C1 后,会立即回复三个紧挨着的数据块:S0, S1, S2。总共发送 1 + 1536 + 1536 = 3073 个字节。
- S0 (Server Version) :
- 大小: 1 个字节。
- 内容: 服务器选择的 RTMP 版本号。如果服务器同意使用客户端请求的版本,它也会返回 0x03。
- 作用: 确认双方使用的协议版本一致。
- S1 (Server Packet) :
- 大小: 1536 个字节。
- 内容 : 结构与 C1 完全相同,但填充的是服务器自己的数据 。
- Time (4 字节): 服务器本地的时间戳。
- Zero (4 字节): 同样是 0x00000000。
- Random Data (1528 字节): 服务器自己生成的随机数据。
- 作用: 同 C1,用于后续客户端的验证和延迟测量。
- S2 (Server Echo) :
- 大小: 1536 个字节。
- 内容 : 这里是关键!S2 的内容必须与服务器收到的 C1 包的内容完全一样! 它是 C1 的一个精确"回声 (Echo)"。
- 作用 :
- 确认收到: 客户端收到 S2 后,通过比较 S2 的内容和自己之前发送的 C1 是否一致,就可以确认服务器已经成功收到了它的 C1 包。
- 延迟测量: 如前所述,客户端可以用 S2 来计算 RTT。
第三步:客户端发送 C2
客户端在收到服务器的 S0+S1+S2 后,会发送最后一个握手包 C2。
- C2 (Client Echo) :
- 大小: 1536 个字节。
- 内容 : 与 S2 类似,C2 的内容必须与客户端收到的 S1 包的内容完全一样! 它是 S1 的一个"回声"。
- 作用: 服务器收到 C2 后,通过比较 C2 和自己之前发送的 S1 是否一致,就可以确认客户端已经成功收到了它的 S1 包。
握手完成!
当服务器收到并验证 C2 后,整个握手过程就结束了。双方都确认了对方是"活的"并且网络是通的。接下来,就可以开始传输真正的 RTMP 消息了(比如 connect 命令)。
4.2.2 Wireshark 视角
假设你的 IP 是 192.168.1.10 (Client),服务器 IP 是 8.8.8.8 (Server)。
你在 Wireshark 中会看到这样的TCP流:
- [SYN, SYN/ACK, ACK] - TCP 三次握手,建立 TCP 连接。
- [PSH, ACK] 192.168.1.10 -> 8.8.8.8 Len: 1537
- 这就是 C0+C1!
- 你点开这个包的二进制数据,会看到:
- 第 1 个字节: 03
- 第 2-5 字节: 比如 00 1a 2b 3c (这是你发送时的时间戳)
- 第 6-9 字节: 00 00 00 00
- 后面跟着 1528 字节的随机数据。
- [PSH, ACK] 8.8.8.8 -> 192.168.1.10 Len: 3073
- 这就是 S0+S1+S2!
- 你点开它的二进制数据,会看到:
- 第 1 个字节: 03 (S0)
- 第 2-1537 字节 (S1): 服务器自己的时间戳和随机数据。
- 第 1538-3073 字节 (S2): 这部分的数据会和你上面 C1 包里的 1536 字节数据一模一样! Wireshark 甚至可能会帮你高亮出来。
- [PSH, ACK] 192.168.1.10 -> 8.8.8.8 Len: 1536
- 这就是 C2!
- 它的 1536 字节数据,会和上面 S1 包里的 1536 字节数据一模一样。
至此,握手分析完毕。 紧接着的下一个包,你就会看到 RTMP 的 connect 命令了。这个过程虽然看起来复杂,但在 Wireshark 里却是清晰可见、按部就班的。
4.3分块
4.3.1块格式
每个块包含一个头和数据体。块头包含三个部分:
Basic Header + Message Header + Extended Timestamp + Chunk Data
**Basic Header (基本头,1 到 3 个字节):**这个字段对块流 ID 和块类型进行编码。块类型决定了消息头的编码格式。(这一字段的) 长度完全取决于块流 ID,因为块流 ID 是一个可变长度的字段。
- fmt (Format, 2位): 这是块头压缩的关键! 它决定了后面的消息头包含哪些信息。fmt=0 表示完整的、未压缩的头;fmt=1, 2, 3 表示不同程度的压缩(比如复用上一个块的时间戳或长度)。
- cs_id (Chunk Stream ID, 6-22位): 块流 ID。它就像车道编号,用来区分这个块属于哪个"流"(比如,cs_id=3 可能专门用于传输视频,cs_id=4 专门用于传输音频)。
**Message Header (消息头,0,3,7,或者 11 个字节):**这一字段对正在发送的消息 (不管是整个消息,还是只是一小部分) 的信息进行编码。这一字段的长度可以使用块头中定义的块类型进行决定。
- Timestamp (时间戳): 这个块所属的消息的时间戳。
- Message Length (消息长度): 这个块所属的整个消息的总长度。
- Message Type ID (消息类型 ID): 区分消息是视频(9)、音频(8)、命令(20)还是其他类型。
- Message Stream ID (消息流 ID): 通常是固定的,表示逻辑上的流。
**Extended Timestamp (扩展 timestamp,0 或 4 字节):**这一字段是否出现取决于块消息头中的 timestamp 或者 timestamp delta 字段。更多信息参考 5.3.1.3 节。
**Chunk Data (有效大小):**当前块的有效负载,相当于定义的最大块大小。
4.3.2块基本头
块基本头对块流 ID 和块类型 (由下图中的 fmt 字段表示) 进行编码。块基本头字段可能会有 1,2 或者 3 个字节,取决于块流 ID。
RTMP 协议最多支持 65597 个流,流 ID 范围 3 - 65599。ID 0、1、2 被保留。0 值表示二字节形式,并且 ID 范围 64 - 319 (第二个字节 + 64)。1 值表示三字节形式,并且 ID 范围为 64 - 65599 ((第三个字节) * 256 + 第二个字节 + 64)。3 - 63 范围内的值表示整个流 ID。带有 2 值的块流 ID 被保留,用于下层协议控制消息和命令。
块基本头中的 0 - 5 位 (最低有效) 代表块流 ID。
块流 ID 2 - 63 可以编进这一字段的一字节版本中。

块流 ID 64 - 319 可以以二字节的形式编码在头中。ID 计算为 (第二个字节 + 64):

块流 ID 64 - 65599 可以编码在这个字段的三字节版本中。ID 计算为 ((第三个字节) * 256 + (第二个字节) + 64)。

cs id (六位):这一字段包含有块流 ID,值的范围是 2 - 63。值 0 和 1 用于指示这一字段是 2- 或者 3- 字节版本。
fmt (两个字节):这一字段指示 'chunk message header' 使用的四种格式之一。没中块类型的 'chunk message header' 会在下一小节解释。
cs id - 64 (8 或者 16 位):这一字段包含了块流 ID 减掉 64 后的值。例如,ID 365 在 cs id 中会以一个 1 进行表示,和这里的一个 16 位 的 301 (cs id - 64)。
块流 ID 64 - 319 可以使用 2-byte 或者 3-byte 的形式在头中表示。
4.3.3块消息头
块消息头又四种不同的格式,由块基本头中的 "fmt" 字段进行选择。
- fmt=0: 11字节的消息头。包含所有信息。用于一个新的消息流的第一个块,或者当时间戳有较大跳变时。
- timestamp (3 字节): 消息的时间戳。注意,这里只用了 3 个字节。
- message length (3 字节): 整个消息的长度(字节)。
- message type id (1 字节): 消息的类型(8=音频, 9=视频, 20=AMF0命令等)。
- message stream id (4 字节, 小端序): 消息流的 ID。

- fmt=1: 7字节的消息头。省略了 Message Stream ID,复用上一个块的。用于流长度和类型不变,但时间戳有增量时。
- timestamp delta (3 字节): 时间戳增量。表示当前消息的时间戳与上一个同 cs_id 的块的时间戳之差。
- message length (3 字节): 整个消息的长度。
- message type id (1 字节): 消息的类型。

- fmt=2: 3字节的消息头。进一步省略了 Message Length 和 Type ID。用于流长度和类型都不变,时间戳有稳定增量时(比如连续的视频帧)。
- timestamp delta (3 字节): 时间戳增量。

- fmt=3: 0字节的消息头。完全没有消息头! 它表示这个块的所有信息(除了时间戳)都和前一个块完全一样。用于一个大的消息被分割成的多个后续块。
4.3.4扩展时间戳
- 触发条件: 当块消息头中的 3 字节时间戳或时间戳增量字段的值等于其最大值 0xFFFFFF (即 16,777,215) 时,即为4.6小时,就表示"我这里放不下了,请去后面的扩展字段里读取完整的值"。
- 扩展时间戳字段 (Extended Timestamp):
- 大小: 4 个字节。
- 位置: 它不属于常规的块消息头。它是一个可选的、独立的字段,紧跟在块消息头之后、块数据 (Chunk Data) 之前。
- 内容: 它包含一个完整的 32 位时间戳或时间戳增量。这个 32 位的值才是真实的时间戳。
工作流程举例:
假设一个直播已经进行了 5 个小时,当前一个视频帧的真实时间戳是 18,000,000 毫秒。
- 发送端准备发送这个视频帧的第一个块 (假设是 Type 0 头)。
- 它发现真实时间戳 18,000,000 超过了 3 字节能表示的最大值 16,777,215。
- 于是,它执行以下操作:
- 在块消息头的 3 字节 timestamp 字段里,填入 0xFFFFFF 。这是一个标志位,告诉接收端"请注意,后面有扩展时间戳"。
- 在块消息头之后,紧接着插入一个 4 字节的扩展时间戳字段,里面填入真实的 32 位时间戳值 18,000,000 (即 0x0112A880)。
- 再后面是块数据 (Chunk Data)。
- 接收端在解析时:
- 读到块消息头,发现里面的时间戳是 0xFFFFFF。
- 它就知道不能用这个值,而是需要从后面紧接着的 4 个字节中读取一个完整的 32 位时间戳作为真实的时间戳。
4.3.5块数据
- 这就是实际的"货物"------部分视频帧数据、完整的音频数据或信令数据。
- 每个 Chunk 的数据大小是可配置的,默认通常是 128 字节。服务器和客户端可以通过一个特殊的控制消息 Set Chunk Size 来协商这个值。
4.3.6举例说明 (Wireshark 视角)
假设服务器要发送一个 300 字节的视频消息 (Message) ,而协商好的块大小 (Chunk Size) 是 128 字节。
你在 Wireshark 中会看到这样的数据流:
Packet 1: 第一个块 (Chunk 1)
bash
RTMP Chunk (1 of 3)
Chunk Basic Header: fmt=0, cs_id=3 // 完整的头,新的视频消息开始
Chunk Message Header:
Timestamp: 1000 ms
Message Length: 300 bytes
Message Type ID: 9 (Video)
Message Stream ID: 1
Chunk Data: [128 bytes of video data] // 视频消息的前 128 字节
Packet 2: 音频块 (Audio Chunk)
在视频块的传输间隙,服务器插入了一个完整的音频消息
bash
RTMP Chunk (1 of 1)
Chunk Basic Header: fmt=0, cs_id=4 // 另一个流,也是完整的头
Chunk Message Header:
Timestamp: 1020 ms
Message Length: 50 bytes
Message Type ID: 8 (Audio)
Message Stream ID: 1
Chunk Data: [50 bytes of audio data] // 完整的 50 字节音频数据
Packet 3: 第二个视频块 (Chunk 2)
继续传输之前那个 300 字节的视频消息
bash
RTMP Chunk (2 of 3)
Chunk Basic Header: fmt=3, cs_id=3 // 极简的头!复用 Chunk 1 的所有信息
// 没有消息头 (Message Header)!
Chunk Data: [128 bytes of video data] // 视频消息的第 129-256 字节
Packet 4: 第三个视频块 (Chunk 3)
传输视频消息的最后一部分
bash
RTMP Chunk (3 of 3)
Chunk Basic Header: fmt=3, cs_id=3 // 仍然是极简的头
Chunk Data: [44 bytes of video data] // 视频消息剩下的 44 字节
分析这个例子:
- 交错传输: 一个大的视频消息被拆分,中间完美地插入了一个完整的音频消息,实现了并发传输。
- 块头压缩 : 第一个视频块使用了完整的 fmt=0 头。而后续的视频块使用了 fmt=3,几乎没有额外的头部开销,非常高效。
- 重组: 接收方根据 cs_id=3,将这三个视频块的 Chunk Data (128 + 128 + 44 字节) 拼接起来,就还原出了完整的 300 字节视频消息。根据 cs_id=4,它知道那个 50 字节的块是一个独立的音频消息。
五.协议控制消息
这类消息不像音视频数据那样持续不断地发送,但它们是维持和管理整个 RTMP 会话的"交通警察"和"管理员"。它们负责设置参数、通知事件、确保数据流的顺畅。
协议控制消息的 Message Type ID 通常在 1 到 6 之间。它们都使用 Chunk Stream ID (cs_id) 2,并且 Message Stream ID (ms_id) 通常为 0。这是一个约定,使得控制信道与音视频、命令信道分离开。
以下消息的内容均为在chunk data中
5.1Set Chunk Size (设置块大小) - Type ID: 1
- 方向: 客户端 -> 服务器,或 服务器 -> 客户端。
- 作用 : 动态地设置 RTMP 块 (Chunk) 的最大大小。
- 消息体 (Payload): 4 个字节,表示新的块大小(一个 31 位的整数,最高位必须是0)。
- 详细解释 :
- RTMP 连接建立之初,默认的块大小是 128 字节。
- 通信的任何一方都可以随时发送这个消息来改变后续发送的块大小。比如,客户端可以发送一个 Set Chunk Size 消息,将块大小设置为 4096 字节。
- 服务器收到后,就知道客户端后续发来的、被分割的大消息,每个块的数据部分最大是 4096 字节。同样,服务器也可以要求客户端接收更大的块。
- 为什么需要改变?
- 增大块大小: 在高带宽、低延迟的网络中,使用更大的块可以减少块头的数量,降低协议开销,提升吞吐量。
- 减小块大小: 在低延迟要求的应用中,使用较小的块可以更好地实现消息的交织(interleaving),防止大的视频帧阻塞小的控制消息。
- 举例: 客户端想把块大小改成 65536 字节。它会发送一个 Type ID 为 1 的消息,消息体是 4 字节的 0x00010000。
5.2. Abort Message (中止消息) - Type ID: 2
- 方向: 客户端 -> 服务器,或 服务器 -> 客户端。
- 作用 : 通知对端丢弃一个部分接收但尚未处理完的消息。
- 消息体 : 4 个字节,表示要中止的那个消息所在的 Chunk Stream ID (cs_id)。
- 详细解释 :
- 这个消息本身并不常用。它主要用于一种特殊情况:发送方发送了一个非常大的消息(比如一个超长的脚本数据),但在发送到一半时决定放弃。它可以发送一个 Abort Message 来告诉接收方:"嘿,刚才在 cs_id=5 上发给你的那个消息,别等了,直接扔掉吧。"
5.3. Acknowledgement (确认) - Type ID: 3
- 方向: 客户端 -> 服务器,或 服务器 -> 客户端。
- 作用 : 告知对端自己已经成功接收了多少字节的数据。
- 消息体: 4 个字节,表示从连接开始到现在,总共接收到的字节数。
- 详细解释 :
- 当接收方收到这个消息后,它可以放心地从自己的发送缓冲区中清除已被对方确认的数据。
- 这与 TCP 自身的确认机制有些重叠,但它在 RTMP 层面提供了一个应用级的流量控制参考。
- 服务器或客户端可以根据收到的确认信息,来判断网络是否拥堵,并结合下面的"窗口大小"来调整发送速率。
5.4. Window Acknowledgement Size (窗口确认大小) - Type ID: 5
- 方向: 客户端 -> 服务器,或 服务器 -> 客户端。
- 作用 : 设置一个"确认窗口"的大小。 接收方被要求在每接收到这个大小的字节数后,就必须向发送方发送一个 Acknowledgement (Type ID: 3) 消息。
- 消息体: 4 个字节,表示窗口的大小(字节)。
- 详细解释 :
- 这是实现 RTMP 流量控制的关键机制。
- 流程 :
- 连接开始时,客户端会发送一个 Window Acknowledgement Size 消息给服务器,比如 2500000 (2.5MB)。
- 服务器收到后,就知道自己需要设置一个 2.5MB 的确认窗口。
- 服务器开始向客户端发送数据。
- 当服务器发送的字节数累计达到 2.5MB 时,它会暂停发送,等待客户端的 Acknowledgement。
- 客户端在接收数据,当它接收的字节数累计达到 2.5MB 时,它会立即向服务器发送一个 Acknowledgement 消息,告知"我已经收到了 2.5MB"。
- 服务器收到这个确认后,更新自己的发送窗口,继续发送接下来的 2.5MB 数据。
- 通过这个"发-等-确认-再发"的循环,发送方可以确保自己的发送速度不会远超接收方的处理速度,从而避免了缓冲区溢出和网络拥塞。
5.5. Set Peer Bandwidth (设置对端带宽) - Type ID: 6
- 方向: 客户端 -> 服务器,或 服务器 -> 客户端。
- 作用 : 告知对端自己的输出带宽限制。
- 消息体 : 5 个字节。
- Window Size (4 字节): 与 Type ID 5 中的窗口大小含义相同。
- Limit Type (1 字节) :
- 0 (Hard): 对端必须将自己的输出带宽限制在这个值以内。
- 1 (Soft): 对端应该动态调整带宽,可以将这个值作为参考,但不是强制限制。
- 2 (Dynamic): 对端收到这个消息后,如果它之前收到的 Limit Type 是 Hard,就应该切换成 Soft。
- 详细解释 :
- 这个消息用来通知对方自己的处理能力或网络状况。比如,一个客户端网络状况不佳,它可以发送一个 Set Peer Bandwidth 消息,Limit Type 为 Hard,告诉服务器:"请你给我发数据的时候,不要超过 500kbps,否则我就卡死了。"
- 它与 Window Acknowledgement Size 共同构成了 RTMP 的流量控制体系。Window Size 控制的是"量"(发多少字节后停下来等确认),Bandwidth Limit 控制的是"速"(每秒最多发多少字节)。
5.6注:Message Type ID 4
Message Type ID 4 确实存在,它被定义为 User Control Message (用户控制消息)。
这类消息用于在 RTMP 协议层面传递一些重要的事件通知 ,比如"流已开始"、"流已结束"、"缓冲区状态"等。它与我们之前讨论的协议控制消息(Type ID 1, 2, 3, 5, 6)不同,后者主要用于管理连接本身的参数(如块大小、带宽),而用户控制消息则更多地关注流 (Stream) 的状态。
六.RTMP消息
6.1消息的类型
6.1.1. 命令消息 (20, 17)
命令消息在客户端和服务器端传递 AMF 编码的命令。这些消息被分配以消息类型值为 20 以进行 AMF0 编码,消息类型值为 17 以进行 AMF3 编码。这些消息发送以进行一些操作,比如,连接,创建流,发布,播放,对端暂停。命令消息,像 onstatus、result 等等,用于通知发送者请求的命令的状态。一个命令消息由命令名、事务 ID 和包含相关参数的命令对象组成。一个客户端或者一个服务器端可以通过和对端通信的流使用这些命令消息请求远程调用 (RPC)。
6.1.2. 数据消息 (18, 15)
客户端或者服务器端通过发送这些消息以发送元数据或者任何用户数据到对端。元数据包括数据 (音频,视频等等) 的详细信息,比如创建时间,时长,主题等等。这些消息被分配以消息类型为 18 以进行 AMF0 编码和消息类型 15 以进行 AMF3 编码。
6.1.3. 共享对象消息 (19, 16)
所谓共享对象其实是一个 Flash 对象 (一个名值对的集合),这个对象在多个不同客户端、应用实例中保持同步。消息类型 19 用于 AMF0 编码、16 用于 AMF3 编码都被为共享对象事件保留。每个消息可以包含有不同事件。
支持以下事件类型:
事件 | 描述 |
---|---|
Use(=1) | 客户端发送这一事件以通知服务器端一个已命名的共享对象已创建。 |
Release(=2) | 当共享对象在客户端被删除时客户端发送这一事件到服务器端。 |
Request Change (=3) | 客户端发送给服务器端这一事件以请求共享对象的已命名的参数所关联到的值的改变。 |
Change (=4) | 服务器端发送这一事件已通知发起这一请求之外的所有客户端,一个已命名参数的值的改变。 |
Success (=5) | 如果请求被接受,服务器端发送这一事件给请求的客户端,以作为 RequestChange 事件的响应。 |
SendMessage (=6) | 客户端发送这一事件到服务器端以广播一条消息。一旦接收到这一事件,服务器端将会给所有的客户端广播这一消息,包括这一消息的发起者。 |
Status (=7) | 服务器端发送这一事件以通知客户端异常情况。 |
Clear (=8) | 服务器端发送这一消息到客户端以清理一个共享对象。服务器端也会对客户端发送的 Use 事件使用这一事件进行响应。 |
Remove (=9) | 服务器端发送这一事件有客户端删除一个 slot。 |
Request Remove (=10) | 客户端发送这一事件有客户端删除一个 slot。 |
Use Success (=11) | 服务器端发送给客户端这一事件表示连接成功。 |
6.1.4. 音频消息 (8)
客户端或者服务器端发送这一消息以发送音频数据到对端。消息类型 8 为音频消息保留。
6.1.5. 视频消息 (9)
客户端或者服务器发送这一消息以发送视频数据到对端。消息类型 9 为视频消息保留。
6.1.6. 统计消息 (22)
统计消息是一个单一的包含一系列的使用 6.1 节描述的 RTMP 子消息的消息。消息类型 22 用于统计消息。

统计消息的消息流 ID 覆盖了统计中子消息的消息流 ID。
统计消息里的 timestamp 和第一个子消息的 timestamp 的不同点在于子消息的 timestamp 被相对流时间标调整了偏移。每个子消息的 timestamp 被加入偏移以达到一个统一流时间。第一个子消息的 timestamp 应该和统计消息的 timestamp 一样,所以这个偏移量应该为 0。
反向指针包含有前一个消息的大小 (包含前一个消息的头)。这样子匹配了 FLV 文件的格式,用于反向查找。
使用统计消息具有以下性能优势:
- 块流可以在一个块中以至多一个单一完整的消息发送。因此,增加块大小并使用统计消息减少了发送块的数量。
- 子消息可以在内存中连续存储。在网络中系统调用发送这些数据时更高效。
6.1.7. 用户控制消息事件
客户端或者服务器端发送这一消息来通知对端用户控制事件。关于这个的消息格式参考 6.2 节。
支持以下用户控制事件类型:
事件 | 描述 |
---|---|
Stream Begin (=0) | 服务器发送这个事件来通知客户端一个流已就绪并可以用来通信。默认情况下,这一事件在成功接收到客户端的应用连接命令之后以 ID 0 发送。这一事件数据为 4 字节,代表了已就绪流的流 ID。 |
Stream EOF (=1) | 服务器端发送这一事件来通知客户端请求的流的回放数据已经结束。在发送额外的命令之前不再发送任何数据。客户端将丢弃接收到的这个流的消息。这一事件数据为 4 字节,代表了回放已结束的流的流 ID。 |
StreamDry (=2) | 服务器端发送这一事件来通知客户端当前流中已没有数据。当服务器端在一段时间内没有检测到任何消息,它可以通知相关客户端当前流已经没数据了。这一事件数据为 4 字节,代表了已没数据的流的流 ID。 |
SetBuffer Length (=3) | 客户端发送这一事件来通知服务器端用于缓存流中任何数据的缓存大小 (以毫秒为单位)。这一事件在服务器端开始处理流之前就发送。这一事件数据的前 4 个字节代表了流 ID 后 4 个字节代表了以毫秒为单位的缓存的长度。 |
StreamIs Recorded (=4) | 服务器端发送这一事件来通知客户端当前流是一个录制流。这一事件数据为 4 字节,代表了录制流的流 ID。 |
PingRequest (=6) | 服务器端发送这一事件用于测试是否能够送达客户端。时间数据是为一个 4 字节的 timestamp,代表了服务器端发送这一命令时的服务器本地时间。客户端在接收到这一消息后会立即发送 PingResponse 回复。 |
PingResponse (=7) | 客户端作为对 ping 请求的回复发送这一事件到服务器端。这一事件数据是为一个 4 字节的 timestamp,就是接收自 PingRequest 那个。 |
6.2. 命令类型
客户端和服务器端交换 AMF 编码的命令。服务器端发送一个命令消息,这个命令消息由命令名、事务 ID 以及包含有相关参数的命令对象组成。例如,包含有 'app' 参数的连接命令,这个命令说明了客户端连接到的服务器端的应用名。接收者处理这一命令并回发一个同样事务 ID 的响应。回复字符串可以是 _result、_error 或者 一个方法名的任意一个,比如,verifyClient 或者 contactExternalServer。
命令字符串 _result 或者 _error 是响应信号。事务 ID 指示出响应所指向的命令。这和 AMAP 和其他一些协议的标签一样。命令字符串中的方法名表示发送者试图执行接收者一端的一个方法。
以下类的对象用于发送不同的命令:
NetConnection 代表上层的服务器端和客户端之间连接的一个对象。
NetStream 一个代表发送音频流、视频流和其他相关数据的通道的对象。当然,我们也会发送控制数据流的命令,诸如 play、pause 等等。
6.2.1. NetConnection 命令
NetConnection 管理着一个客户端应用和服务器端之间的双相连接。此外,它还提供远程方法的异步调用。
NetConnection 可以发送以下命令:
- connect
- call
- close
- createStream
6.2.1.1. connect 命令
客户端发送 connect 命令到服务器端来请求连接到一个服务器应用的实例。
由客户端发送到服务器端的 connect 命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令的名字。设置给 "connect"。 |
Transaction ID | 数字 | 总是设置为 1。 |
Command Object | 对象 | 具有名值对的命令信息对象。 |
Optional User Arguments | 对象 | 任意可选信息。 |
以下是为 connect 命令中使用的名值对对象的描述。
属性 | 类型 | 描述 | 范例 |
---|---|---|---|
app | 字符串 | 客户端连接到的服务器端应用的名字。 | testapp |
flashver | 字符串 | Flash Player 版本号。和ApplicationScript getversion() 方法返回的是同一个字符串。 | FMSc/1.0 |
swfUrl | 字符串 | 进行当前连接的 SWF 文件源地址。 | file://C:/FlvPlayer.swf |
tcUrl | 字符串 | 服务器 URL。具有以下格式:protocol://servername:port/appName/appInstance | rtmp://localhost:1935/testapp/instance1 |
fpad | 布尔 | 如果使用了代理就是 true。 | true 或者 false。 |
audioCodecs | 数字 | 表明客户端所支持的音频编码。 | SUPPORT_SND_MP3 |
videoCodecs | 数字 | 表明支持的视频编码。 | SUPPORT_VID_SORENSON |
videoFunction | 数字 | 表明所支持的特殊视频方法。 | SUPPORT_VID_CLIENT_SEEK |
pageUrl | 字符串 | SWF 文件所加载的网页 URL。 | http://somehost/sample.html |
objectEncoding | 数字 | AMF 编码方法。 | AMF3 |
命令执行时消息流动如下:

-
客户端发送 connect 命令到服务器端以请求对服务器端应用实例的连接。
-
收到 connect 命令后,服务器端发送协议消息 '窗口确认大小' 到客户端。服务器端也会连接到 connect 命令中提到的应用。
-
服务器端发送协议消息 '设置对端带宽' 到客户端。
-
在处理完协议消息 '设置对端带宽' 之后客户端发送协议消息 '窗口确认大小' 到服务器端。
-
服务器端发送另一个用户控制消息 (StreamBegin) 类型的协议消息到客户端。
-
服务器端发送结果命令消息告知客户端连接状态 (success/fail)。这一命令定义了事务 ID (常常为 connect 命令设置为 1)。这一消息也定义了一些属性,比如 FMS 服务器版本 (字符串)。此外,它还定义了其他连接关联到的信息,比如 level (字符串)、code (字符串)、description (字符串)、objectencoding (数字) 等等。
7.2.1.2. call 方法
NetConnection 对象的 call 方法执行接收端远程方法的调用 (PRC)。被调用的 PRC 名字作为一个参数传给调用命令。
发送端发送给接收端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Procedure Name | 字符串 | 调用的远程方法的名字。 |
Transaction ID | 数字 | 如果期望回复我们要给一个事务 ID。否则我们传 0 值即可。 |
Command Object | 对象 | 如果存在一些命令信息要设置这个对象,否则置空。 |
Optional Arguments | 对象 | 任意要提供的可选参数。 |
回复的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令的名字。 |
Transaction ID | 数字 | 响应所属的命令的 ID。 |
Command Object | 对象 | 如果存在一些命令信息要设置这个对象,否则置空。 |
Response | 对象 | 调用方法的回复。 |
6.2.1.3. createStream 命令
客户端发送这一命令到服务器端以为消息连接创建一个逻辑通道。音频、视频和元数据使用 createStream 命令创建的流通道传输。
NetConnection 是默认的通信通道,流 ID 为 0。协议和一些命令消息,包括 createStream,使用默认的通信通道。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名。设置给 "createStream"。 |
Transaction ID | 数字 | 命令的事务 ID。 |
Command Object | 对象 | 如果存在一些命令信息要设置这个对象,否则置空。 |
服务器端发送给客户端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | _result 或者 _error;表明回复是一个结果还是错误。 |
Transaction ID | 数字 | 响应所属的命令的 ID。 |
Command Object | 对象 | 如果存在一些命令信息要设置这个对象,否则置空。 |
Stream ID | 数字 | 返回值要么是一个流 ID 要么是一个错误信息对象。 |
6.2.2. NetStream 命令
NetStream 定义了传输通道,通过这个通道,音频流、视频流以及数据消息流可以通过连接客户端到服务端的 NetConnection 传输。
以下命令可以由客户端使用 NetStream 往服务器端发送:
- play
- play2
- deleteStream
- closeStream
- receiveAudio
- receiveVideo
- publish
- seek
- pause
服务器端使用 "onStatus" 命令向客户端发送 NetStream 状态:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名 "onStatus"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | onStatus 消息没有命令对象。 |
Info Object | 对象 | 一个 AMF 对象至少要有以下三个属性。"level" (字符串):这一消息的等级,"warning"、"status"、"error" 中的某个值;"code" (字符串):消息码,例如 "NetStream.Play.Start";"description" (字符串):关于这个消息人类可读描述。 |
6.2.2.1. play 命令
客户端发送这一命令到服务器端以播放流。也可以多次使用这一命令以创建一个播放列表。
如果你想要创建一个动态的播放列表这一可以在不同的直播流或者录制流之间进行切换播放的话,多次调用 play 方法,并在每次调用时传递重置为 false。相反的,如果你想要立即播放指定流,将其他等待播放的流清空,并为重置设为 true。
客户端发送到服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名。设为 "play"。 |
Transaction ID | 数字 | 事务 ID 设为 0。 |
Command Object | Null | 命令信息不存在。设为 null 类型。 |
Stream Name | 字符串 | 要播放流的名字。要播放视频 (FLV) 文件,使用没有文件扩展名的名字对流名进行定义 (例如,"sample")。要重播 MP3 或者 ID3,你必须在流名前加上 mp3:例如,"mp3:sample"。要播放 H.264/AAC 文件,你必须在流名前加上 mp4:并指定文件扩展名。例如,要播放 sample.m4v 文件,定义 "mp4:sample.m4v"。 |
Start | 数字 | 一个可选的参数,以秒为单位定义开始时间。默认值为 -2,表示用户首先尝试播放流名字段中定义的直播流。如果那个名字的直播流没有找到,它将播放同名的录制流。如果没有那个名字的录制流,客户端将等待一个新的那个名字的直播流,并当其有效时进行播放。如果你在 Start 字段中传递 -1,那么就只播放流名中定义的那个名字的直播流。如果你在 Start 字段中传递 0 或一个整数,那么将从 Start 字段定义的时间开始播放流名中定义的那个录制流。如果没有找到录制流,那么将播放播放列表中的下一项。 |
Duration | 数字 | 一个可选的参数,以秒为单位定义了回放的持续时间。默认值为 -1。-1 值意味着一个直播流会一直播放直到它不再可用或者一个录制流一直播放直到结束。如果你传递 0 值,它将只播放单一一帧,因为播放时间已经在录制流的开始的 Start 字段指定了。假定定义在 Start 字段中的值大于或者等于 0。如果你传递一个正数,将播放 Duration 字段定义的一段直播流。之后,变为可播放状态,或者播放 Duration 字段定义的一段录制流。(如果流在 Duration 字段定义的时间段内结束,那么流结束时回放结束)。如果你在 Duration 字段中传递一个 -1 以外的负数的话,它将把你给的值当做 -1 处理。 |
Reset | 布尔 | 一个可选的布尔值或者数字定义了是否对以前的播放列表进行 flush。 |
命令执行时的消息流动是为:
-
当客户端从服务器端接收到 createStream 命令的结果是为 success 时,发送 play 命令。
-
一旦接收到 play 命令,服务器端发送一个协议消息来设置块大小。
-
服务器端发送另一个协议消息 (用户控制),这个消息中定义了 'StreamIsRecorded' 事件和流 ID。消息在前两个字节中保存事件类型,在后四个字节中保存流 ID。
-
服务器端发送另一个协议消息 (用户控制),这一消息包含 'StreamBegin' 事件,来指示发送给客户端的流的起点。
-
如果客户端发送的 play 命令成功,服务器端发送一个 onStatus 命令消息 NetStream.Play.Start & NetStream.Play.Reset。只有当客户端发送的 play 命令设置了 reset 时服务器端才会发送 NetStream.Play.Reset。如果要播放的流没有找到,服务器端发送 onStatus 消息 NetStream.Play.StreamNotFound。
之后,服务器端发送视频和音频数据,客户端对其进行播放。
6.2.2.2. play2
不同于 play 命令的是,play2 可以在不改变播放内容时间轴的情况下切换到不同的比特率。服务器端为客户端可以在 play2 中请求所有支持的码率维护了不同的字段。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设置为 "play2"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | 命令信息不存在,设置为 null 类型。 |
Parameters | 对象 | 一个 AMF 编码的对象,该对象的属性是为公开的 flash.net.NetStreamPlayOptions ActionScript 对象所描述的属性。 |
NetStreamPlayOptions 对象的公开属性在 ActionScript 3 语言指南中 [AS3] 有所描述。
命令执行时的消息流动如下图所示:

6.2.2.3. deleteStream 命令
当 NetStream 对象消亡时 NetStream 发送 deleteStream 命令。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设置为 "deleteStream"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | 命令信息对象不存在,设为 null 类型。 |
Stream ID | 数字 | 服务器端消亡的流 ID。 |
服务器端不再发送任何回复。
6.2.2.4. receiveAudio 命令
NetStream 通过发送 receiveAudio 消息来通知服务器端是否发送音频到客户端。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设置为 "receiveAudio"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型。 |
Bool Flag | 布尔 | true 或者 false 以表明是否接受音频。 |
如果发送来的 receiveVideo 命令布尔字段被设为 false 时服务器端不发送任何回复。如果这一标识被设为 true,服务器端以状态消息 NetStream.Seek.Notify 和 NetStream.Play.Start 进行回复。
6.2.2.5. receiveVideo 命令
NetStream 通过发送 receiveVideo 消息来通知服务器端是否发送视频到客户端。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设置为 "receiveVideo"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型。 |
Bool Flag | 布尔 | true 或者 false 以表明是否接受视频。 |
6.2.2.6. publish 命令
客户端发送给服务器端这一命令以发布一个已命名的流。使用这个名字,任意客户端都可以播放这个流,并接受发布的音频、视频以及数据消息。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设置为 "publish"。 |
Transaction ID | 数字 | 事务 ID 设置为 0。 |
Command Object | Null | 命令信息对象不存在,设置为 null 类型。 |
Publishing Name | 字符串 | 发布的流的名字。 |
Publishing Type | 字符串 | 发布类型。可以设置为 "live"、"record" 或者 "append"。record:流被发布,数据被录制到一个新的文件。新文件被存储在服务器上包含服务应用目录的子路径。如果文件已存在,将重写。append:流被发布,数据被添加到一个文件。如果该文件没找着,将新建一个。live:直播数据只被发布,并不对其进行录制。 |
服务器端回复 onStatus 命令以标注发布的起始位置。
6.2.2.7. seek 命令
客户端发送 seek 命令以查找一个多媒体文件或一个播放列表的偏移量 (以毫秒为单位)。
客户端发送到服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令的名字,设为 "seek"。 |
Transaction ID | 数字 | 事务 ID 设为 0。 |
Command Object | Null | 没有命令信息对象,设置为 null 类型。 |
milliSeconds | 数字 | 播放列表查找的毫秒数。 |
seek 命令执行成功时服务器会发送一个状态消息 NetStream.Seek.Notify。失败的话,服务器端返回一个 _error 消息。
6.2.2.8. pause 命令
客户端发送 pause 命令以告知服务器端是暂停还是开始播放。
客户端发送给服务器端的命令结构如下:
字段名 | 类型 | 描述 |
---|---|---|
Command Name | 字符串 | 命令名,设为 "pause"。 |
Transaction ID | 数字 | 没有这一命令的事务 ID,设为 0。 |
Command Object | Null | 命令信息对象不存在,设为 null 类型。 |
Pause/Unpause Flag | 布尔 | true 或者 false,来指示暂停或者重新播放。 |
milliSeconds | 数字 | 流暂停或者重新开始所在的毫秒数。这个是客户端暂停的当前流时间。当回放已恢复时,服务器端值发送带有比这个值大的 timestamp 消息。 |
当流暂停时,服务器端发送一个状态消息 NetStream.Pause.Notify。NetStream.Unpause.Notify 只有针对没有暂停的流进行发放。失败的话,服务器端返回一个 _error 消息。
6.3. 消息交换例子
这里有几个解释使用 RTMP 交换消息的例子。
6.3.1. 发布录制视频
这个例子说明了一个客户端是如何能够发布一个直播流然后传递视频流到服务器的。然后其他客户端可以对发布的流进行订阅并播放视频。
6.3.2. 广播一个共享对象消息
这个例子说明了在一个共享对象的创建和改变期间交换消息的变化。它也说明了共享对象消息广播的处理过程。

6.3.3. 发布来自录制流的元数据
这个例子描述了用于发布元数据的消息交换。

6.4重点记忆
6.4.1精通
1. RTMP 协议分层模型
- 记忆程度 : 能在白板上画出消息(Message) -> 块(Chunk) -> TCP 的分层关系图。
- 核心要点 : 必须能清晰地讲出为什么需要分块 (Chunking) ------为了解决队头阻塞,实现多路复用和低延迟。
2. 块 (Chunk) 的结构
- 记忆程度 : 能说出 Chunk 由 块头 (Chunk Header) 和 块数据 (Chunk Data) 组成。
- 核心要点 :
- 块头 : 必须记住它由 基础头 (Basic Header) 和 消息头 (Message Header) 组成。
- 基础头: 记住包含 fmt (4种格式) 和 cs_id (块流ID)。
- 块头压缩 : 必须深刻理解 fmt 字段 (0, 1, 2, 3) 的作用,能说出这四种格式分别省略了哪些字段,以及它们各自的应用场景(fmt=0 用于新流开始,fmt=3 用于大消息的后续块)。这是 RTMP 高效的核心。
- 扩展时间戳: 知道它的触发条件(时间戳字段为 0xFFFFFF)和作用(处理超长时间戳)。
3. 核心命令消息 (Command Message, Type ID: 20)
- 记忆程度 : 记住以下三个核心命令 的名称、作用以及关键参数 。
- connect :
- 作用: 客户端请求连接到服务器上的一个应用。
- 关键参数: app (应用名,如 "live"), tcUrl (推流地址)。
- publish :
- 作用: 请求在某个流上发布数据,准备推流。
- 关键参数: publishingName (推流码), publishingType ('live', 'record', 'append')。
- play :
- 作用: 请求播放某个流。
- 关键参数: streamName (要播放的流名)。
- connect :
- 核心要点 : 知道这些命令的消息体是用 AMF0 格式编码的,包含 CommandName, TransactionID, CommandObject 等部分。
4. 核心媒体消息 (Audio/Video, Type ID: 8/9)
- 记忆程度: 能说出音视频消息体的基本结构。
- 核心要点 :
- 知道消息体的第一个字节是控制字节,包含了编码和帧类型信息。
- 视频 (ID: 9) : 记住控制字节里有 FrameType (关键帧/非关键帧) 和 CodecID (AVC/H.264)。
- 音频 (ID: 8) : 记住控制字节里有 SoundFormat (AAC) 和其他音频参数。
- 知道在 H.264/AAC 传输时,消息体里还会有 AVC/AAC Packet Type 字段,用来区分是配置信息 (Sequence Header) 还是媒体数据 (NALU/Raw)。
6.4.2熟悉
这部分内容是构成完整 RTMP 会话的重要环节,你不需要记住每个字节,但要能清晰地描述它们在流程中的作用。
1. 完整的推/拉流时序
- 记忆程度 : 能在白板上默画出 从 TCP 握手到持续推流的完整时序图(就像我们之前整理的那样)。
- 核心要点 : 顺序不能错!TCP -> RTMP Handshake -> connect -> _result -> createStream(可选) -> publish -> onStatus('Publish.Start') -> onMetaData -> 发送音视频数据。
2. 协议控制消息 (Protocol Control, Type ID: 1, 5, 6)
- 记忆程度 : 记住以下三个消息的名称和作用 。
- Set Chunk Size: 设置块大小。
- Window Acknowledgement Size: 设置确认窗口大小,用于流量控制。
- Set Peer Bandwidth: 告知对方自己的带宽限制。
- 核心要点 : 知道这几个消息是用来协商"交通规则",进行流量控制的,通常在 connect 之后立即交换。
3. 关键数据消息 (Data Message, Type ID: 18)
- 记忆程度 : 记住 onMetaData 这个事件。
- 核心要点 : 知道它的作用是传输视频的元数据(宽高、帧率、码率、编码器等),是播放器正确渲染画面的重要依据,通常在 publish 成功后第一个发送。
6.4.3:了解
这部分是协议的"细枝末节",在面试中被问到的概率较低,或者即使问到,你只需要说出它的概念和用途即可。
- 用户控制消息 (User Control, Type ID: 4): 知道有 Stream Begin, Stream EOF, Ping/Pong 这样的事件,用于同步流状态和心跳保活。
- 其他命令消息: 知道有 pause, seek, closeStream, deleteStream 等用于播放控制和清理的命令。
- 共享对象消息 (Shared Object, Type ID: 19, 16): 知道这是 Flash 时代用来同步数据的技术,现在基本不用了。
- 聚合消息 (Aggregate, Type ID: 22) : 知道它可以把多个小消息打包成一个大消息,用于优化传输。