【音视频】RTMP协议推流抓包分析
一、RTMP基本原理
RTMP(Real Time Messaging Protocol)是专为实时流媒体传输设计的应用层协议,核心目标是在客户端与服务器间高效传输音视频数据
1.1 基于TCP的可靠连接
RTMP建立在TCP协议之上,利用TCP的可靠传输特性(如流量控制、错误重传)确保数据稳定传输,适合对时序敏感的实时流媒体场景(如直播、视频会议)
1.2 消息分块与流式传输
-
消息(Message)与块(Chunk)的分层结构
- 消息层:将完整数据(如一帧视频、一条命令)封装为消息,包含消息类型、长度、时间戳等头部信息。
- 块层:将大消息分割为小数据块(Chunk,通常128字节)传输,减少网络缓冲压力,支持实时解包播放。
-
消息类型
- 命令消息:用于客户端与服务器交互(如创建流、播放控制),通过AMF(Action Message Format)编码。
- 媒体消息:封装音视频数据(如H.264视频、AAC音频),按时间戳有序传输。
1.3 低延迟与时序控制
-
握手同步时间戳
通过三次握手交换时间戳(见前序握手流程),计算网络延迟(RTT),确保客户端与服务器时序一致,避免播放卡顿。 -
流式传输与缓冲策略
- 服务器边发送边编码,客户端边接收边解码,仅缓冲少量数据(通常几十毫秒到几秒),实现低延迟(一般1-3秒)。
- 支持动态调整码率,适应网络波动。
1.4 命令交互与媒体传输的协同
-
命令交互流程
- 客户端通过
connect
命令建立连接,createStream
创建流,publish
发布媒体或play
拉取媒体。 - 服务器以AMF格式响应命令,返回状态(如成功、错误)。
- 客户端通过
-
媒体数据传输
- 发布端(如推流软件)将编码后的音视频数据封装为媒体消息,按时间戳顺序发送。
- 接收端(如播放器)解析消息,按时间戳同步音视频轨道,实现同步播放。
二、RTMP协议握手流程
RTMP(Real Time Messaging Protocol)是Adobe开发的用于实时流媒体传输的应用层协议,其握手流程是建立客户端与服务器连接的关键步骤。以下是RTMP握手的详细流程和技术细节:
1.1 RTMP握手的基本概念
RTMP建立在TCP连接之上,握手过程属于应用层协议交互,目的是:
- 协商协议版本
- 同步时间戳
- 交换随机数(防重放攻击)
- 确认双方通信能力
1.2 握手流程:三次交互(C0/C1/C2与S0/S1/S2)
RTMP握手分为客户端(Client)和服务器(Server)的双向数据交换,共6个数据包,可分为三个阶段:
阶段1:客户端发送C0和C1
-
C0:版本信息
- 长度:1字节
- 内容:客户端支持的RTMP协议版本号(如0x03表示版本3)。
-
C1:客户端握手包
- 长度:至少1536字节(含填充数据)
- 结构:
- 时间戳(Timestamp):4字节无符号整数(0x00000000),后续由服务器填充有效时间。
- 时间戳扩展(Timestamp Extended):4字节无符号整数(当时间戳超过0xFFFFFF时使用)。
- 随机数(Random Data):1528字节随机数据(用于安全验证和防重放)。
阶段2:服务器响应S0和S1,并发送S2
-
S0:版本确认
- 长度:1字节
- 内容:服务器选择的协议版本号(通常与C0一致,若不支持则返回0x00并断开连接)。
-
S1:服务器握手包
- 长度:至少1536字节(结构与C1类似)
- 结构:
- 时间戳:4字节,服务器生成的当前时间戳(用于客户端同步)。
- 时间戳扩展:4字节。
- 随机数:1528字节随机数据(与客户端随机数互验)。
-
S2:服务器响应包
- 长度:至少1536字节
- 结构:
- 时间戳A:4字节,填充客户端C1中的时间戳(用于客户端确认服务器接收C1的时间)。
- 时间戳扩展A:4字节。
- 时间戳B:4字节,服务器S1中的时间戳(用于客户端同步服务器时间)。
- 时间戳扩展B:4字节。
- 填充数据:与C1中的随机数长度一致(1528字节)。
阶段3:客户端发送C2,完成握手
- C2:客户端响应包
- 长度:至少1536字节
- 结构:
- 时间戳A:4字节,填充服务器S1中的时间戳(用于服务器确认客户端接收S1的时间)。
- 时间戳扩展A:4字节。
- 时间戳B:4字节,客户端C1中的时间戳(用于服务器同步客户端时间)。
- 时间戳扩展B:4字节。
- 填充数据:与S1中的随机数长度一致(1528字节)。
1.3 握手关键技术细节
-
时间戳同步
- 客户端和服务器通过交换时间戳(Timestamp)计算网络延迟(RTT),用于后续数据传输的时序控制。
- 时间戳超过0xFFFFFF(约24天)时,需使用时间戳扩展字段(4字节)。
-
随机数的作用
- 1528字节的随机数用于验证双方身份,防止重放攻击(Replay Attack),确保握手的唯一性。
-
填充数据
- 握手包强制填充至1536字节,避免数据包长度泄露信息,增强安全性。
-
协议版本兼容
- 若服务器不支持客户端的版本(如C0为0x04),则返回S0=0x00并断开连接。
1.4 握手流程时序图
客户端 服务器| || C0(版本) + C1(时间戳+随机数) → || || ← S0(版本) + S1(时间戳+随机数) || || C2(响应S1时间戳) → || || ← S2(响应C1时间戳) || |✔ 握手完成,开始RTMP消息交互 |
1.5 扩展:RTMPS(加密握手)
- RTMPS(基于TLS/SSL的RTMP)在握手前增加TLS握手流程,用于加密传输。
- 握手包结构与RTMP类似,但随机数等数据会通过TLS加密,防止中间人攻击。
三、RTMP 推流抓包分析
3.1 测试环境
- 推流端:FFmpeg Windows
- 服务器:SRS Ubuntu
- 网络:局域网
3.2 推流
- 使用ffmpeg推流视频到RTMP服务器
- 将
h264
和aac
格式封装到flv
中,然后用RTMP
协议发到服务器
ffmpeg -i .\4k_50.mp4 -vcodec h264 -acodec aac -f flv "rtmp://10.22.79.251/live/livestream"
3.3 TCP三次握手
3.3.1 TCP第一次握手
1. 端口标识
- 源端口(Source Port):
61319
- 客户端的 临时端口(系统随机分配,用于本次连接的临时标识,通常 >1024)。
- 目的端口(Destination Port):
1935
- 知名端口,RTMP 协议默认端口(多用于直播、流媒体场景),也可能是自定义服务,但 1935 最典型关联 RTMP。
2. 序列号(Sequence Number)
- 相对序号:
0
(Wireshark 简化显示,方便分析) - 原始序号:
499989480
(客户端生成的 初始序列号(ISN),随机值)- 作用:TCP 通过序列号保证数据有序性,SYN 包的 ISN 是连接的 “起始编号”,后续数据的 SEQ 会基于此递增。
- 特殊点:SYN 包自身会 消耗 1 个序列号(即使无数据),因此下一个包的 SEQ 为
ISN + 1
。
3. 确认号(Acknowledgment Number)
- 值:
0
- 因这是 第一次握手(客户端→服务器),客户端尚未收到服务器数据,故确认号为
0
。 - 若收到服务器的
SYN+ACK
,客户端的 ACK 会变为服务器 ISN + 1
(确认已接收对方的 SYN)。
- 因这是 第一次握手(客户端→服务器),客户端尚未收到服务器数据,故确认号为
4. 标志位(Flags)
- 值:
0x002 (SYN)
- SYN 标志位为 1,表示这是 连接请求包(三次握手的第一步)。
- 后续若服务器响应,会返回
SYN+ACK
(SYN=1、ACK=1);客户端再回ACK
(ACK=1),完成握手。
5. 窗口大小(Window)
- 值:
65535
- 客户端的 接收窗口,告诉服务器:“我最多能接收 65535 字节的数据”。
- 这是 TCP 流量控制 的核心:服务器发数据不能超过该窗口大小。
6. 校验和(Checksum)
- 值:
0x6ee3
,状态Unverified
- 并非错误!现代网卡支持 校验和卸载(硬件计算校验和),抓包时可能未填充,故 Wireshark 标记为 “未验证”,属正常现象。
3.3.2 TCP第二次握手
1. 端口映射
- 源端口(Src Port):
1935
(服务器端端口,持续监听,对应 RTMP 等服务) - 目的端口(Dst Port):
61319
(客户端临时端口,与第一次握手的源端口对应)
→ 体现 “客户端→服务器” 的反向回复:服务器用客户端请求的目的端口(1935)作为源端口,回应客户端的源端口(61319)。
2. 序列号(Sequence Number)
- 相对序号:
0
(Wireshark 简化显示) - 原始序号:
2916634360
(服务器生成的 初始序列号(ISN),随机值)
→ 服务器也需要一个 “起始编号”,后续数据的 SEQ 会基于此递增(和客户端的 ISN 独立)。
3. 确认号(Acknowledgment Number)
- 相对序号:
1
(Wireshark 简化显示) - 原始序号:
499989481
→ 计算逻辑:对客户端第一次握手SYN
包的确认。
客户端SYN
包的原始序列号是499989480
,TCP 规定SYN
包自身消耗 1 个序列号,因此确认号 = 客户端 ISN + 1 =499989480 + 1 = 499989481
。
4. 标志位(Flags)
- 值:
0x012
→ 二进制0001 0010
,对应SYN=1
+ACK=1
SYN=1
:服务器也发起 “同步请求”,交换自己的 ISN(和客户端的 SYN 呼应)。ACK=1
:确认已收到客户端的SYN
包(通过确认号体现)。
5. 窗口大小(Window)
- 值:
64240
→ 服务器的 接收窗口,告诉客户端:“我最多能接收 64240 字节的数据”,用于后续流量控制。
6. TCP 选项(Options)
- 包含 MSS(Maximum Segment Size,最大分段大小)、SACK 允许 等:
- MSS:协商 “单个 TCP 段的最大数据长度”(通常为
MTU - 40
,如以太网 MTU=1500,则 MSS=1460),避免 IP 分片。 - SACK(Selective Acknowledgment):允许接收方选择性确认失序的数据包,提升重传效率。
- MSS:协商 “单个 TCP 段的最大数据长度”(通常为
3.3.3 TCP第三次握手
1. 序列号(Sequence Number)
- 相对值:
1
(Wireshark显示的相对序列号,简化分析) - 原始值:
499989481
(实际传输的绝对序列号,用于唯一标识数据) - 作用:表示发送方已发送到序列号1(累计计数),无数据 payload(
Len: 0
),故仅用于确认。
2. 确认号(Acknowledgment Number)
- 相对值:
1
(相对确认号) - 原始值:
2916634361
(绝对确认号) - 作用:告知对方“已收到序列号≤1的数据”,请求对方下一个数据包从
2
开始发送。
3. 标志位(Flags)
- 值:
0x010
(二进制00010000
),仅ACK位置1。 - 含义:这是一个纯确认报文,不含数据(
Len: 0
),仅用于确认之前收到的数据包。
4. 窗口大小(Window)
- 原始值:
255
- 计算后大小:
65280
(因窗口缩放因子为256
,255 × 256 = 65280
) - 作用:告知对方“当前接收缓冲区还能容纳65280字节的数据”,用于流量控制。
5. 头部长度(Header Length)
- 值:
20字节
(二进制0101
,表示5个32位字,5×4=20
) - 含义:TCP头部无选项字段(最小头部长度),仅包含基本字段。
通过TCP的三次握手,成功建立起TCP连接,接着下面就是RTMP的握手了
3.4 RTMP三次握手
3.4.1 RTMP第一次握手
握手初始化(C0+C1 包)
RTMP 握手分三步,此包是 客户端向服务器发送的首阶段握手数据:
1. C0:协议版本(1 字节)
Protocol version: 03
→ 表示使用 RTMP/1.0 版本(0x03 是 RTMP 标准版本号,兼容多数服务器)。
2. C1:握手数据(1536 字节,此处为分段续传)
包含 时间戳、随机数、零填充 等字段:
- 时间戳:同步双方时钟,保证音视频流的时序一致性。
- 随机数:防伪造,验证连接真实性。
- 零填充:固定 1536 字节长度(RTMP 握手包格式要求)。
3. 分段传输的原因
C0+C1 总长 1 + 1536 = 1537
字节,而 TCP 的 MSS(最大分段大小)为 1460(以太网常见 MTU=1500,减去 IP 头 20 + TCP 头 20,剩余 1460),因此:
- 第一个 TCP 段传
1460
字节(包含 C0 和 1459 字节的 C1); - 本次段传
77
字节(C1 的剩余部分,1537 - 1460 = 77
)。
3.4.2 RTMP第二次握手
RTMP 握手分三步,此包是 客户端对服务器 S0+S1+S2
的最终确认,核心作用:
- 确认服务器的握手数据:
- 验证服务器
S1
中的时间戳、随机数等参数,确保双方同步。 - 告知服务器:“我已收到并认可你的握手响应(S0+S1+S2)”。
- 验证服务器
- 完成握手流程:
- 客户端发
C0+C1
→ 服务器回S0+S1+S2
→ 客户端发C2
,三阶段握手完成,RTMP 连接正式就绪。
- 客户端发
RTMP 层:握手响应(S0+S1+S2 包)
RTMP 握手的核心是 “版本协商 + 时间戳同步 + 随机数验证”,此包包含三部分:
1. S0:协议版本(1 字节)
Protocol version: 03
→ 与客户端 C0 的版本一致(RTMP/1.0),确保 版本兼容。
2. S1:服务器握手数据(1536 字节,分段传输)
包含 服务器时间戳、随机数、零填充:
- 时间戳:同步双方时钟,保证音视频流的时序一致性。
- 随机数:验证连接真实性,防止伪造。
- 零填充:固定 1536 字节长度(RTMP 握手包格式要求)。
3. S2:客户端握手数据的 “回声”
- 内容与客户端 C1 包完全一致(图中
Handshake data
部分)。 - 作用:验证服务器是否正确接收并解析了客户端的 C1 包,确保 双向同步。
RTMP第三次握手
这个报文是 RTMP握手阶段的C2包
(客户端发送的第二次握手响应),承载在TCP连接中,属于RTMP握手的最终确认环节。以下从 协议层级、RTMP握手逻辑、字段细节 展开分析:
RTMP的C2包
C2包 的关键功能:
- 时间戳双向同步:
- 回传服务器
S1
的时间戳(让服务器计算RTT:服务器当前时间 - S1发送时间
)。 - 携带客户端
C2
的时间戳(让服务器同步客户端时序)。
- 回传服务器
- 随机数校验:
- 包含与
C1
一致的随机数据片段,验证握手完整性(防止伪造)。
- 包含与
- 握手完成标志:
C2
发送后,RTMP握手正式完成,后续进入应用层交互(如connect
命令、媒体流传输)。
通过RTMP三次握手,成功建立RTMP通讯,此时可以开始传输RTMP协议数据了
3.5 connect报文
RTMP 头部(RTMP Header)
RTMP 消息通过 Chunk(分块) 传输,头部包含基础控制信息:
字段 | 值 / 解析 | 作用 |
---|---|---|
Format | 0 | Chunk 格式为 完整头(包含所有字段:时间戳、体大小、类型、流 ID),用于首条消息或 Chunk 参数变化时。 |
Chunk Stream ID | 3 | Chunk 流 ID,3 是 RTMP 约定的 控制流(Control Stream),用于传输协议控制命令(如 connect 、createStream )。 |
Timestamp | 0 | 时间戳(毫秒),此处为初始连接,时间戳置 0。 |
Body size | 141 | 消息体长度(141 字节),即后续 AMF0 编码数据的大小。 |
Type ID | 0x14 (十进制 20 ) | 消息类型:AMF0 Command (RTMP 规定 Type ID=20 表示 AMF0 编码的命令消息)。 |
Stream ID | 0 | 流 ID,0 表示 全局流(用于控制消息,非媒体流)。 |
RTMP 消息体(AMF0 编码)
RTMP 使用 AMF0(Action Message Format 0) 序列化命令和数据,此包的结构为 “命令名 + 事务 ID + 命令参数”:
1. 命令名:connect
- AMF0 类型:
String (0x02)
- 内容:
connect
→ 告知服务器:执行 “连接到应用” 的命令。
2. 事务 ID(Transaction ID)
- AMF0 类型:
Number (0x00)
- 值:
1
→ 用于 匹配响应:服务器返回的_result
或_error
消息会携带相同事务 ID(此处为 1),确保请求 - 响应对应。
3. 命令参数(Object)
以 AMF0 对象形式传递连接参数,包含 4 个键值对:
键(Key) | 值(Value) | 意义 |
---|---|---|
app | String 'live' | 要连接的 应用名称(服务器端配置的应用,如直播推流的 live 应用)。 |
type | String 'nonprivate' | 连接类型:nonprivate 表示 公开连接(无访问限制)。 |
flashVer | String 'FMLE/3.0 (compatible; Lavf62.0.102)' | 模拟的 Flash 版本: - FMLE 是 Adobe Flash Media Live Encoder(推流工具); - Lavf62.0.102 是 FFmpeg 的格式库版本(实际是 FFmpeg推流)。 |
tcUrl | String 'rtmp://10.22.79.251:1935/live' | 目标 RTMP URL: rtmp://<服务器IP>:<端口>/<应用名> ,明确连接的服务器和应用。 |
报文的核心意义:RTMP 连接协商的 “敲门砖”
-
阶段定位:
RTMP 握手(C0/C1→S0/S1/S2→C2)完成后,进入 应用层连接协商阶段,此包是客户端发起的 第一个应用层命令。 -
对服务器的要求:
服务器需验证:app
是否存在(如live
应用是否配置);tcUrl
是否合法(IP、端口、应用名是否匹配);- 客户端身份(通过
flashVer
等字段,部分服务器验证推流工具合法性)。
-
后续流程:
服务器会返回_result
或_error
消息(携带事务 ID=1):- 若成功:返回连接参数(如
fmsVer
、capabilities
等),客户端可继续创建流(createStream
命令)。 - 若失败:返回错误码(如
NetConnection.Connect.Rejected
),连接终止。
- 若成功:返回连接参数(如
3.6 Window Acknowledgement Size
报文
这是 RTMP 协议的 Window Acknowledgement Size
控制报文,用于 应用层流量控制(告知对方自身的接收窗口大小),以下从 RTMP 结构 和 协议逻辑 解析:
RTMP 头部(RTMP Header)解析
字段 | 值 / 解析 | 作用 |
---|---|---|
Format | 0 | Chunk 格式为 完整头(包含所有字段),用于首条控制消息或参数变化时。 |
Chunk Stream ID | 2 | Chunk 流 ID,2 是 RTMP 约定的 控制流(用于传输窗口、块大小等协议控制消息)。 |
Timestamp | 0 | 时间戳(毫秒),此处为控制消息,时间戳置 0。 |
Body size | 4 | 消息体长度(4 字节,刚好存储一个 32 位整数)。 |
Type ID | 0x05 (十进制 5 ) | 消息类型:Window Acknowledgement Size (RTMP 规定 Type ID=5 表示此控制消息)。 |
Stream ID | 0 | 流 ID,0 表示 全局控制流(非媒体流,仅用于协议控制)。 |
RTMP 消息体(Window Acknowledgement Size)
- 内容:
Window acknowledgement size: 2500000
- 本质:一个 32 位无符号整数(4 字节),表示 接收方的应用层接收窗口大小(单位:字节)。
协议逻辑:RTMP 应用层的 “流量控制”
-
为什么需要应用层窗口?
TCP 层已有窗口机制,但 RTMP 是 流式协议(音视频传输对时序、吞吐量更敏感),应用层窗口可:- 更 精准控制媒体数据(如区分音视频流和控制流的窗口);
- 适配 RTMP 分块(Chunk)机制(按 Chunk 统计数据量,而非 TCP 段)。
-
窗口的作用:
接收方告知发送方:“我最多能接收2500000
字节的 RTMP 数据,超过后请等待我的 ACK 确认再发”。- 发送方需累计发送的数据量(按 RTMP Chunk 计算),达到窗口大小时,必须等待接收方的
Acknowledgement
消息才能继续发送。
- 发送方需累计发送的数据量(按 RTMP Chunk 计算),达到窗口大小时,必须等待接收方的
上下文关联:连接初始化阶段的协商
结合前序报文(如 connect
命令),此包发生在 RTMP 握手完成、连接协商阶段:
- 服务器通过此消息 设置自身的接收窗口(告诉客户端:“我最多收 250 万字节,别发太快”);
- 后续客户端需遵守此窗口限制,避免发送过量数据导致接收方缓冲区溢出。
与 TCP 窗口的区别
维度 | RTMP 应用层窗口(此报文) | TCP 传输层窗口 |
---|---|---|
控制层级 | 应用层(RTMP 协议内) | 传输层(TCP 协议内) |
统计对象 | RTMP Chunk 的数据量 | TCP 段的字节数 |
作用 | 适配流媒体传输的时序、分块特性 | 保证网络层的可靠传输、拥塞控制 |
总结:应用层流量控制的 “信号灯”
这个报文是 RTMP 协议的流量控制核心:
- 通过
Window Acknowledgement Size
告知对方自身的接收能力,避免数据泛滥; - 与 TCP 窗口协同,从 应用层 + 传输层 两层保障流媒体传输的稳定性(既防网络拥塞,也防应用层处理不过来)。
后续若客户端发送的数据量累计达到 2500000
字节,需等待服务器的 Acknowledgement
消息(Type ID=6)才能继续发送,否则会触发流控等待。
3.7 Set Peer Bandwidth 报文、Set Chunk Size报文
这是 RTMP 协议的两个核心控制报文(同一条 TCP 段中携带,属于 应用层传输参数协商 阶段),分别是 Set Peer Bandwidth
(设置对端带宽) 和 Set Chunk Size
(设置分块大小),用于优化流媒体传输效率。以下分层解析:
整体上下文:RTMP 连接初始化后的“参数协商”
在 RTMP 握手(C0/C1/S0/S1/S2/C2)和 connect
命令之后,服务器会发送 控制消息 协商 带宽、分块大小 等传输参数,为后续音视频流传输铺路。
报文 1:Set Peer Bandwidth
(Type ID = 0x06)
1. RTMP 头部
字段 | 值/解析 | 作用 |
---|---|---|
Format | 0 | 完整 Chunk 头(首条控制消息,携带所有字段)。 |
Chunk Stream ID | 2 | 控制流(Chunk Stream ID=2,RTMP 约定用于传输带宽、块大小等控制消息)。 |
Timestamp | 0 | 控制消息无时间戳,置 0。 |
Body size | 5 | 消息体长度(5 字节:4 字节窗口 + 1 字节限制类型)。 |
Type ID | 0x06 (十进制 6 ) | 消息类型:Set Peer Bandwidth (设置对端发送带宽限制)。 |
Stream ID | 0 | 全局控制流(非媒体流)。 |
2. RTMP 消息体
Window acknowledgement size
:2500000
(250 万字节)→ 告知客户端:“你发送的 RTMP 数据累计不能超过 250 万字节,否则需等待我的确认”。Limit type
:Dynamic (2)
→ 带宽限制类型:Dynamic
表示 “动态限制”(客户端可根据接收方窗口灵活调整发送速率,非强制硬限制);- 其他类型:
Hard
(硬限制,必须严格遵守)、Soft
(软限制,可短暂突破)。
报文 2:Set Chunk Size
(Type ID = 0x01)
1. RTMP 头部
字段 | 值/解析 | 作用 |
---|---|---|
Format | 0 | 完整 Chunk 头(首条控制消息,携带所有字段)。 |
Chunk Stream ID | 2 | 控制流(同一会话的控制消息复用 Chunk Stream ID=2)。 |
Timestamp | 0 | 控制消息无时间戳,置 0。 |
Body size | 4 | 消息体长度(4 字节,存储分块大小)。 |
Type ID | 0x01 (十进制 1 ) | 消息类型:Set Chunk Size (设置 RTMP 分块大小)。 |
Stream ID | 0 | 全局控制流(非媒体流)。 |
2. RTMP 消息体
Chunk size
:60000
→ 将 RTMP 分块大小设置为 60000 字节(默认分块大小为 128 字节)。- 作用:
- 更大的分块可 减少 Chunk 头部开销(每个 Chunk 需携带头部,大分块降低头部占比);
- 提升 大流量场景(如直播推流)的传输效率(减少分片次数,降低 CPU 消耗)。
协议逻辑:“带宽 + 分块”双优化
-
Set Peer Bandwidth
的意义:
服务器限制客户端的 发送速率上限,避免自身接收缓冲区溢出(结合之前的Window Acknowledgement Size
,从 “发送方速率”和“接收方容量” 双向控制流量)。 -
Set Chunk Size
的意义:
协商更高效的 RTMP 分块策略,适配大带宽传输(如 60000 字节接近 TCP MSS,减少 IP/TCP 分片,提升吞吐量)。 -
与 TCP 层的协同:
- TCP 层通过窗口、拥塞控制保证 网络可靠性;
- RTMP 层通过
Set Peer Bandwidth
和Set Chunk Size
优化 应用层传输效率,两者结合让流媒体传输更稳定、高效。
总结:传输参数的“黄金配置”
这两个报文是 RTMP 连接的 “性能调优阶段”:
Set Peer Bandwidth
限制发送速率,防止拥塞;Set Chunk Size
放大分块,提升效率;- 两者共同为后续 音视频流传输(如
publish
推流、play
拉流)奠定基础,让数据既“流得稳”又“流得快”。
后续若客户端发送大尺寸视频帧,会按 60000 字节的分块传输,减少协议开销;同时遵守 250 万字节的带宽限制,避免压垮服务器。
3.8 Set Chunk Size报文、releaseStream报文、FCublish报文、createStream报文、_checkbw
报文
这组报文属于 RTMP 协议的“流管理与控制阶段”,是客户端向服务器发起的一系列 应用层命令,用于 清理旧流、创建新流、检测带宽 等关键操作,为后续音视频传输铺路。以下逐一分解:
1. Set Chunk Size (60000)
:优化传输效率
- 类型:RTMP 控制消息(
Type ID = 0x01
),Chunk Stream ID = 2
(控制流)。 - 作用:
确认服务器的分块大小设置(将 RTMP 分块大小设为 60000 字节),大幅提升大流量传输效率(默认分块 128 字节,大分块减少协议头部开销)。
2. releaseStream('livestream')
:清理残留流
- 类型:AMF0 命令(
Type ID = 0x14
),Chunk Stream ID = 3
(命令流)。 - 结构:
[命令名: "releaseStream", 事务ID: 2, 参数: [Null, "livestream"]]
- 作用:
释放服务器上已存在的livestream
流(避免重名冲突,清理历史残留资源)。
3. FCublish('livestream')
:声明发布类型
- 类型:AMF0 命令(
Type ID = 0x14
),Chunk Stream ID = 3
。 - 结构:
[命令名: "FCublish", 事务ID: 3, 参数: [Null, "livestream"]]
- 作用:
告知服务器:即将以 “文件式发布”(File Publish) 模式推流(区别于实时流的Publish
,常用于预存内容传输)。
4. createStream()
:创建新流实例
- 类型:AMF0 命令(
Type ID = 0x14
),Chunk Stream ID = 3
。 - 结构:
[命令名: "createStream", 事务ID: 4, 参数: [Null]]
- 作用:
请求服务器 创建新的媒体流实例,服务器会返回唯一 流ID(如1
),后续音视频数据将通过该流传输。
5. _checkbw()
(重复多次):动态带宽检测
- 类型:AMF0 命令(
Type ID = 0x14
),Chunk Stream ID = 3
。 - 结构:
[命令名: "_checkbw", 事务ID: 5, 参数: [Null]]
- 作用:
客户端请求服务器 检测当前连接的带宽能力(服务器可能回发测试数据),用于动态调整推流码率,避免网络拥塞。
协议逻辑:RTMP 流建立的“标准流程”
这些命令遵循 “清理→声明→创建→检测” 的顺序,环环相扣:
- 清理旧流:
releaseStream
确保新流无冲突。 - 声明类型:
FCublish
告知服务器发布模式(文件/实时)。 - 创建载体:
createStream
生成流ID,作为数据传输的“管道”。 - 带宽适配:
_checkbw
动态感知网络,优化传输参数(码率、分块等)。
关键设计细节
Chunk Stream ID
分工:2
专用于 控制消息(分块、带宽),3
专用于 应用命令(流管理),体现 RTMP 的 “逻辑流复用” 设计。
- 事务ID递增:从
2
开始逐次加 1,确保 请求-响应一一对应(服务器回复_result
时携带相同 ID)。
总结:推流前的“筹备战役”
这组报文是 RTMP 推流的“前置条件”:
- 优化传输(大分块提升效率);
- 清理冲突(旧流释放);
- 明确意图(发布类型);
- 创建载体(新流ID);
- 适配网络(带宽检测)。
所有步骤完成后,客户端才会执行 publish
推流 或 play
拉流,正式进入音视频传输阶段。每个命令都是流稳定运行的基石。
3.9 _result
报文
_result
响应(关联 releaseStream
命令)
1. RTMP 头部解析
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 0 | 完整 Chunk 头(首次回复 releaseStream 命令,携带所有字段)。 | |
Chunk Stream ID | 3 | 命令流(与客户端的 releaseStream 命令同流,保持会话关联)。 | |
Timestamp | 0 | 响应无时间戳(控制消息类响应,置 0)。 | |
Body size | 21 | 消息体长度(21 字节,AMF0 编码的响应数据)。 | |
Type ID | 0x14 (十进制 20 ) | 消息类型:AMF0 Command (应用层响应,如 _result )。 | |
Stream ID | 0 | 全局流(命令阶段,尚未关联具体媒体流)。 |
2. RTMP 消息体(AMF0 编码)
按 “响应标识 + 事务ID + 结果参数” 结构解析:
- 响应标识:
String '_result'
(AMF0 字符串,长度 7)→ 告知客户端:“这是命令的响应”。 - 事务ID:
Number 2
(AMF0 数值)→ 与客户端releaseStream
命令的事务ID(2
)严格匹配,确保 请求-响应一一对应。 - 结果参数:
Null
(AMF0 空值)→ 占位参数。Object
(可选,图中未完全展开)→ 包含具体结果(如code
:NetStream.Release.Success
表示成功,或NetStream.Release.Failed
表示失败)。
协议逻辑:“命令-响应”闭环
-
关联客户端命令:
此_result
响应对应客户端在 Frame 113 发送的releaseStream('livestream')
命令(通过Call for this response in frame: 113
关联)。 -
核心作用:
服务器告知客户端:“你请求释放的livestream
流,处理结果是成功/失败”。- 若成功:客户端可安全创建新流(如
createStream
),避免重名冲突。 - 若失败:客户端需处理错误(如流不存在、权限不足)。
- 若成功:客户端可安全创建新流(如
RTMP 命令-响应机制的关键
- 事务ID绑定:每个命令(如
releaseStream
、createStream
)都有唯一事务ID,响应通过ID关联,确保流程有序。 _result
通用响应:RTMP 用_result
统一回复命令,区别于错误响应_error
(后者携带具体错误码)。
总结:流资源释放的“确认函”
这个报文是 RTMP 流管理的关键反馈:
- TCP 层用
PSH+ACK
保证响应及时、可靠; - RTMP 层通过
_result
和事务ID,明确releaseStream
命令的执行结果; - 只有收到成功响应,客户端才会继续执行 创建新流(
createStream
)、推流(publish
) 等后续操作,确保流资源干净、无冲突。
若响应为失败,需排查流是否存在、权限是否充足等问题。
3.10 多个_result
报文
这是 RTMP 协议中服务器对客户端多个命令的批量响应(_result
报文),对应之前客户端发送的 releaseStream
、FCublish
、createStream
等命令,标志 流管理阶段的关键确认。以下分层解析:
报文核心特征:“命令-响应”的批量闭环
1. 关联关系:
每个 _result
报文通过 Call for this response in frame: 113
关联到客户端在 Frame 113 发送的命令(releaseStream
、FCublish
、createStream
等)。
2. 事务ID匹配:
RTMP 通过 事务ID(Number
字段) 绑定请求和响应:
- 事务ID=2 → 对应
releaseStream('livestream')
命令; - 事务ID=3 → 对应
FCublish('livestream')
命令; - 事务ID=4 → 对应
createStream()
命令。
单个 _result
报文结构(以事务ID=2为例)
1. RTMP 头部
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 0 | 完整 Chunk 头(首条响应,携带所有字段)。 | |
Chunk Stream ID | 3 | 命令流(与客户端命令同流,保持会话关联)。 | |
Timestamp | 0 | 响应无时间戳(控制类响应,置 0)。 | |
Body size | 21 (示例) | 消息体长度(AMF0 编码数据大小)。 | |
Type ID | 0x14 (AMF0 Command) | 响应类型:告知客户端这是命令的结果反馈。 | |
Stream ID | 0 | 全局流(命令阶段,未关联具体媒体流)。 |
2. RTMP 消息体(AMF0 编码)
String '_result' // 响应标识:“这是命令结果”
Number 2 // 事务ID:匹配客户端的 releaseStream 命令(事务ID=2)
Null // 占位参数
Object { // 结果数据(示例,实际含 code、description 等)code: "NetStream.Release.Success", description: "Stream released successfully"
}
协议逻辑:多命令的“结果确认链”
-
releaseStream
响应(事务ID=2):- 若成功(
code=NetStream.Release.Success
):旧流livestream
已释放,无重名冲突。 - 若失败(
code=NetStream.Release.Failed
):需排查流是否存在、权限问题。
- 若成功(
-
FCublish
响应(事务ID=3):- 若成功(
code=NetStream.Publish.Start
):服务器认可“文件式发布”模式,准备接收数据。 - 若失败(
code=NetStream.Publish.Denied
):可能因发布模式不支持、权限不足。
- 若成功(
-
createStream
响应(事务ID=4):- 若成功:服务器返回 新流ID(如
Number: 1
,隐藏在 Object 中),客户端后续通过该 ID 推流。 - 若失败:需检查服务器资源(如流数量上限)。
- 若成功:服务器返回 新流ID(如
整体流程意义:推流前的“安全检查”
这些 _result
报文是 RTMP 推流的“通行证”:
- 确保 旧流已清理(
releaseStream
成功); - 确保 发布模式被认可(
FCublish
成功); - 确保 新流已创建(
createStream
成功,拿到流ID)。
只有全部响应成功,客户端才会执行 publish
推流 或 play
拉流,正式进入音视频传输阶段。
总结:“批量确认,流程推进”
这组报文体现 RTMP “命令-响应”的严谨性:
- 每个命令通过事务ID精准绑定响应,避免流程混乱;
- 服务器批量回复提升效率,客户端快速判断前序操作是否合规;
- 是流管理阶段的 “最终检查点”,决定后续音视频传输能否启动。
若某条响应失败,客户端需终止流程并报错(如流冲突、权限不足)。
3.11 publish报文
RTMP 头部(RTMP Header)解析
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 0 | 完整 Chunk 头(首次向新流发送命令,携带所有字段)。 | |
Chunk Stream ID | 8 | 媒体流ID(区别于之前的控制流2 、命令流3 ,专用於音视频数据传输)。 | |
Timestamp | 0 | 推流起始时间戳(初始化为0,后续帧会递增)。 | |
Body size | 40 | 消息体长度(40字节,AMF0编码的命令参数)。 | |
Type ID | 0x14 (十进制 20 ) | 消息类型:AMF0 Command (应用层推流命令)。 | |
Stream ID | 0 | 全局流(publish 阶段,尚未完全关联具体流,后续通过 Chunk Stream ID 区分)。 |
RTMP 消息体(AMF0 编码)解析
按 “命令名 + 事务ID + 参数” 结构拆解:
1. 命令名:publish
- AMF0 类型:
String (0x02)
,长度7
。 - 作用:告知服务器:“执行推流命令”。
2. 事务ID:6
- AMF0 类型:
Number (0x00)
,值6
。 - 作用:唯一标识此命令,服务器回复
_result
时需携带相同 ID,确保 请求-响应对应。
3. 参数(3个字段)
参数索引 | 类型 | 内容/解析 | 作用 | |
---|---|---|---|---|
1 | Null (0x05) | 占位符 | RTMP 命令格式要求,无实际意义。 | |
2 | String (0x02) | livestream ,长度 10 | 推流的目标流名称(与之前 releaseStream 、FCublish 一致,确保流名统一)。 | |
3 | String (0x02) | live ,长度 4 | 发布类型: - live :实时流(边推边播,不存储);- record :录制流(存储到服务器);- append :追加到已有录制流。 |
协议逻辑:推流的“启动信号”
-
阶段定位:
发生在createStream
成功响应后(已创建新流,拿到流ID),是 从“流管理”到“数据传输”的转折点。 -
对服务器的要求:
服务器需验证:- 流名称
livestream
是否可用(已通过releaseStream
清理,通常合法); - 发布类型
live
是否支持(服务器配置是否允许实时推流); - 客户端权限(是否允许推流到该应用)。
- 流名称
-
后续流程:
服务器返回_result
响应(事务ID=6):- 若成功(
code=NetStream.Publish.Start
):客户端开始发送 音视频数据(视频帧、音频帧,通过Chunk Stream ID=8
传输)。 - 若失败(
code=NetStream.Publish.Denied
):推流终止,需排查权限、流冲突等问题。
- 若成功(
关键设计细节
Chunk Stream ID=8
的意义:
专用於 媒体流传输,与控制流(2
)、命令流(3
)分离,实现 RTMP 多路复用(控制、命令、媒体数据并行传输,互不阻塞)。- 发布类型
live
的影响:
决定服务器如何处理数据(实时转发给观众,或同时录制),是推流行为的核心配置。
总结:推流的“发令枪”
这个报文是 RTMP 推流的启动指令,承载:
- 目标流名(
livestream
)和 发布类型(live
),明确推流规则; - 通过事务ID(
6
)关联响应,确保流程可控; - 切换到专用媒体流(
Chunk Stream ID=8
),为后续音视频数据传输铺路。
它标志着 从“协议协商”到“实际数据传输”的跨越,是直播/点播流程的核心转折点。
3.12 onFCublish 报文
这是 RTMP 协议中服务器向客户端发送的 onFCublish
异步事件报文,用于 实时通知推流状态变化(此处为“推流成功启动”)
RTMP 头部(RTMP Header)解析
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 0 | 完整 Chunk 头(首次发送此事件,携带所有字段)。 | |
Chunk Stream ID | 5 | 服务器事件流ID(专用於异步事件通知,区别于客户端的命令流/媒体流)。 | |
Timestamp | 0 | 事件类消息无时间戳(或置初始值),聚焦状态通知。 | |
Body size | 40 (实际按数据计算,示例中体部长度对应) | 消息体长度(AMF0 编码的事件参数)。 | |
Type ID | 0x14 (十进制 20 ) | 消息类型:AMF0 Command (应用层事件通知,本质是服务器主动发送的命令)。 | |
Stream ID | 1 | 关联到之前 createStream 创建的 媒体流ID(标识此事件属于哪个流)。 |
RTMP 消息体(AMF0 编码)解析
按 “事件名 + 事务ID + 参数” 结构拆解:
1. 事件名:onFCublish
- AMF0 类型:
String (0x02)
,长度11
。 - 作用:告知客户端:“触发了文件式发布(FCublish)的状态变化事件”(对应之前客户端的
FCublish
命令)。
2. 事务ID:0
- AMF0 类型:
Number (0x00)
,值0
。 - 作用:因是 服务器主动发起的异步事件(非客户端请求的响应),无对应事务ID,故置
0
。
3. 参数(3个字段)
参数索引 | 类型 | 内容/解析 | 作用 |
---|---|---|---|
1 | Null (0x05) | 占位符 | RTMP 事件格式要求,无实际意义。 |
2 | Object (0x03) | 包含 code 和 description 两个键值对 | 具体事件内容,反馈推流状态: |
- code | String 'NetStream.Publish.Start' (长度 22 ) | 事件码:推流成功启动(核心状态标识)。 | |
- description | String 'Started publishing stream.' (长度 27 ) | 描述信息:补充状态细节(如“开始推流”)。 |
协议逻辑:“异步事件通知”的设计
-
与
_result
的区别:_result
:同步响应(客户端发命令→服务器回结果,一一对应)。onFCublish
:异步事件(服务器主动推送状态变化,无需客户端请求)。
→ 前者是“请求-响应”,后者是“状态订阅”,更高效实时。
-
事件的触发条件:
当客户端publish
命令成功执行(流创建、权限验证通过),服务器 主动触发onFCublish
事件,告知客户端“推流已启动”。 -
对客户端的要求:
客户端需 预先注册事件监听器(如onFCublish
、onStatus
),才能接收并处理此类异步通知。
上下文关联:推流流程的“关键信号”
- 前序动作:客户端发送
publish('livestream', 'live')
命令,请求推流。 - 后续动作:
- 客户端收到
onFCublish
后,立即开始 发送音视频数据(视频帧、音频帧,通过Chunk Stream ID=8
传输); - 服务器同时开始 转发数据给观众(或存储,依发布类型
live
/record
决定)。
- 客户端收到
总结:推流的“实时状态灯”
这个报文是 RTMP 异步事件驱动设计 的体现:
- 通过
onFCublish
主动通知推流启动,无需客户端轮询,提升响应速度; - 事件体中的
code
和description
明确状态(成功启动),指导客户端下一步操作(发媒体数据); - 标志 从“命令交互”到“数据传输”的无缝衔接,是直播流程中“推流正式开始”的核心信号。
若事件码为 NetStream.Publish.Failed
,则需排查权限、流冲突等问题。
3.13 Audio Data 报文
RTMP 头部(RTMP Header)解析
字段 | 值/解析 | 作用 |
---|---|---|
Format | 2 | 增量 Chunk 头:复用前一个音频 Chunk 的头部参数(如 Chunk Stream ID 、Type ID ),仅更新 时间戳增量 和 体部长度,大幅减少头部开销。 |
Chunk Stream ID | 4 | 音频专属流ID(RTMP 多路复用设计,音频、视频、数据通常用不同 Chunk Stream ID,如音频=4,视频=5,避免相互阻塞)。 |
Timestamp delta | 23 | 相对于前一个音频 Chunk 的时间戳增量(毫秒),累计时间戳 141ms (通过 Timestamp: 141 (calculated) 体现)。 |
Body size | 隐含在体部(对应音频帧长度) | 音频帧的实际字节数(如示例中体部数据长度)。 |
Type ID | 隐含(因 Format=2,复用前序 Type ID,通常为 0x08 表示 Audio Message) | 消息类型:音频数据(无需显式传输,通过 Chunk Stream ID 和上下文推断)。 |
RTMP 体部(Audio Data)解析
1. 音频格式标识(Control 字段)
- 值:
0xaf
(二进制10101111
) - 解析:
- 高 4 位(
1010
):表示 AAC 编码(RTMP 音频格式中,0x0A
对应 AAC)。 - 低 4 位(
1111
):表示 原始 AAC 数据帧(而非 AAC 序列头,序列头通常在推流起始阶段发送,标识编码参数如采样率、声道数)。
- 高 4 位(
2. 音频数据内容
- 示例数据:
01211004608c1c...
- 本质:HE-AAC 编码的音频帧(结合
0xaf
和实际推流场景,推测为高效的 HE-AAC 格式,适应低带宽直播)。 - 时间关联:时间戳增量
23ms
符合 AAC 帧时长特性(44.1kHz 采样率下,一帧 1024 采样点,时长约23.2ms
,与23ms
接近,验证为一帧完整音频数据)。
协议逻辑:音频流的“高效传输”
-
阶段定位:
发生在publish
命令成功 +onFCublish
事件接收后,属于 实际音视频数据传输阶段(推流的核心环节)。 -
RTMP 多路复用的体现:
- 音频(Chunk Stream ID=4)、视频(假设 ID=5)、控制命令(ID=3)并行传输,互不干扰。
- Format=2 的增量 Chunk 头,让连续音频帧的头部开销降到最低(仅需传输时间戳增量和数据,无需重复发送 Type、Stream ID 等固定参数)。
-
与 TCP 的协作:
- TCP 的
PSH + ACK
保证音频帧 及时交付(PSH 让数据立即入队,ACK 保证不丢包)。 - RTMP 的 Chunk 拆分机制,适配 TCP MSS(避免 IP 分片,提升传输效率)。
- TCP 的
关键细节:音频编码与流控
- HE-AAC 选择的原因:
高效压缩,适合低带宽直播场景(如移动网络),牺牲少量音质换取更高码率适配性。 - 序列头的隐含前提:
推流起始阶段必然发送过 AAC 序列头(Type ID=0x00,携带Audio Specific Config
),包含采样率、声道数、profile 等参数,后续帧(Type ID=0x08)无需重复发送。
总结:直播音频的“载体帧”
这个报文是 RTMP 推流中音频数据的最小传输单元:
- 通过 增量 Chunk 头 优化传输效率,适合连续音频流;
- 采用 HE-AAC 编码 适配低带宽,时间戳增量验证帧完整性;
- 标志推流进入 “实际数据传输” 的稳定阶段,是观众听到声音的直接来源。
若音频帧丢失或乱序,会导致直播声音卡顿、失真,体现 RTMP 与 TCP 协同保障媒体流稳定的重要性。
3.14 Video Data 报文
RTMP 头部(RTMP Header)解析
字段 | 值/解析 | 作用 |
---|---|---|
Format | 1 | 增量 Chunk 头:复用前一视频 Chunk 的固定参数(如 Type ID 、Stream ID ),仅更新 时间戳增量 和 体部长度,大幅降低头部开销(连续视频帧的高效传输关键)。 |
Chunk Stream ID | 6 | 视频专属流ID(RTMP 多路复用设计,与音频流、控制流分离,避免相互阻塞,确保视频帧独立传输)。 |
Timestamp delta | 20 | 相对于前一个视频 Chunk 的时间戳增量(毫秒),累计时间戳 120ms (通过 Timestamp: 120 (calculated) 体现,保证视频帧时序正确)。 |
Body size | 242 | 视频帧的实际字节数(H.264 编码的单帧数据大小)。 |
Type ID | 0x09 (隐含,因 Format=1 复用前序 Type ID,显式标识为 Video Data) | 消息类型:视频数据(推流阶段的核心负载)。 |
RTMP 体部(Video Data)解析
1. 视频控制字段(Control Byte)
- 值:
0x27
(二进制00100111
) - 分解解析:
- 高 4 位(
0010
):表示 帧类型为 Inter-frame(P 帧)(RTMP 视频类型定义:0x1
=关键帧/I 帧,0x2
=预测帧/P 帧,此处0010
对应 P 帧,依赖前一帧解码,压缩比高)。 - 低 4 位(
0111
):表示 编码格式为 H.264(RTMP 编码类型定义:0x07
对应 H.264,是直播场景的主流编码)。
- 高 4 位(
2. H.264 视频数据内容
- 示例数据:
010000104000000e9419f5145152c7f00000030000003000000300000030000003...
- 本质:H.264 的 NAL 单元(Network Abstraction Layer),属于 P 帧数据(帧间压缩,依赖前一帧重构画面)。
- 依赖前提:推流起始阶段必然发送过 H.264 序列头(SPS/PPS)(Video Data 类型为
0x17
,携带编码参数如分辨率、帧率、码率,后续 P 帧无需重复发送配置)。
协议逻辑:视频流的“高效传输策略”
-
阶段定位:
发生在publish
命令成功 +onFCublish
事件接收后,属于 实际音视频数据持续传输阶段(推流的核心业务环节)。 -
RTMP 多路复用的极致体现:
- 视频(Chunk Stream ID=6)、音频(如 ID=4)、控制命令(ID=3)并行独立传输,即使某一流阻塞(如网络抖动),不影响其他流(如控制命令仍可交互)。
- Format=1 的增量 Chunk 头:连续视频帧仅需传输
时间戳增量
和数据
,头部开销从完整头的 ~12 字节降至 ~4 字节,提升传输效率 60%+。
-
与 H.264 编码的协同:
- I 帧(关键帧):间隔发送(如每 2 秒),携带完整画面信息,确保解码起点。
- P 帧(预测帧):连续发送,仅记录与前帧的差异,大幅降低带宽消耗(适合直播低延迟、高帧率场景)。
关键细节:视频传输的“稳定性保障”
- 时间戳增量的意义:
20ms
对应 50fps 帧率(1000ms/20ms=50),符合直播常见帧率(如 25/30/50fps),保证画面流畅性。 - TCP 与 RTMP 的适配:
- 视频帧大小
242 字节
< TCP MSS(通常 1460 字节),避免 IP 分片,降低网络层处理开销。 - RTMP Chunk 拆分机制自动适配 TCP 传输,无需应用层手动分片。
- 视频帧大小
总结:直播视频的“最小传输单元”
这个报文是 RTMP 推流中视频数据的核心载体:
- 通过 增量 Chunk 头 实现高效传输,适配连续视频流的高频率发送;
- 采用 H.264 编码的 P 帧,平衡带宽与画质,支撑实时直播;
- 依托 多路复用 设计,确保视频流与其他流(音频、控制)并行稳定传输。
若视频帧丢失或时间戳紊乱,会导致直播画面卡顿、花屏,体现 RTMP 与 TCP 协同保障媒体流时序和完整性的关键作用。
3.15 FCUnpublish、deleteStream报文
报文 1:FCUnpublish('livestream')
(取消文件式发布)
1. RTMP 头部
字段 | 值/解析 | 作用 |
---|---|---|
Format | 1 | 增量 Chunk 头(复用命令流 Chunk Stream ID=3 的前序参数,仅更新事务ID)。 |
Chunk Stream ID | 3 | 命令流(与 createStream 、publish 等命令复用同一逻辑流,保证会话关联)。 |
Timestamp delta | 0 | 命令连续发送,时间戳增量为 0(相对时间无变化)。 |
Body size | 37 | 消息体长度(AMF0 编码的命令参数)。 |
Type ID | 0x14 (AMF0 Command) | 命令类型:告知服务器执行“取消文件式发布”操作。 |
2. RTMP 消息体(AMF0 编码)
String 'FCUnpublish' // 命令名:取消之前的 FCublish 操作
Number 7 // 事务ID:唯一标识此命令(与响应 `_result` 关联)
Null // 占位参数
String 'livestream' // 目标流名:与之前 publish 的流名一致
作用:告知服务器 停止对 livestream
的文件式发布(如停止录制或存储),释放相关资源。
报文 2:deleteStream(1)
(删除流实例)
1. RTMP 头部
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 1 | 增量 Chunk 头(复用命令流 Chunk Stream ID=3 的参数,连续命令优化)。 | |
Chunk Stream ID | 3 | 命令流(延续会话,保证操作连贯性)。 | |
Timestamp delta | 0 | 命令连续发送,时间戳增量为 0。 | |
Body size | 34 | 消息体长度(AMF0 编码的命令参数)。 | |
Type ID | 0x14 (AMF0 Command) | 命令类型:告知服务器执行“删除流实例”操作。 |
2. RTMP 消息体(AMF0 编码)
String 'deleteStream' // 命令名:删除流实例
Number 8 // 事务ID:唯一标识此命令(与响应 `_result` 关联)
Null // 占位参数
Number 1 // 流ID:对应之前 `createStream` 响应的流ID(如 1)
作用:请求服务器 删除 ID=1 的流实例(推流阶段创建的媒体流),彻底清理资源(避免残留流占用内存)。
协议逻辑:流生命周期的“收尾动作”
-
阶段定位:
发生在 推流结束后(如直播停止、客户端断开前),属于 流销毁阶段,与“创建→发布→销毁”的生命周期闭环对应。 -
操作顺序的必要性:
- 先
FCUnpublish
:停止文件式发布的副作用(如录制),确保数据不再写入存储。 - 再
deleteStream
:删除流实例,释放服务器内存、端口等资源。
→ 若顺序颠倒,可能导致发布状态残留或流资源无法彻底释放。
- 先
-
与服务器响应的关联:
服务器会针对每个命令返回_result
响应(事务ID=7 和 8),告知操作是否成功:- 成功:资源彻底释放;
- 失败:需排查(如流已被删除、权限不足)。
关键设计细节
- 流ID的强关联:
deleteStream
的流ID(1
)必须是createStream
响应中分配的ID,确保精准删除目标流。 - 命令流的复用:
Chunk Stream ID=3
贯穿所有应用层命令(create、publish、unpublish、delete),体现 RTMP “逻辑流复用” 的高效设计。
总结:流资源的“优雅回收”
这两个报文是 RTMP 推流的“收尾关键”:
- 通过
FCUnpublish
终止发布行为,切断数据写入; - 通过
deleteStream
销毁流实例,释放服务器资源; - 形成 “创建→发布→销毁” 的完整生命周期,避免资源泄漏,保障服务器长期稳定运行。
若缺少这些步骤,服务器可能残留无效流,导致资源耗尽或新流冲突,体现 RTMP 协议对“资源管理”的严谨性。
3.16 onFCUnpublish 报文
RTMP 头部(RTMP Header)解析
字段 | 值/解析 | 作用 | |
---|---|---|---|
Format | 0 | 完整 Chunk 头(首次发送此事件,携带所有核心字段,确保客户端完整解析)。 | |
Chunk Stream ID | 5 | 服务器事件专属流ID(与 onFCublish 同属事件流,区分命令流、媒体流)。 | |
Timestamp | 0 | 事件类消息无实际时间戳(聚焦状态通知,置初始值)。 | |
Body size | 105 | 消息体长度(AMF0 编码的事件参数总大小)。 | |
Type ID | 0x14 (十进制 20 ) | 消息类型:AMF0 Command (服务器主动发起的事件通知,本质是应用层命令)。 | |
Stream ID | 1 | 关联到 之前创建的媒体流ID(如 createStream 响应的流ID=1),明确事件所属流。 |
RTMP 消息体(AMF0 编码)解析
按 “事件名 + 事务ID + 参数” 结构拆解:
1. 事件名:onFCUnpublish
- AMF0 类型:
String (0x02)
,长度13
。 - 作用:告知客户端:“触发了‘取消文件式发布’的状态变化事件”(对应客户端的
FCUnpublish
命令)。
2. 事务ID:0
- AMF0 类型:
Number (0x00)
,值0
。 - 作用:因是 服务器主动发起的异步事件(非客户端请求的响应),无对应事务ID,故置
0
(区别于_result
的事务ID关联)。
3. 参数(3个字段)
参数索引 | 类型 | 内容/解析 | 作用 |
---|---|---|---|
1 | Null (0x05) | 占位符 | RTMP 事件格式要求,无实际意义。 |
2 | Object (0x03) | 包含 code 和 description 两个键值对 | 具体事件内容,反馈“取消发布”的结果: |
- code | String 'NetStream.Unpublish.Success' (长度 26 ) | 事件码:取消发布成功(核心状态标识,表明服务器已停止文件式发布逻辑)。 | |
- description | String 'Stop publishing stream.' (长度 20 ) | 描述信息:补充状态细节(如“已停止推流发布”)。 |
协议逻辑:“取消发布”的闭环确认
-
与客户端命令的关联:
此事件对应客户端在 Frame 14050 发送的FCUnpublish('livestream')
命令(通过流ID和事件名关联)。 -
核心作用:
服务器告知客户端:“你请求取消的文件式发布操作已成功执行”,包括:- 停止对
livestream
的录制/存储(若之前是record
类型); - 释放发布相关的临时资源(如缓冲区、编码器上下文)。
- 停止对
-
对后续流程的影响:
客户端收到此事件后,才会发起deleteStream
命令(删除流实例),确保:- 先“停止发布行为”,再“销毁流载体”,避免资源泄漏或状态紊乱。
RTMP 异步事件的设计价值
- 实时性:服务器主动推送状态,无需客户端轮询,降低延迟。
- 解耦性:命令(
FCUnpublish
)和事件(onFCUnpublish
)分离,客户端只需监听事件即可响应状态变化,代码更简洁。 - 幂等性保障:事件码
NetStream.Unpublish.Success
确保操作仅成功一次,避免重复取消导致的异常。
总结:流销毁的“第一步确认”
这个报文是 RTMP 流生命周期管理的关键环节:
- 标志 “发布状态”的干净清除(停止录制、释放临时资源);
- 通过异步事件机制,实现 命令-反馈的高效协同;
- 为后续
deleteStream
命令(销毁流实例) 奠定基础,保障服务器资源彻底回收。
若事件码为 NetStream.Unpublish.Failed
,需排查流是否已被删除、权限问题等,确保流销毁流程的完整性。
四、TCP 关闭
这里是强制关闭的,因此没有TCP的四次挥手
这是 客户端主动发送的 TCP 连接复位报文(RST + ACK
),用于 强制终止 RTMP 会话的底层 TCP 连接,标志 整个推流流程的最终收尾。
TCP 头部核心字段解析
字段 | 值/解析 | 作用 |
---|---|---|
源端口/目的端口 | 61319 → 1935 | 延续 RTMP 会话(RTMP 默认端口 1935 ,客户端临时端口 61319 )。 |
标志位(Flags) | 0x014 (RST + ACK ) | - ACK :确认服务器之前的报文(保证 TCP 可靠性,回应最后一个 ACK);- RST :强制复位连接(无需协商,立即关闭,丢弃缓冲区数据)。 |
序列号(Seq) | 7897823 (相对值) | 客户端发送的当前数据序号(延续会话的序列上下文)。 |
确认号(Ack) | 4507 (相对值) | 确认服务器发送的最后一个数据(Ack = 服务器最后 Seq + 1 ,保证数据接收完整)。 |
协议逻辑:“强制断开”的场景
-
RTMP 会话的收尾阶段:
前序报文已完成FCUnpublish
(取消发布)→onFCUnpublish
(确认取消) 流程,流资源已逻辑释放,此时需要 物理断开 TCP 连接,彻底释放网络资源。 -
RST
的触发原因:- 正常收尾:客户端推流结束,选择 快速断开(相比
FIN
优雅关闭,RST
更高效,适合流媒体这种“一次性会话”场景)。 - 异常保护:若客户端检测到连接异常(如服务器无响应、本地资源不足),也会发
RST
强制断开,避免资源泄漏。
- 正常收尾:客户端推流结束,选择 快速断开(相比
-
与
FIN
的区别:FIN
:优雅关闭(双方协商,逐步释放资源,确保数据完整);RST
:强制关闭(无需协商,立即终止,可能丢弃未发送数据)。
→ 流媒体场景中,因数据多为实时性(丢失末尾数据影响小),常用RST
快速断开。
RTMP 上下文的关联意义
- 前序流程完整:从
connect
连接 →publish
推流 →FCUnpublish
取消 →onFCUnpublish
确认,已完成 “连接→推流→销毁→确认” 的完整生命周期。 - TCP 是 RTMP 的载体:RTMP 依赖 TCP 传输,
RST
断开 TCP 后,RTMP 会话彻底终止,服务器会回收所有关联资源(流、端口、缓冲区等)。
总结:“会话的最终终止符”
这个报文是 RTMP 推流会话的“物理断开信号”:
- 通过
RST + ACK
强制终止 TCP 连接,快速释放网络资源; - 标志整个 “连接→推流→销毁” 流程的彻底结束,客户端和服务器不再维持会话状态;
- 体现 TCP 协议的 “健壮性”(允许强制断开应对异常)和 RTMP 应用的“高效性”(选择快速断开适配流媒体场景)。
若在推流过程中发送 RST
,则可能是异常中断;此处因前序资源释放完整,属于 正常收尾的高效实现。
更多资料:https://github.com/0voice