RTMP协议基础:协议原理、握手流程、消息类型与推流地址结构

RTMP(Real-Time Messaging Protocol,实时消息传输协议)是Adobe公司于2002年推出的应用层协议,基于TCP传输,默认端口为1935。它采用长连接方式,在推流端与服务器之间建立一条持久通道,以块(Chunk)为基本单位连续传输音视频数据。理解RTMP的底层机制,是后续调整推流参数、排查卡顿与延迟问题的前提。

2.1 RTMP协议核心原理

RTMP本质上是一个“消息分发系统”。推流端将采集到的音频、视频帧封装为消息(Message),消息再被切割成更小的块(Chunk)通过TCP发送。服务器收到后重组消息,再转发给播放端或转封装为其他协议(如HLS)。这一设计有两个关键优势:

  • 多路复用:一条TCP连接上可同时传输音频、视频、控制命令等多路消息,通过Chunk Stream ID区分。
  • 低延迟:消息切割为小块后,大视频帧不会阻塞后续音频帧的发送,避免队头阻塞。

RTMP的传输模型遵循“分块-传输-重组”的流水线。每个Chunk包含基本头、消息头与负载数据。基本头中最重要的字段是csid(Chunk Stream ID),用于标识该块属于哪一条流;消息头中则包含时间戳、消息长度与消息类型ID。推流参数中的chunk_size(默认128字节,可协商至4096或更大)直接影响发送效率——块越小,传输越精细但头部开销越大;块越大,吞吐量越高但可能引入轻微延迟。

核心要点:RTMP是TCP之上的长连接协议,以“消息→块”的二级结构实现多路复用与实时传输。推流端与服务器之间始终维持一条双向通道,控制消息与媒体数据交错发送。

推流端 采集 → 编码 封装为消息 切割为Chunk 消息层 (Message) 消息头 + 负载 类型ID / 时间戳 / 长度 块层 (Chunk) 基本头 + 消息头 + 数据 csid / chunk_size TCP传输层 端口1935,长连接 RTMP服务器 重组Chunk为消息 解析消息类型 转发/转封装 封装 切割 发送 网络传输 ACK/控制消息 关键参数: chunk_size 影响吞吐与延迟 csid 区分音视频流 消息类型ID 决定处理方式

2.2 RTMP握手流程

RTMP握手是推流端与服务器建立连接的第一步,采用三次握手机制,共交换6个数据包(C0/C1/C2与S0/S1/S2)。握手的目的在于确认双方协议版本并同步时间戳。握手完成后,推流端才能发送connect命令进入应用层交互。

握手流程如下:

  1. C0 → S0:客户端发送版本号(通常为3),服务器确认版本。
  2. C1 → S1:客户端发送1536字节的随机数据,包含时间戳与随机数。
  3. S2 → C2:服务器回显C1的时间戳与随机数,完成双向验证。
  4. 对称地,服务器也发送S1,客户端回复C2,最终双方各收到对方的C1/S1回显。

在实际推流工具(如OBS、FFmpeg)中,握手过程由库自动完成,但理解其机制有助于排查“连接超时”或“握手失败”类错误。例如,当服务器防火墙屏蔽1935端口或TCP连接被重置时,推流日志会显示Handshake failed。

排障提示:若推流端反复出现“握手失败”,先用telnet 服务器IP 1935测试TCP连通性;若TCP正常,再检查服务器端RTMP应用是否启动、鉴权是否开启。

2.3 RTMP消息类型

RTMP消息通过消息类型ID(Message Type ID)区分用途。推流过程中最常涉及以下几类:

类型ID 消息名称 作用 推流中的典型场景
1 Set Chunk Size 协商块大小 推流开始时设置chunk_size为4096或更大
2 Abort Message 中断消息传输 丢弃不完整的消息
3 Acknowledgement 确认接收字节数 每收到一定字节后回复
4 User Control Message 流事件通知 Stream Begin、Ping等
5 Window Acknowledgement Size 设置确认窗口 控制发送速率
6 Set Peer Bandwidth 设置对端带宽 限制推流码率
8 Audio Message 传输音频数据 AAC编码后的音频帧
9 Video Message 传输视频数据 H.264编码后的视频帧
18 Metadata Message 传输元数据 分辨率、帧率、码率等参数
20 Command Message (AMF0) 发送命令 connect、publish、play等

其中,类型8和9承载实际的音视频数据,是推流的主体;类型20用于执行connect、createStream、publish等命令,完成推流会话的建立;类型18用于发送@setDataFrame,携带编码器信息与视频参数,播放端依赖这些元数据初始化解码器。

2.4 推流地址结构解析

RTMP推流地址通常遵循以下格式:

rtmp://服务器IP:端口/应用名/流密钥?参数1=值1&参数2=值2

以常见的直播平台为例:

rtmp://live.example.com:1935/live/stream_abc123?token=8f3a2b9c
  • rtmp://:协议标识,固定前缀。
  • live.example.com:RTMP服务器域名或IP。
  • 1935:端口号,默认1935,可省略;若使用非默认端口(如8080),必须显式写出。
  • /live:应用名(Application),服务器上配置的挂载点,决定请求由哪个应用处理。
  • stream_abc123:流名称(Stream Key),用于唯一标识一路推流。
  • ?token=...:查询参数,常用于鉴权或传递自定义配置。

在OBS或FFmpeg中,推流地址被拆分为“服务器”和“串流密钥”两部分。例如OBS中:

  • 服务器:rtmp://live.example.com:1935/live
  • 串流密钥:stream_abc123?token=8f3a2b9c

FFmpeg命令行推流时则需拼接完整地址:

ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac -f flv \
  "rtmp://live.example.com:1935/live/stream_abc123?token=8f3a2b9c"

常见误区:部分推流者将“应用名”与“流名称”混淆,导致服务器返回NetStream.Play.StreamNotFound错误。请务必确认:应用名是服务器路径的一部分,流名称是最后一段唯一标识。若平台提供独立的“推流地址”和“推流码”,通常前者包含应用名,后者就是流名称(可能带参数)。

2.5 案例:从握手到推流成功

假设使用FFmpeg向自建SRS服务器推流,完整交互过程如下:

  1. FFmpeg发起TCP连接到192.168.1.100:1935。
  2. 完成RTMP握手(C0/C1/C2与S0/S1/S2交换)。
  3. 发送connect命令(类型20),携带app=live、tcUrl=rtmp://192.168.1.100:1935/live。
  4. 服务器回复_result,确认连接成功。
  5. 发送createStream命令,服务器返回流索引。
  6. 发送publish命令,携带流名称stream_abc123。
  7. 服务器回复onFCPublish与onStatus(NetStream.Publish.Start)。
  8. 开始发送音视频消息(类型8/9)与元数据(类型18)。

若第6步后收到NetStream.Publish.BadName,说明流名称已被占用或鉴权失败;若收到NetConnection.Connect.Rejected,则需检查应用名或token是否正确。

2.6 误区与复盘

误区一:认为RTMP握手需要“先发后收”严格串行。实际上C0/C1可以同时发送,S0/S1也可合并,但C2必须在收到S1后才能发送。部分低质量库实现不当会导致握手超时。

误区二:忽略chunk_size协商。默认128字节的块大小在推高码率视频时会产生大量头部开销,CPU占用率显著上升。建议在连接建立后主动发送Set Chunk Size消息,将块大小提升至4096或更高。

误区三:将推流地址中的查询参数(如?token=)误放入应用名中。参数必须附加在流名称末尾,否则服务器无法正确解析鉴权信息。

复盘一次真实故障:某推流者使用OBS推流,日志显示Connection to rtmp://xxx failed: 连接超时。排查过程:先ping服务器IP正常,再telnet 1935端口失败,确认是防火墙未放行TCP 1935端口。放行后推流成功。此案例说明,RTMP握手虽由协议自动完成,但底层TCP连通性是前提,任何网络层拦截都会导致握手无法开始。