目录1、基本概念2、RTMP创建流2.1、RTMP创建流的基本流程2.2、RTMP握手2.3、建立RTMP连接NetConnection2.4、创建RTMP流NetStream2.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 RTSPRTMP更适合“推流到服务器”进行大规模分发如直播RTSP更多用于点对点或IP摄像头的控制与流媒体播放如安防监控。RTMP vs HLSRTMP延迟更低几秒甚至亚秒级HLS基于HTTP切片延迟较高10秒以上但兼容性极好Web/移动端。2、RTMP创建流2.1、RTMP创建流的基本流程socket建立TCP连接RTMP握手建立RTMP连接NetConnection创建RTMP流NetStream2.2、RTMP握手s0、s1、s2通常作为一个包发送握手由三个固定大小的块组成过程非常简单直接C0 C1: 客户端主动向服务器发送一个数据包其中C0是一个字节标识RTMP版本目前为3C1是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流NetStreamNetStream 代表了真正传输多媒体数据的逻辑通道。所有的音频、视频和元数据都是通过这个通道发送的。创建它的方式推流和拉流有所不同。通用步骤创建 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、传输音视频数据当所有的握手、连接、创建流和发布/播放的命令都成功完成后就进入了最核心的数据传输阶段。客户端推流端开始将经过编码和封装的音频、视频数据按照FLVFlash Video的格式打包成RTMP消息块Chunk源源不断地发送给服务器。服务器收到后会将这些数据块重新组装成消息并转发给所有正在 play 这个流的客户端拉流端。例如一个H.264视频帧会被封装成FLV Tag其中包含帧类型关键帧/普通帧、编码器ID7 for H.264和实际的数据NALU。AAC音频也是类似的流程。通过这样一系列严谨的指令交互RTMP协议实现了可靠、高效的直播推拉流服务。2.8、连接断开1、断开 NetStream客户端发送closeStream命令告诉服务器要关闭当前的流通道。服务器回复onStatus命令确认流已关闭。对于推流端onStatus中的code为NetStream.Unpublish.Success对于拉流端服务器可能不发送onStatus或发送NetStream.Play.Stop2、断开 NetConnection客户端发送 close 命令请求关闭网络连接。服务器回复 _result 命令确认连接已关闭。至此RTMP 应用层的连接完全断开。3、TCP 四次挥手客户端发起 TCP 四次挥手彻底关闭底层的 TCP 连接。两种断开方式的差异客户端主动断开时流程如上图所示先发closeStream再发close收到_result后开始 TCP 挥手。服务器主动断开时服务器直接发送onStatus命令NetConnection.Connect.Closed然后服务器发起 TCP 挥手客户端不需要发送closeStream和close命令。推流端与拉流端断开 NetStream 的差异推流端发送closeStream后服务器会回复onStatusNetStream.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字节 │ └────────────────────────────────────────────────┘ ↓ 分块 块1128字节 块2128字节 块351字节 ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ 块头 128 │ │ 块头 128 │ │ 块头51 │ └─────────────┘ └─────────────┘ └──────────┘每个块在网络上独立传输接收端根据块头中的信息将同一消息的多个块重新组装。3.3、RTMP消息格式Chunk格式RTMP Header由三部分组成基本头信息Basic Header、消息头信息Messgae Header、扩展时间戳Extended Timestamp。其中后两个为可选。3.1.1、基本头信息Basic HeaderBasic Header长度可变由第一个字节的fmt(2位) 和CSID(6位)共同决定CSID编码规则当CSID2-63时Basic Header占1字节当CSID0时Basic Header占2字节实际CSID64第二个字节值当CSID1时Basic Header占4字节实际CSID64256后3字节值3.1.2、消息头信息Messgae Header字段长度说明时间戳3字节消息的时间戳用于音视频同步分割前是4字节分割后是3字节负载长度3字节消息体的字节数消息类型ID1字节标识消息类型8音频9视频20AMF0命令等消息流ID4字节标识所属的消息流Message Header长度由 fmt 值决定fmt00b(0)完整11字节(时间戳3B消息长度3B类型ID 1B流ID 4B)fmt01b(1)7字节(省略流ID)fmt10b(2)3字节(仅保留时间戳)fmt11b(3)0字节(完全省略)省略逻辑基于数据分块传输时的冗余信息消除同一流可省略流ID同一消息可省略类型ID固定长度可省略消息长度时间戳相同可省略时间戳3.1.3、扩展时间戳Extended Timestamp当且仅当 Message Header 中的时间戳值为0xFFFFFF时启用作用:当3字节时间戳不足以表示实际时间值时提供扩展支持3.4、块流Chunk Stream—— 多路复用的核心3.4.1、什么是块流块流是一个虚拟的逻辑通道用块流IDChunk 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。将多个小消息打包成一个减少头部开销提高传输效率