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 如何驱动一场直播的推拉。下一节,我们把这些命令排成时序,看一场直播从建立到开播的完整舞步。

Copyright © Since 2021 李述博 (Arikan.Li) , All Rights Reserved all right reserved,powered by GitbookLast Updated: 2026-08-26 08:25:15

results matching ""

    No results matching ""