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之上的长连接协议,以“消息→块”的二级结构实现多路复用与实时传输。推流端与服务器之间始终维持一条双向通道,控制消息与媒体数据交错发送。
2.2 RTMP握手流程
RTMP握手是推流端与服务器建立连接的第一步,采用三次握手机制,共交换6个数据包(C0/C1/C2与S0/S1/S2)。握手的目的在于确认双方协议版本并同步时间戳。握手完成后,推流端才能发送connect命令进入应用层交互。
握手流程如下:
- C0 → S0:客户端发送版本号(通常为3),服务器确认版本。
- C1 → S1:客户端发送1536字节的随机数据,包含时间戳与随机数。
- S2 → C2:服务器回显C1的时间戳与随机数,完成双向验证。
- 对称地,服务器也发送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服务器推流,完整交互过程如下:
- FFmpeg发起TCP连接到
192.168.1.100:1935。 - 完成RTMP握手(C0/C1/C2与S0/S1/S2交换)。
- 发送
connect命令(类型20),携带app=live、tcUrl=rtmp://192.168.1.100:1935/live。 - 服务器回复
_result,确认连接成功。 - 发送
createStream命令,服务器返回流索引。 - 发送
publish命令,携带流名称stream_abc123。 - 服务器回复
onFCPublish与onStatus(NetStream.Publish.Start)。 - 开始发送音视频消息(类型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连通性是前提,任何网络层拦截都会导致握手无法开始。