1. RTMP是什么?
RTMP(Real-Time Messaging Protocol,实时消息传输协议)是一种用来持续传输音频、视频和相关信息的协议。在直播中,它的一个常见用途是推流:主播一边录,直播软件一边把已经编码好的画面和声音发给平台,不必等正常直播结束后才上传。
摄像头和麦克风复责采集画面和声音,编码器复责把这些数据压缩到网络能传的动的大小,而RTMP复责规定了直播软件和服务器该怎样传递这些数据。
1.1 RTMP的特点
音视频可以共用一条连接。直播软件会交替发送一小段视频段、一小段音频,并附上时间戳。
实时不等于零延迟。RTMP通常通过TCP传输。网络丢包时,TCP会重传以保证数据完整、有序。这也意味着网络不好时,直播可能出现卡顿或延迟增加。
2.RTMP握手是什么?
由于RTMP是基于TCP实现的。RTMP握手发生在TCP连接建立后、发送直播数据之前。可以把TCP连接理解为“电话接通了”,把RTMP握手理解为“双方在通话前说:我们都用同一种协议交流”。
以下是RTMP公开规范里的简单握手。C代表客户端发出的数据,S代表服务端发出的数据。

3. RTMP分块是什么?
在RTMP握手后,RTMP把音频、视频和命令组织成消息(Message),再把每条消息切成一个或多个块(Chunk)来传输。
3.1 为什么要分块?
想象一条很大的视频消息正在发送,这时来了一条很短的音频消息。如果必须把整条视频发完,音频就要一直等。切成小块后,发送端可以在两个视频块间插入音频块,再接着发视频。
3.2 分块的流程?
发送端:先确定消息有多长、每块能装多少。假设FFmpeg准备发送一条300字节的RTMP音频消息(300字节指的是消息体,不包括后续要添加的块头)。RTMP默认每块最多装128字节的消息体。
给每块加头:让接收端知道“这是谁的数据”。每块的结构是:
基本头+消息头+可选的拓展时间戳+本地数据。基本头中最重要的是CSID(块流ID)和fmt(消息头格式)。可以把CSID理解为该往哪个拼装区放,把fmt理解为这次写了多少新信息。传输中:不同块流可以穿插,同一条消息保持顺序。例如音频使用
CSID=4,视频使用CSID=6,发送顺序可以是:视频块① → 音频块① → 音频块② → 视频块② → 音频块③ CSID 6 CSID 4 CSID 4 CSID 6 CSID 4
接收端:收到多少算一块,拼到什么时候算一条消息?
关键是:首块说明的是整条消息的长度,每块实际读取的长度由接收端算出来。


4. RTMP中的AMF0指令是什么?
在RTMP中,connect、createStream、publish是命令;AMF0是把命令名和参数写出字节的规则。可以把它理解成一次远程函数调用,客户端告诉服务器“我要执行哪个操作”,再按照顺序附上参数。
联系前面的分块操作,接收端的处理顺序是:先把Chunk拼成完整的RTMP消息,再根据消息类型解析消息体。如果消息体类型是20(十六进制0x14),就按AMF0命令消息读取。
- 一条AMF0命令,里面依次放了什么?
| 顺序 | 字段 | 常用 AMF0 类型 | 含义 |
|---|---|---|---|
| 1 | 命令名 | String | 要执行什么,例如 "connect"、"publish" |
| 2 | 事务 ID | Number | 用来把请求和回复对上。例如请求编号为 2,对应的 _result 也带 2 |
| 3 | 命令对象 | Object 或 Null | 放这条命令的配置;不需要时,通常仍写一个 null 占位 |
| 4 及以后 | 命令参数 | 按命令而定 | 例如流名称、发布模式;有些命令没有额外参数 |
- 用一次推流看懂三个核心命令
假设要推流到rtmp://example.com/live/camera01。这里live是应用名,camera01是流名。握手完成后,关键对话如下(省略了协议控制消息):
| 方向 | 命令体示意 | 通俗解释 |
|---|---|---|
| 客户端 → 服务器 | ["connect", 1, {app:"live", tcUrl:"rtmp://example.com/live"}] | 连接应用:“我要进入 live 这个应用。” |
| 服务器 → 客户端 | ["_result", 1, …, {code:"NetConnection.Connect.Success", …}] | “编号 1 的连接请求成功了。”此时还没开始推流。 |
| 客户端 → 服务器 | ["createStream", 2, null] | 创建流:“请给我分配一条消息流。” |
| 服务器 → 客户端 | ["_result", 2, null, 1] | “编号 2 的请求成功了,给你的消息流 ID 是 1。”这个 1 是示例,以实际返回值为准。 |
| 客户端 → 服务器 | ["publish", 0, null, "camera01", "live"] | 发布流:“我要在刚分配的消息流上,以直播模式发布 camera01。” |
| 服务器 → 客户端 | ["onStatus", 0, null, {code:"NetStream.Publish.Start", …}] | “这条流已开始发布。”接下来传送的音频、视频各自使用对应的 RTMP 消息类型。 |

- AMF0怎么编码一条命令?
以["createStream", 2, null]为例。
| 字段 | 实际字节(十六进制) | 怎样理解 |
|---|---|---|
命令名 "createStream" | 02 · 00 0C · 63 72 65 61 74 65 53 74 72 65 61 6D | 02 表示字符串;00 0C 表示后面有 12 B 文本;共 15 B |
事务 ID 2 | 00 · 40 00 00 00 00 00 00 00 | 00 表示 Number;后面 8 B 是大端双精度浮点数 2.0;共 9 B |
命令对象 null | 05 | 05 本身就表示 Null,没有后续内容;共 1 B |

5.主播如何通过RTMP推流
当主播收到服务器已接受发布请求。这时直播通道已经建好了,但观众还不能看到画面,后续两条可以同时进行的流程:
主播 → 服务器:publish 成功 → 发送解码配置 → 持续发送音频、视频 观众 → 服务器:建立自己的连接 → play 请求同一条流 → 接收、解码、播放
5.1 主播先告诉接收端:这些数据怎么解码
以常见的AAC音频+H264视频为例,推流刚开始时:
| 消息 | RTMP 消息类型 | 里面是什么 | 通俗理解 |
|---|---|---|---|
onMetaData | 18,即 0x12(AMF0 数据) | 可包含宽高、帧率、编码类型等 | “这场直播大致是什么规格。”这是描述信息,可选。 |
| AAC 配置消息 | 8,即 0x08(音频) | AACPacketType=0,携带 AudioSpecificConfig,描述 AAC 类型、采样率、声道配置等 | “后面的声音要按这些参数解码。” |
| AVC 配置消息 | 9,即 0x09(视频) | AVCPacketType=0,携带 AVC 解码配置,其中通常包括 SPS、PPS | “后面的 H.264 画面要用这套参数解码。” |
此处即使onMetaData写了视频是H.264、分辨率是1920x1080,也不能代替视频解码器需要的SPS、PPS。前者描述直播,后者参与实际编码。
5.2 主播开始持续发送真正的声音和画面
**配置发出后,同样的RTMP消息类型开始承载媒体数据。判断”这是配置还是内容“,要再看消息体里的包类型:
| 抓包看到什么 | 实际含义 |
|---|---|
音频消息 0x08,AACPacketType=0 | AAC 解码配置,本身不产生可播放的声音 |
音频消息 0x08,AACPacketType=1 | 压缩后的 AAC 音频数据 |
视频消息 0x09,AVCPacketType=0 | H.264 解码配置,本身不是一幅画面 |
视频消息 0x09,AVCPacketType=1 | H.264 编码数据,可包含关键帧或依赖其他帧的画面 |
- 然后,观众加入:走自己的
play流程。假设主播发布的流名叫camera01,观众也用RTMP播放。观众不会复用主播的TCP连接,而是自己向服务器发起请求:
观众 → 服务器:握手 → connect(“live”) → createStream → play(“camera01”) 服务器 → 观众:
StreamBegin、onStatus(NetStream.Play.Start)服务器 → 观众:解码配置 + 音视频数据其中:
publish("camera01")是主播说”我要发布“这路直播;
play("camera01")是观众说”我要观看“这路直播。两端的应用名和流名找到同一份内容,各自连接上的消息流ID不要求相同。
5.3 常见现象解析
为什么观众中途进入,有时先听到声音,过一会儿才看到画面?
一种原因是:播放器已经拿到 AAC 配置和音频数据,可以开始放声音;但此时收到的视频帧依赖入场前的画面。视频解码器还需要配置和一个能独立开始解码的位置,例如 H.264 的 IDR 帧。如果服务器缓存了合适的关键帧及后续数据,就可以从那里开始发给新观众。
5.4 直播过程中:媒体与控制消息
在建立连接时或传输期间会出现一些控制消息:
| 控制消息 | 解决什么问题 |
|---|---|
Set Chunk Size | 发送方通知对端:“接下来我按新的块大小切消息。” |
Window Acknowledgement Size / Acknowledgement | 指定接收多少字节后报告一次,并回报累计接收量。不是每个音视频帧都回复一次。 |
PingRequest / PingResponse | 一方探测对端是否仍有响应,对端回送对应的时间值。 |
5.5 结束直播/退出直播
主播正常下播时,如果编码器里还有延迟输出,推流程序应先排出并发送尾部音视频,再结束发布。RTMP 可以用 deleteStream 删除相应的消息流;如果这条连接不再使用,再关闭 TCP。若是网络突然中断,就不一定能看到完整的收尾命令。反过来,一个观众退出,只结束他自己的播放,不会让主播或其他观众下线。