15、RTMP协议入门:RTMP握手、AMF编码、消息类型、Chunk流机制

RTMP协议,说白了就是直播推流的老祖宗。我最早接触它的时候,还在用Flash播放器看视频。现在虽然HLS、WebRTC这些新协议很火,但RTMP在推流端依然是绝对的主力。你想想看,各大直播平台、OBS、FFmpeg,底层走的都是RTMP。

这一节,咱们就把它扒开看看。RTMP握手怎么玩?AMF编码是什么鬼?消息类型有哪些?Chunk流又是怎么分片的?嗯,一个一个来。

15.1 RTMP握手:先打个招呼

RTMP握手是客户端和服务器建立连接的第一步。说白了,就是双方互相确认一下「嘿,你也是RTMP协议吧?」。握手过程分三个阶段:

  • C0/S0:版本号。客户端发一个字节,告诉服务器我用的是RTMP版本(通常是3)。服务器回一个字节确认。
  • C1/S1:客户端发1536字节的随机数据。服务器也回1536字节。这里包含时间戳和随机数。
  • C2/S2:客户端收到S1后,把S1的时间戳和随机数原样返回。服务器收到C1后也一样。

我刚开始做推流SDK时,就栽在握手这一步。当时服务器一直断开连接,我查了半天,发现是C2里时间戳没对齐。嗯,细节决定成败。

握手核心要点

  • C0和S0固定为0x03(RTMP版本3)
  • C1和S1共1536字节,前4字节是时间戳,后4字节是随机数,其余填充0
  • C2和S2是回显,必须原样返回对方的时间戳和随机数

避坑指南:我曾经遇到过服务器只支持简单握手(Simple Handshake),不支持复杂握手。如果你用FFmpeg推流连不上,试试加个 -rtmp_handshake simple 参数。

15.2 AMF编码:数据怎么打包?

AMF(Action Message Format)是Adobe搞出来的一种序列化格式。RTMP用它来传输命令、元数据这些控制信息。说白了,就是把数据变成二进制流,方便网络传输。

AMF有两种版本:AMF0和AMF3。RTMP主要用AMF0,偶尔也用AMF3。常见的类型有:

类型标记 AMF0类型 说明
0x00 Number 双精度浮点数(8字节)
0x01 Boolean 布尔值(1字节)
0x02 String 字符串(2字节长度 + 内容)
0x03 Object 键值对集合,以0x000009结束
0x08 ECMA Array 带长度的键值对集合
0x0A Null 空值

举个例子,你要发送一个命令「connect」,AMF编码后大概长这样:

// 伪代码:AMF编码 "connect"
0x02                    // String类型
0x00 0x07              // 字符串长度7
0x63 0x6F 0x6E 0x6E 0x65 0x63 0x74  // "connect" 的ASCII码

我个人习惯在调试时,用Wireshark抓包看AMF数据。你想想看,如果编码错了,服务器根本不知道你在说什么。我曾经遇到过AMF Object的结束标记写成了0x000000,结果服务器一直解析不完,连接超时。

15.3 消息类型:RTMP在聊什么?

RTMP消息分为好几类。握手完成后,客户端和服务器就开始互发消息。消息类型由消息头里的Type ID决定:

Type ID 消息类型 用途
1 Set Chunk Size 设置Chunk大小
2 Abort Message 中断消息
3 Acknowledgement 确认收到数据
4 User Control 用户控制事件(如流开始/结束)
5 Window Acknowledgement Size 设置确认窗口大小
6 Set Peer Bandwidth 设置对端带宽
8 Audio 音频数据
9 Video 视频数据
15 Data Message (AMF3) 元数据(如onMetaData)
16 Shared Object (AMF3) 共享对象
17 Command Message (AMF3) 命令(如connect、play)
18 Data Message (AMF0) 元数据(AMF0版本)
20 Command Message (AMF0) 命令(AMF0版本)

你看,音频视频数据分别用Type ID 8和9。命令消息用20(AMF0)或17(AMF3)。我一般用AMF0,兼容性更好。

注意:消息类型和Chunk Stream ID是两码事。消息类型告诉你怎么解析数据,Chunk Stream ID告诉你怎么重组分片。别搞混了。

15.4 Chunk流机制:大包怎么拆?

RTMP底层走的是TCP,但TCP是流式传输,没有消息边界。所以RTMP搞了个Chunk机制,把大消息切成小块,一块一块发。接收端再拼回去。

Chunk的基本结构:

  • Basic Header:1-3字节,包含Chunk Stream ID和Chunk Type
  • Message Header:0、3、7、11字节四种格式,包含时间戳、消息长度、消息类型
  • Extended Timestamp:可选,当时间戳超过0xFFFFFF时用
  • Chunk Data:实际数据,长度不超过Chunk Size

Chunk Type有四种:

Type Message Header长度 说明
0 11字节 完整消息头,用于新消息或时间戳变化
1 7字节 不含消息流ID,其他字段与Type 0相同
2 3字节 只含时间戳增量,其他字段继承
3 0字节 完全继承上一个Chunk的头部

为什么会这样设计?说白了就是为了省带宽。你想想看,视频帧的音频数据,消息类型、流ID这些字段几乎不变。用Type 3的Chunk,每次只发1字节的Basic Header,省了多少开销。

我做过一个优化:把Chunk Size从默认的128字节改成4096字节。推流时CPU占用率降了15%。但注意,改太大也不行,网络丢包时重传成本高。

Chunk流的核心逻辑

  1. 发送方把消息按Chunk Size切成多个Chunk
  2. 第一个Chunk用Type 0,后续用Type 1/2/3
  3. 接收方根据Chunk Stream ID和消息长度重组
  4. 如果某个Chunk丢了,整个消息都得重传

15.5 知识体系总览

下面这张图,把RTMP协议的核心流程串起来了。从握手开始,到AMF编码、消息类型、Chunk分片,最后到音视频数据发送。我建议你把它存下来,写代码时对照着看。

RTMP协议核心流程 1. RTMP握手 C0/S0 → C1/S1 → C2/S2 2. AMF编码 Number/String/Object 3. 消息类型 Command/Audio/Video 4. Chunk Chunk结构 Basic Header (1-3字节) Message Header (0/3/7/11字节) Extended Timestamp (可选) Chunk Data (≤ Chunk Size) 总结:RTMP推流数据流 音视频数据 → 封装成消息 → AMF编码命令/元数据 → Chunk分片 → TCP发送

15.6 实战中的坑

最后分享几个我踩过的坑:

  • 握手超时:RTMP握手必须在几秒内完成。我曾经在弱网环境下测试,握手一直卡在C2阶段。后来加了超时重试机制,问题解决。
  • AMF编码大小端:AMF0的Number是大端(Big Endian)。如果你用C/C++写,记得用htonl/htons转换。我见过有人直接memcpy,结果服务器解析出NaN。
  • Chunk Size协商:客户端和服务器通过Set Chunk Size消息协商分片大小。默认128字节,但推流时建议调大。我一般设4096,兼顾延迟和CPU。
  • 消息重组:接收方必须等同一个消息的所有Chunk到齐才能处理。如果中间丢了一个Chunk,整个消息都得丢弃。我曾经因为网络抖动,视频花屏,查了半天才发现是Chunk重组逻辑有bug。

调试小技巧:用Wireshark抓包时,过滤条件写 rtmprtmpt。然后看每个Chunk的Basic Header,确认Chunk Type是否正确。我习惯先抓一个完整的推流过程,对照协议文档逐帧分析。

好了,RTMP协议的核心就这些。握手、AMF编码、消息类型、Chunk流,这四个东西搞明白,你就能自己写一个简单的RTMP推流器了。嗯,下一节咱们聊聊怎么用代码实现它。


公众号:蓝海资料掘金营,微信deep3321