7.3.4 Message 类型族与 AMF 编码(Message Types & AMF Encoding)
上一节结束时留了两个问题:type 8、9 之外还有哪些消息类型?命令消息里的字符串用什么编码书写?Chunk 层只负责切与拼,真正赋予字节意义的,是 Message 层的类型族。本节把这张编号地图一次性摊开。
类型族全景:一张编号地图
RTMP 的消息类型按 "谁在用" 可以划成四个梯队 [1]:
| type | 名称 | 用途 | 梯队 |
|---|---|---|---|
| 1 / 2 / 3 / 5 / 6 | 协议控制消息 | Set Chunk Size / Abort / Ack / Window Ack Size / Set Peer Bandwidth | Chunk 层自用 |
| 4 | 用户控制消息 | StreamBegin / EOF / Dry / SetBufferLength / Recorded / Ping 等流层事件 | 流层信号 |
| 8 / 9 | 音频 / 视频消息 | 媒体数据本体 | 媒体 |
| 15 / 16 / 17 | AMF3 数据 / 共享对象 / 命令消息 | 元数据、分布式状态、RPC | 控制 |
| 18 / 19 / 20 | AMF0 数据 / 共享对象 / 命令消息 | 同上,AMF0 编码 | 控制 |
| 22 | 整合消息 | 多条子消息的批处理打包 | 优化 |
编号的排布藏着记忆线索:AMF3 与 AMF0 的编号恰好差 3(20→17、18→15、19→16),三组一一对应。协议控制与用户控制两个梯队共享同一条信道:ms id 0(控制流)+ cs id 2(保留信道),到达即生效,时间戳被忽略——区别只在措辞强度:协议控制消息是 必须(MUST),用户控制消息是 应当(SHOULD) [1]。7.3.3 里 "cs id 2 保留" 的悬念,在此落地。
协议控制五件套:流量与节奏的自调节
Chunk 层的五条控制消息构成了一套微型流控协议 [1]:
- Set Chunk Size(1):通知对端新的切片上限,字段 31 bit,双向独立维护(7.3.3 已用)
- Abort(2):携带一个 cs id,告诉对端 "那条流上没收完的消息直接丢弃",关闭连接前常用
- Acknowledgement(3):4 字节序列号 = 累计已收字节数;每收满一个窗口必须回执
- Window Acknowledgement Size(5):设定回执窗口的字节数
- Set Peer Bandwidth(6):窗口值加 1 字节的 Limit Type——0 = Hard(照此限速)、1 = Soft(与既有限速取小)、2 = Dynamic(前一限制为 Hard 才生效,否则忽略)
这套机制的用意不是拥塞控制(那是 TCP 的本职),而是 防止对端灌爆自己的应用层缓冲——直播服务器借此给每个客户端的发送节奏戴上笼头。
Message 头:大端世界
再看 Message 层的逻辑头:type(1B)+ length(3B)+ timestamp(4B)+ stream id(3B),一律大端 [1]。对照 7.3.3 的陷阱——Chunk 头里 4 字节的 ms id 偏偏是小端,而且比这里还多一个字节。同一份协议、两层格式、两套字节序,抓包对照时务必想清楚自己站在哪一层。
值得多说一句的是分层关系:规范明确,RTMP 消息并不绑定 Chunk 流传输,换用其他传输协议同样可以承载 [1]。Chunk 层只是它最常见的载体——Type 0 头部里那份 timestamp/length/type/ms id,其实就是 Message 头信息的线上化身。
AMF:自描述的对象编码
控制梯队的消息负载,全部用 AMF(Action Message Format) 编码。它的设计取向是 自描述:每个值以 1 字节的类型标记开头,解码器无需预知 schema 即可遍历。以 AMF0 为例:Number 是 8 字节大端双精度浮点,String 是 2 字节大端长度加 UTF-8 字节流,Object 是键值对序列、以约定的结束标记收尾。AMF3 则在此基础上做了整型变长、字符串去重等压缩优化。
命令消息因 AMF 而成为 线上 RPC:一条命令消息由 命令名(字符串)+ Transaction ID(数字)+ 命令对象(参数表) 三件套组成;接收方处理后用 相同的 Transaction ID 回以 _result 或 _error——规范自己打了个比方,这套应答关联方式类似 IMAP 的 tag [1]。connect、createStream、publish、play 等下一节的全部主角,都装在这副骨架里。AMF 的完整定义在 Adobe 的 AMF 规范文档中,RTMP 规范以引用方式使用,本书不展开 [1]。
整合消息:批处理打包
type 22 的 Aggregate Message 是性能优化的产物:把多条子消息按 "头部 + 数据 + Back Pointer" 的单元串成一条大消息。规则有三条,语义轻重各不相同 [1]:
- ms id 覆盖是规定:整合消息的 ms id 直接覆盖 各子消息自身的 ms id,不容商量
- 时间戳重整是建议:子消息时间戳统一加上一个 offset(整合消息与首条子消息的时间戳之差)归一到流时间轴;首条子消息的时间戳 宜(SHOULD) 等于整合消息的时间戳,即 offset 宜为 0
- Back Pointer 有出处:记录前一条子消息含头的长度,格式与 FLV 的 PrevTagSize 一脉相承,专供回退 seek——7.1.3 的容器知识在此回流
收益有两重:切片数量随单条消息变大而减少;子消息在内存中连续存放,系统调用发送更高效 [1]。服务器在高并发分发时尤爱此招。
小结:静态族谱,动态会话
至此,Message 层的静态全景已经齐备:四个梯队的类型编号、协议控制五件套、大端消息头、AMF 自描述编码、整合消息的批处理语义。但族谱只是名册,RTMP 真正的生命力在 会话——connect 如何建立应用连接、createStream 如何开辟流信道、publish 与 play 如何驱动一场直播的推拉。下一节,我们把这些命令排成时序,看一场直播从建立到开播的完整舞步。