目录
1、基本概念
2、RTMP创建流
2.1、RTMP创建流的基本流程
2.2、RTMP握手
2.3、建立RTMP连接(NetConnection)
2.4、创建RTMP流(NetStream)
2.5、RTMP推流
2.6、RTMP拉流
2.7、传输音视频数据
2.8、连接断开
2.9、RTMP处理流程
3、RTMP消息
3.1、消息(Message)—— 逻辑上的数据单元
3.2、块(Chunk)—— 网络传输的最小单元
3.3、RTMP消息格式(Chunk格式)
3.4、块流(Chunk Stream)—— 多路复用的核心
3.5、RTMP消息类型
1、基本概念
RTMP底层是基于TCP的
定位与角色:RTMP是应用层协议,主要用于直播场景中从推流端(如OBS)到服务器(如SRS/Nginx)的传输,以及服务器间分发。
核心特点:基于TCP,提供低延迟的实时传输,默认使用1935端口。优势是生态成熟,缺点是部分移动端和浏览器(Flash已淘汰)原生支持不佳。
与其他协议的对比:
RTMP vs RTSP:RTMP更适合“推流到服务器”进行大规模分发(如直播),RTSP更多用于点对点或IP摄像头的控制与流媒体播放(如安防监控)。
RTMP vs HLS:RTMP延迟更低(几秒甚至亚秒级),HLS基于HTTP切片,延迟较高(10秒以上),但兼容性极好(Web/移动端)。
2、RTMP创建流
2.1、RTMP创建流的基本流程
socket建立TCP连接
RTMP握手
建立RTMP连接(NetConnection)
创建RTMP流(NetStream)
2.2、RTMP握手
s0、s1、s2通常作为一个包发送
握手由三个固定大小的块组成,过程非常简单直接:
C0 + C1: 客户端主动向服务器发送一个数据包,其中C0是一个字节,标识RTMP版本(目前为3),C1是1536字节的随机数据。
S0 + S1 + S2: 服务器收到后,会回复S0(确认版本)和S1(服务器端的随机数据)。紧接着,服务器会基于收到的C1计算出S2,并立即发送给客户端。
C2: 客户端收到S1后,计算出C2并发送给服务器,完成最后的确认。
目的:
1、协商协议版本:通过C0和S0交换版本号,确保双方能互相理解
2、验证连接的有效性:通过来回“打乒乓”的方式,确保对方是真实存在的RTMP端点,而不是无效或恶意的连接。
2.3、建立RTMP连接(NetConnection)
握手完成后,客户端需要建立一个“网络连接”(NetConnection)。可以把它理解为客户端和服务器之间的一个控制通道。这是一个上行链路,所有后续的命令都会通过这个通道发送
客户端发送
connect命令:这是请求建立网络连接(NetConnection)的开始。服务器回复协议控制消息:服务器收到
connect后,会先发送几个RTMP协议层面的控制消息,作为对连接建立的底层确认。这些消息包括:确认窗口大小 (Window Acknowledgement Size)—— 服务端的接收窗口大小
设置带宽 (Set Peer Bandwidth)—— 限制客户端发送速率上限
客户端回复协议控制消息:作为对上一步的回应,客户端会再发送一个确认窗口大小 (Window Acknowledgement Size)消息。—— 客户端的接收窗口大小
服务器发送
_result命令:服务器处理完connect命令后,会发送_result命令(与是否收到客户端的回复协议控制消息无关)。这个命令标志着connect请求在应用层被成功处理,网络连接(NetConnection)正式建立完成。如果connect处理失败,则会发送_error命令
2.4、创建RTMP流(NetStream)
NetStream 代表了真正传输多媒体数据的逻辑通道。所有的音频、视频和元数据都是通过这个通道发送的。创建它的方式,推流和拉流有所不同。
通用步骤:创建 NetStream
无论推流还是拉流,都需要先创建这个流通道。
客户端发送
createStream命令:客户端通过之前建立的控制通道(NetConnection)向服务器发送createStream命令,请求创建一个新的流。服务器回复
_result:服务器处理请求,如果成功,会在_result命令中携带一个Stream ID返回给客户端。这个ID非常重要,它标识了即将进行音视频传输的通道,后续所有的数据包都会使用这个ID进行封装。
推流端的特殊步骤:
releaseStream在发送createStream之前,推流端(publisher)通常会先发送一个releaseStream命令。这个名字很直白——“释放流”。目的是确保要发布的流名没有被之前的会话占用,避免冲突,起到一个“重置/清理”的作用。
2.5、RTMP推流
推流端 (Publisher) 的流程
发送
publish命令:推流端发送publish命令,告诉服务器:“我要开始推流了!” 这个命令会携带流的名称(如mystream)和流的类型(live表示实时直播,record表示录制)。服务器回复
onStatus:服务器收到publish命令后,会开始准备接收数据。准备就绪后,它会发送一个onStatus命令给推流端,其中的code字段为NetStream.Publish.Start,这相当于一个“开始发布”的许可。发送元数据 (
onMetaData):推流端在正式推送音视频数据前,会先发送一个包含onMetaData的setDataFrame命令。这个数据包非常重要,它包含了视频的宽高、编码格式(H.264)、音频的采样率、频道数等描述信息。播放器需要先收到这些信息才能正确解码后续的音视频数据。
2.6、RTMP拉流
发送
play命令:拉流端发送play命令,并附上想观看的流名称(如mystream),请求服务器开始发送该流的数据。服务器回复
onStatus:服务器找到对应的流后,会发送多个onStatus命令来通知播放器状态变化,例如NetStream.Play.Reset(重置播放)和NetStream.Play.Start(开始播放)。接收元数据 (
onMetaData):紧接着,服务器会将推流端发来的那个包含onMetaData的setDataFrame命令原样转发给播放器,告知其音视频的基本属性。
2.7、传输音视频数据
当所有的握手、连接、创建流和发布/播放的命令都成功完成后,就进入了最核心的数据传输阶段。
客户端(推流端)开始将经过编码和封装的音频、视频数据,按照FLV(Flash Video)的格式打包成RTMP消息块(Chunk),源源不断地发送给服务器。服务器收到后,会将这些数据块重新组装成消息,并转发给所有正在 play 这个流的客户端(拉流端)。
例如,一个H.264视频帧会被封装成FLV Tag,其中包含帧类型(关键帧/普通帧)、编码器ID(7 for H.264)和实际的数据(NALU)。AAC音频也是类似的流程。
通过这样一系列严谨的指令交互,RTMP协议实现了可靠、高效的直播推拉流服务。
2.8、连接断开
1、断开 NetStream
客户端发送closeStream命令,告诉服务器要关闭当前的流通道。
服务器回复onStatus命令,确认流已关闭。对于推流端,onStatus中的code为NetStream.Unpublish.Success;对于拉流端,服务器可能不发送onStatus或发送NetStream.Play.Stop
2、断开 NetConnection
客户端发送 close 命令,请求关闭网络连接。
服务器回复 _result 命令,确认连接已关闭。至此,RTMP 应用层的连接完全断开。
3、TCP 四次挥手
客户端发起 TCP 四次挥手,彻底关闭底层的 TCP 连接。
两种断开方式的差异
客户端主动断开时,流程如上图所示:先发closeStream,再发close,收到_result后开始 TCP 挥手。
服务器主动断开时,服务器直接发送onStatus命令(NetConnection.Connect.Closed),然后服务器发起 TCP 挥手,客户端不需要发送closeStream和close命令。
推流端与拉流端断开 NetStream 的差异
推流端发送closeStream后,服务器会回复onStatus(NetStream.Unpublish.Success),明确告知推流端服务器已停止接收数据。
拉流端发送closeStream后,服务器通常不回复onStatus,直接停止发送数据即可,因为拉流端主动断开不需要服务器确认。
2.9、RTMP处理流程
完整通信流程
建立TCP连接
RTMP握手
创建RTMP连接(NetConnection)
创建流(NetStream)
推流/拉流
断开链接
3、RTMP消息
3.1、消息(Message)—— 逻辑上的数据单元
消息是RTMP中应用层看到的基本数据单元。无论是音频数据、视频数据,还是控制命令,在逻辑上都被封装成消息。
一个消息包含以下字段:消息类型ID、负载长度、时间戳、消息流ID、消息体。
3.2、块(Chunk)—— 网络传输的最小单元
消息在真正发送时,会被分割成更小的块。为什么?设想一下:如果一个大的视频帧(比如200KB)直接发送,而它后面紧跟着一个音频帧(只有几十字节),在TCP流式传输中,音频数据必须等视频帧发完才能发,这就会造成音频延迟或卡顿。
分块解决了这个问题:把大消息切成小块,音频小块可以穿插在视频小块之间发送。
默认块大小是128字节,可以通过协议控制消息动态调整。
分块过程示意
假设有一个307字节的视频消息,块大小为128字节,分块过程如下:
原始消息(307字节) ┌────────────────────────────────────────────────┐ │ 307字节 │ └────────────────────────────────────────────────┘ ↓ 分块 块1(128字节) 块2(128字节) 块3(51字节) ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ 块头 + 128 │ │ 块头 + 128 │ │ 块头+51 │ └─────────────┘ └─────────────┘ └──────────┘
每个块在网络上独立传输,接收端根据块头中的信息将同一消息的多个块重新组装。
3.3、RTMP消息格式(Chunk格式)
RTMP Header由三部分组成:基本头信息(Basic Header)、消息头信息(Messgae Header)、扩展时间戳(Extended Timestamp)。其中后两个为可选。
3.1.1、基本头信息(Basic Header):
Basic Header长度可变,由第一个字节的fmt(2位) 和CSID(6位)共同决定
CSID编码规则:
当CSID=2-63时:Basic Header占1字节
当CSID=0时:Basic Header占2字节,实际CSID=64+第二个字节值
当CSID=1时:Basic Header占4字节,实际CSID=64+256+后3字节值
3.1.2、消息头信息(Messgae Header):
| 字段 | 长度 | 说明 |
|---|---|---|
| 时间戳 | 3字节 | 消息的时间戳,用于音视频同步(分割前是4字节,分割后是3字节) |
| 负载长度 | 3字节 | 消息体的字节数 |
| 消息类型ID | 1字节 | 标识消息类型(8=音频,9=视频,20=AMF0命令等) |
| 消息流ID | 4字节 | 标识所属的消息流 |
Message Header长度由 fmt 值决定:
fmt=00b(0):完整11字节(时间戳3B+消息长度3B+类型ID 1B+流ID 4B)
fmt=01b(1):7字节(省略流ID)
fmt=10b(2):3字节(仅保留时间戳)
fmt=11b(3):0字节(完全省略)
省略逻辑:基于数据分块传输时的冗余信息消除:
同一流可省略流ID
同一消息可省略类型ID
固定长度可省略消息长度
时间戳相同可省略时间戳
3.1.3、扩展时间戳(Extended Timestamp):
当且仅当 Message Header 中的时间戳值为0xFFFFFF时启用
作用:当3字节时间戳不足以表示实际时间值时提供扩展支持
3.4、块流(Chunk Stream)—— 多路复用的核心
3.4.1、什么是块流?
块流是一个虚拟的逻辑通道,用块流ID(Chunk Stream ID)标识。多个块流可以复用在同一条TCP连接上。
3.4.2、为什么要多路复用?
在实际直播中,需要同时传输:
视频数据(量大、优先级相对低)
音频数据(量小、优先级高)
控制消息(量极小、优先级最高)
通过将它们放在不同的块流中,可以在网络拥塞时优先发送音频和控制消息的块,确保声音不卡顿、命令及时响应。
3.4.3、块流与消息流的关系
消息流ID:标识逻辑上的消息流(由应用层定义)
块流ID:标识逻辑传输通道
多个不同消息流的块可以复用到同一个块流中,也可以一个消息流独占一个块流。接收端通过块流ID找到对应的块流,再通过消息流ID区分不同的消息,最后将同一个消息的多个块按顺序组装还原。
块流和消息流都是逻辑通道,它们都是在同一条TCP物理连接之上划分的、不同粒度的逻辑抽象。
3.4.4、完整数据流示意
发送端: 消息1(视频,200KB)──→ 分块 ──→ 块1.1, 块1.2, 块1.3... 消息2(音频,2KB) ──→ 分块 ──→ 块2.1 消息3(控制,小) ──→ 分块 ──→ 块3.1 ↓ 复用(交错发送) TCP发送队列:[块2.1] [块3.1] [块1.1] [块1.2] [块2.2] [块1.3]... (音频和控制消息优先) 接收端: 按块流ID分类 → 按消息流ID组装 → 还原原始消息
3.5、RTMP消息类型
Message Header 中的 TypeID 字段,1字节
协议控制消息:1~3,5~6。负责管理数据传输的底层机制,确保传输高效可靠。
用户控制消息:4。如果说协议控制消息是管"管道"的,那用户控制消息就是管"管道里的水"的。它主要用于通知对方关于流(Stream)的状态变化,比如流创建、结束、缓冲区长度等。
数据消息:8,9。传输的音视频数据。
命令消息:15~20。交互命令。比如publish、connect、play等。
聚合消息:22。将多个小消息打包成一个,减少头部开销,提高传输效率