文章目录
RTMP(Real-Time Messaging Protocol)
参考2012年RTMP官网文档:Adobe RTMP Specification · RTMP (veriskope.com)
协议概述
RTMP(Real-Time Messaging Protocol)是 Adobe 开发的实时流媒体传输协议,是一种应用层协议,主要用于音视频推拉流(如直播场景)。它基于 TCP,具有低延迟(通常 1-3 秒),支持多路复用和动态带宽适应。以下是 RTMP 协议的核心结构和交互流程的详细解析。
Adobe 的实时消息传输协议(RTMP)通过可靠的流传输(如 TCP [RFC0793])提供双向消息多路复用服务,用于在一对通信对等体之间传输并行的视频、音频和数据消息以及相关的时间信息。在传输容量受限的情况下,不同类别的消息通常会被赋予不同的优先级,这会影响消息在底层流传输中的排队顺序。
协议特点
- 传输层:基于 TCP(默认端口 1935)。
- 低延迟:适合实时互动场景(如直播连麦)。
- 多路复用:支持多个音视频流、数据流共存。
- 动态分块:将大消息拆分为小块传输(Chunk),避免网络拥塞。
- 支持多种编码:H.264、H.265、AAC、MP3 等。
字节顺序、对齐方式和时间格式
- 字节顺序:所有整数字段均采用网络字节顺序传输,字节 0 为显示的第一个字节,位 0 为字或字段中的最高有效位,即大端序,具体传输顺序在互联网协议 [RFC0791] 中有详细描述。本文档中的数值常量除非另有说明,均为十进制(基数为 10)。
- 对齐方式:除非另有规定,RTMP 中的所有数据均按字节对齐。例如,16 位字段可能位于奇数字节偏移处。需要填充时,填充字节的值应为 0。
- 时间格式:RTMP 中的时间戳为相对于未指定纪元的整数毫秒数。通常,每个流的时间戳从 0 开始,但只要两个端点对纪元达成一致,也可不从 0 开始。由于时间戳为 32 位长,每 49 天 17 小时 2 分 47.296 秒会溢出。因此,RTMP 应用程序在处理时间戳时应使用序列号算法 [RFC1982],并能够处理溢出情况。时间戳增量也以相对于前一个时间戳的无符号整数毫秒数指定,长度可能为 24 位或 32 位。
使用领域
- 在线视频直播:RTMP 被广泛用于在线视频直播平台,如直播体育赛事、音乐会、会议等。它能够实时传输视频和音频流,确保观众可以实时观看直播内容
- 视频点播:通过 RTMP,用户可以点播存储在服务器上的视频内容,实现视频的在线播放。
- 在线教育:在线教育领域,RTMP 可用于实时直播讲座、培训课程或录制的教学视频的播放。
- 视频会议和远程协作:RTMP 协议可以支持视频会议应用,让参与者进行实时的视频交流和共享。
- 游戏直播:游戏玩家可以使用 RTMP 协议将他们的游戏过程实时直播给其他用户观看。
RTMP的优势在于其低延迟和高可靠性,能够在网络条件不稳定的情况下提供相对稳定的流媒体传输。它在实时性要求较高的场景中表现出色,如直播和互动应用。
直播系统全流程解析
- 主播端流程
- 推流URL生成
- 主播点击“开播”时,客户端向腾讯云服务器请求推流地址
- 示例URL:
rtmp://192.168.31.30:1935/live/stream
rtmp://
:实时消息传输协议(低延迟直播首选)192.168.31.30:1935
:服务器IP与默认端口。/live/stream
:流名称(标识直播流的唯一路径)- 表示这是一个「直播」(Live)服务模块,区别于点播(VOD);
- 音视频采集与处理
- SDK功能
- 采集:摄像头(视频)、麦克风(音频)数据捕获。
- 处理:美颜滤镜、分辨率调整、流量控制(网络波动时动态压缩数据量)。
- 传输:通过SDK将处理后的数据按推流URL推送至腾讯云服务器。
- SDK功能
- 推流URL生成
- 腾讯云服务器处理
- 接收主播推流数据(RTMP协议)
- 转码(适配不同终端,如生成HLS格式供移动端播放)
- 分发(多路复用,支持海量观众同时观看)
- 观众端流程
- 播放URL获取
- 操作:观众进入直播间时,客户端代码向腾讯云查询播放地址(可能与推流URL相同,但服务器内部路由不同)。
- 播放优化
- SDK功能:
- 硬件加速解码(GPU提升播放流畅度)。
- 秒开优化(预加载首帧数据减少等待)。
- 自适应码率(根据网络切换高清/标清版本)。
- SDK功能:
- 播放URL获取
RTMP 协议中视频流的 推流 与 拉流 交互逻辑
音视频基于RTMP传输流程
- 在RTMP协议中RTMP Message是协议交互中的基本单位,不管是控制协议、命令协议、数据协议他们都是以RTMP Message进行发送
- 但是在真正发送的时候RTMP Message 会切分为 n个RTMP Chunk,再通过 tcp 协议传输
- RTMPChunk Stream 层级允许在Mesage stream 层次,将大消息切割成小消息,这样可以避免大的低优先级的消息(如视频消息)阻塞小的高优先级的消息(如音频消息或控制消息),
RTMP Chunk如下图所示
TypeID:消息ID
StreamID:流ID,RTMP Message是一条流,有一个ID
定义
- 有效载荷:数据包中包含的数据,如音频样本或压缩视频数据。
- 数据包:由固定头部和有效载荷数据组成。某些底层协议可能需要定义数据包的封装方式。
- 端口:传输协议用于区分给定主机内多个目的地的抽象概念。TCP/IP 协议使用小正整数标识端口。OSI 传输层使用的传输选择器(TSEL)等同于端口。
- 传输地址:网络地址和端口的组合,用于标识传输层端点,如 IP 地址和 TCP 端口。数据包从源传输地址发送到目的传输地址。
- 消息流:消息在其中流动的逻辑通信通道。
- 消息流 ID:每个消息都有一个与之关联的 ID,用于标识其所在的消息流。
- 块:消息的片段。消息在通过网络发送之前会被分解为较小的部分并交错传输,以确保所有消息能按时间戳顺序在多个流中进行端到端的传递。
- 块流:允许块在特定方向上流动的逻辑通信通道,可在客户端和服务器之间双向传输。
- 块流 ID:每个块都有一个与之关联的 ID,用于标识其所在的块流。
- 多路复用:将单独的音频 / 视频数据合并为一个连贯的音频 / 视频流,从而实现多个视频和音频的同时传输。
- 解复用:多路复用的逆过程,将交错的音频和视频数据组装成原始的音频和视频数据。
- 远程过程调用(RPC):允许客户端或服务器调用对等端的子例程或过程的请求。
- 元数据:关于数据的描述。电影的元数据包括电影标题、时长、创建日期等。
- 应用程序实例:服务器上的应用程序实例,客户端通过发送连接请求与之连接。
- 动作消息格式(AMF):一种紧凑的二进制格式,用于序列化 ActionScript 对象图。AMF 有两个版本:AMF 0 [AMF0] 和 AMF 3 [AMF3]。
为什么 rtmp 基于 tcp 协议,tcp 协议已经有化整为零的方式,rtmp 还需要将 message 划分更小的单元 chunk 呢 ?
tcp 协议划分一个个tcp报文,是为了在网络传输层上保障数据连续性,丢包重发等特性,rtmp划分 chunk 消息快,是为了在网络应用层上实现低延迟的特性,分块传输可以减少单个数据包的大小,从而减小了传输延迟,提高了实时性;
RTMP Messages
本节介绍了服务器和客户端之间为实现相互通信而交换的不同类型的消息和命令。
服务器和客户端之间交换的不同类型消息包括:
- 用于发送音频数据的音频消息
- 用于发送视频数据的视频消息
- 用于发送用户数据的数据消息
- 共享对象消息以及命令消息。共享对象消息提供了一种在多个客户端和服务器之间管理分布式数据的通用方式。命令消息则在客户端和服务器之间传输 AMF 编码的命令。客户端或服务器可以通过命令消息在流上请求远程过程调用(RPC),并将其传达给对等方。
传输处理
- 分割与重组:实际传输中,为实现多路复用、分包及不同媒体流消息收发公平性,大的 RTMP Message 会被切分成多个 RTMP Chunk 。每个 Chunk 可能是单独 Message,也可能是 Message 一部分 。接收端依据 Chunk 中 data 长度、message id 和 message 长度等信息,将 Chunk 还原成完整 Message 。
- 交织发送:不同媒体流(如音频流、视频流 )的 Chunk 会交织在一起发送。发送顺序依据时间戳先后,保证来自不同流的 Message 能按时间先后有序传输,防止低优先级大 Message(如视频数据 )阻塞高优先级小 Message(如音频和控制数据 )
rtmp message types
服务器和客户端通过网络发送消息进行相互通信。这些消息可以是任何类型,包括音频消息、视频消息、命令消息、共享对象消息、数据消息和用户控制消息。
主要分为三类: 协议控制消息、数据消息、命令消息等
- 协议控制消息
- Message Type ID=1~6,主要用于协议内的控制
- 数据消息
- Message Type lD = 8 9 18
- 8:Audio 音频数据
- 9:Video 视频数据
- 18: Metadata 包括音视频编码、视频宽高等信息,
- Message Type lD = 8 9 18
- 命令消息
- Command Message (20,17)
- 此类型消息主要有 NetConnection 和NetStream 两个类,两个类分别有多个函数,该消息的调用,可理解为远程函数调用。
具体的各种消息类型如下:
- Command Message命令消息(20, 17) :命令消息在客户端和服务器之间传输 AMF 编码的命令。
- 对于 AMF0 编码,这些消息被分配的消息类型值为 20;
- 对于 AMF3 编码,消息类型值为 17。
- 这些消息用于执行一些操作,例如在对等方上进行连接(connect)、创建流(createStream)、发布(publish)、播放(play)、暂停(pause)等。像 onstatus、result 等命令消息则用于通知发送方所请求命令的状态。一个命令消息由命令名称、事务 ID 以及包含相关参数的命令对象组成。客户端或服务器可以通过命令消息在流上请求远程过程调用(RPC),并将其传达给对等方。
- Data Message 数据消息(18, 15) :客户端或服务器发送此消息,用于向对等方发送元数据或任何用户数据。元数据包含有关数据(如音频、视频等)的详细信息,例如创建时间、持续时间、主题等。对于 AMF0 编码,这些消息被分配的消息类型值为 18;对于 AMF3 编码,消息类型值为 15。
- Shared Object Message 共享对象消息(19, 16):共享对象是一种 Flash 对象(由一组名称 - 值对组成),可在多个客户端、实例等之间保持同步。对于 AMF0 编码,消息类型 19 用于共享对象事件;对于 AMF3 编码,消息类型 16 用于共享对象事件。每个消息可以包含多个事件。
+------+------+-------+-----+-----+------+-----+ +-----+------+-----+
|Header|Shared|Current|Flags|Event|Event |Event|.|Event|Event |Event|
| |Object|Version| |Type |data |data |.|Type |data |data |
| |Name | | | |length| |.| |length| |
+------+------+-------+-----+-----+------+-----+ +-----+------+-----+
| |
|<- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >|
| AMF Shared Object Message body |
共享对象消息格式
支持以下事件类型:
事件 | 描述 |
---|---|
Use (=1) | 客户端发送此事件,用于通知服务器创建了一个命名的共享对象。 |
Release (=2) | 当客户端删除共享对象时,会向服务器发送此事件。 |
Request Change (=3) | 客户端发送此事件,请求更改共享对象中某个命名参数的值。 |
Change (=4) | 服务器发送此事件,通知除请求发起客户端之外的所有客户端,某个命名参数的值发生了变化。 |
Success (=5) | 如果请求被接受,服务器会向请求的客户端发送此事件,作为对 RequestChange 事件的响应。 |
SendMessage (=6) | 客户端向服务器发送此事件以广播消息。服务器收到此事件后,会向所有客户端(包括发送方)广播该消息。 |
Status (=7) | 服务器发送此事件,通知客户端有关错误情况。 |
Clear (=8) | 服务器向客户端发送此事件以清除共享对象。服务器在响应客户端连接时发送的 Use 事件时,也会发送此事件。 |
Remove (=9) | 服务器发送此事件,要求客户端删除一个槽位。 |
Request Remove (=10) | 客户端发送此事件,要求删除一个槽位。 |
Use Success (=11) | 服务器在连接成功时向客户端发送此事件。 |
- Audio Message音频消息(8):客户端或服务器发送此消息,用于向对等方发送音频数据。消息类型值 8 被保留用于音频消息。
- Video Message视频消息(9):客户端或服务器发送此消息,用于向对等方发送视频数据。消息类型值 9 被保留用于视频消息。
- Aggregate Message 聚合消息(22):聚合消息是一种单个消息
聚合消息格式:
+---------+-------------------------+
| Header | Aggregate Message body |
+---------+-------------------------+
聚合消息主体格式
+--------+-------+---------+--------+-------+---------+ - - - -
|Header 0|Message|Back |Header 1|Message|Back |
| |Data 0 |Pointer 0| |Data 1 |Pointer 1|
+--------+-------+---------+--------+-------+---------+ - - - -
聚合消息的消息流 ID 会覆盖聚合消息内部子消息的消息流 ID。
聚合消息的时间戳与第一个子消息时间戳之间的差值,用于将子消息的时间戳重新归一化到流的时间尺度上。该偏移量会添加到每个子消息的时间戳上,以得到归一化的流时间。第一个子消息的时间戳应与聚合消息的时间戳相同,因此偏移量应为零。
反向指针包含前一个消息(包括其头部)的大小。它的存在是为了匹配 FLV 文件的格式,并用于向后查找。
使用聚合消息有几个性能优势:
- 块流在一个块内最多只能发送一个完整的消息。因此,增大块大小并使用聚合消息可以减少发送的块数量。
- 子消息可以在内存中连续存储。在进行系统调用以在网络上发送数据时,这种方式更高效。
- 用户控制消息事件:客户端或服务器发送此消息,用于通知对等方有关用户控制事件的信息。
支持以下用户控制事件类型:
事件 | 描述 |
---|---|
Stream Begin (=0) | 服务器发送此事件,通知客户端某个流已可用,可以用于通信。默认情况下,在成功收到客户端的应用程序连接命令后,服务器会在 ID 0 上发送此事件。事件数据为 4 字节,表示已可用流的流 ID。 |
Stream EOF (=1) | 服务器发送此事件,通知客户端该流上的数据播放已按请求结束。在没有发出其他命令的情况下,不会再发送更多数据。客户端会丢弃为该流接收的消息。4 字节的事件数据表示播放结束的流的 ID。 |
StreamDry (=2) | 服务器发送此事件,通知客户端该流上没有更多数据。如果服务器在一段时间内未检测到任何消息,它可以通知已订阅的客户端该流已无数据。4 字节的事件数据表示无数据的流的 ID。 |
SetBuffer Length (=3) | 客户端发送此事件,通知服务器用于缓冲流上任何数据的缓冲区大小(以毫秒为单位)。此事件在服务器开始处理流之前发送。事件数据的前 4 字节表示流 ID,接下来的 4 字节表示缓冲区长度(以毫秒为单位)。 |
StreamIs Recorded (=4) | 服务器发送此事件,通知客户端该流是已录制的流。4 字节的事件数据表示已录制流的 ID。 |
PingRequest (=6) | 服务器发送此事件,用于测试客户端是否可达。事件数据是一个 4 字节的时间戳,表示服务器发送该命令时的本地时间。客户端在收到 PingRequest 消息时,会以 PingResponse 进行响应。 |
PingResponse (=7) | 客户端向服务器发送此事件,作为对 PingRequest 请求的响应。事件数据是一个 4 字节的时间戳,即收到 PingRequest 请求时的时间戳。 |
command messages
客户端和服务器交换的命令采用 AMF 编码。
- AMF 编码:AMF(Action Message Format)是一种二进制编码格式,用于在客户端和服务器之间高效地传输数据。在 RTMP 协议里,客户端和服务器交换的命令就采用这种编码方式。它能把命令相关信息紧凑地打包起来传输,接收方再按规则解码
命令消息组成
- 命令名称:明确要执行的操作,像 “connect”(连接 )、“play”(播放 )等,接收方一看名字就知道客户端或服务器想让自己做什么。
- 事务 ID:相当于一个标签,用来标记这条命令。在客户端发送命令后,服务器响应时会带上相同事务 ID,这样客户端就能知道这个响应是针对自己之前发的哪条命令。就像在一堆信件里,给每封信贴上独特编号,回信时也带上这个编号,方便对应。
- 命令对象(含相关参数):包含执行命令所需的具体信息。比如 “connect” 命令里的 “app” 参数,就是告诉服务器客户端要连到哪个应用程序上。不同命令有不同参数,像 “play” 命令可能有要播放的流名称等参数。
命令消息的响应
服务器收到客户端命令后会处理,然后用相同事务 ID 返回响应。
- _ result命令消息:表示命令执行成功。比如客户端发送 “connect” 命令,服务器处理没问题,就返回带相同事务 ID 的 “_ result” 响应,告知客户端连接成功。
- _ error命令消息:说明命令执行出错。要是客户端发的 “connect” 命令,服务器发现有问题(比如应用名不对 ),就返回 “_ error” 响应,还可能带上错误原因,让客户端知道为啥没成功。
- 方法名(如 verifyClient 或 contactExternalServer ):表示服务器希望客户端去执行某个方法。这是一种更灵活的交互方式,比如服务器让客户端去验证自身身份(verifyClient ) 。
相关类对象作用
NetConnection 命令
- NetConnection:
- 概念:它是客户端与服务器之间连接的高级抽象表示,可理解为二者通信的 “桥梁”。它代表着客户端和服务器建立起的一种逻辑连接关系,基于底层可靠的传输层协议(如 TCP )之上 ;管理客户端应用程序和服务器之间的双向连接。此外,它还支持异步远程方法调用。
- 建立连接:通过调用 NetConnection 对象的方法,如connect()命令消息 ,客户端可向服务器发起连接请求,并传递连接所需参数,如应用名、用户名、密码等(若有认证需求 ),来建立与服务器的 RTMP 连接。
- 传输控制信息:连接建立后,用于在客户端和服务器间传输控制命令和状态信息,如关闭连接(close() 方法 )、获取连接状态(可监听连接状态变化事件 )等
- 关联流对象:它是创建和管理 NetStream 对象的基础,通过 NetConnection 创建的 NetStream 对象,才能在已建立的连接上进行音视频流的传输。
可以在 NetConnection 上发送以下命令消息:
- connect:客户端通过发送带有 “connect” 命令名称的消息,向服务器表明想要建立连接的意图。消息中通常会包含如 “app” 参数(指定要连接的应用程序名称),还可能有用户名、密码等认证相关参数(如果服务器设置了相应的认证机制);
- 比如在一个直播应用场景中,客户端想要连接到名为 “live_app” 的直播应用服务端,就会在 connect 命令消息里将 “app” 参数设置为 “live_app”,然后发送给服务器,等待服务器响应来确定连接是否成功建立。
- call:用于客户端调用服务器端的特定方法或函数,实现更复杂、更灵活的远程交互逻辑。它会携带要调用的方法名以及对应的参数等信息,服务器端接收到后会执行相应的方法,并可以通过对应的响应消息返回执行结果给客户端。
- 示例:假如服务器端有一个验证用户权限的方法 “verifyUser”,客户端可以发送 “call” 命令消息,在消息中指明方法名为 “verifyUser”,并附上如用户 ID、权限令牌等相关参数,服务器端执行该方法进行验证后再回复相应结果。
- close:当客户端想要主动断开与服务器已经建立好的连接时,会发送这个命令消息。它告知服务器关闭当前的连接链路,释放相关资源,结束双方之间的通信交互。
- createStream:在连接成功建立后,为了能够进行音视频流等数据的传输,客户端需要通过 NetConnection 发送此命令消息来请求服务器创建一个新的流(stream)。这个流后续会用于承载具体的音频、视频或者其他相关数据的传输工作。
- 示例:在直播推流场景中,主播端客户端在成功连接到直播服务器后,发送 “createStream” 命令消息,服务器收到后会分配一个流 ID 并返回给客户端,此后客户端就能基于这个流 ID 对应的流来推送音视频数据了
NetStream Commands
- NetStream:
- 概念:是用于在客户端和服务器之间传输音频流、视频流及相关控制数据的通道,可看作是音视频数据传输的 “管道;该网络连接将客户端与服务器相连接。
- 音视频流传输:采集到的音视频数据,经编码处理后,通过 NetStream 对象发送到服务器(推流 ),或从服务器接收音视频流数据进行播放(拉流 ) 。例如在直播场景中,主播端利用 NetStream 将音视频流推送到直播服务器,观众端则通过 NetStream 从服务器拉取流进行观看 。
- 控制流操作:可发送控制命令来管理音视频流的播放行为,像play() 命令用于开始播放指定流,pause() 命令暂停播放,stop() 命令停止播放等 。还能设置一些流相关参数,如音量控制等 。
- 状态反馈:提供流的状态信息反馈,如播放开始、结束、出现错误等事件,开发者可通过监听这些事件来实现相应的业务逻辑,比如播放结束后自动切换到下一个视频等 。
客户端可以通过网络流向服务器发送以下命令:
-
play
- 含义:客户端向服务器发送 “play” 命令,用于请求播放指定的媒体内容(如音频、视频等)。通常会指定要播放的资源名称或者路径等相关标识,服务器在接收到该命令后,就会开始将对应的媒体数据通过网络连接,按照合适的流传输方式发送给客户端,从而让客户端能够进行播放操作。
- 例如,客户端想要播放服务器上名为 “movie.flv” 的视频文件,就可以通过发送 “play” 命令,并附上相应的文件名信息来触发播放流程。
-
play2
- 含义:与 “play” 命令类似,同样是用于启动媒体内容播放的指令,但 “play2” 可能在功能或使用场景上存在一些细微差别。它或许是在特定的系统环境、协议版本或者功能拓展下产生的一种播放请求方式,可能在处理播放逻辑、参数传递、对资源的选取与处理等方面有着独特的设定,具体取决于对应的应用实现,不过总体目标也是为了让客户端能顺利播放服务器端的相关媒体资源。
-
deleteStream
- 含义:此命令用于客户端告知服务器删除某个特定的数据流。比如在某些场景下,当客户端不再需要某个已经建立或者正在传输的音频流、视频流或者其他数据信息流时,就可以发送 “deleteStream” 命令,服务器接收到该命令后,会对相应的数据流进行清理、关闭等操作,释放相关资源,终止该数据流的传输,避免不必要的资源占用以及可能出现的传输混乱等情况。
-
closeStream
- 含义:主要功能是让客户端请求关闭已经开启的某个流通道。它与 “deleteStream” 有相似之处,但更侧重于关闭流的操作本身,可能涉及关闭相关的网络连接、释放流相关的缓存、停止数据的收发等一系列操作,以此来彻底结束某个流所承载的信息传输过程,确保客户端与服务器之间关于该流的交互完全停止,常用于客户端想要结束当前对某个媒体资源的接收或者发送操作时。
-
receiveAudio
- 含义:客户端通过发送 “receiveAudio” 命令来告知服务器,自己希望接收音频数据。例如在多媒体播放场景中,客户端可能一开始只希望接收视频画面,后续想要同时获取音频内容时,就可以发送该命令,服务器收到后便会开始向客户端发送相应的音频信息流,使客户端能够完整地接收并播放包含音频和视频的多媒体内容,或者在一些交互场景下,单独启用音频数据的接收。
-
receiveVideo
- 含义:和 “receiveAudio” 相对应,“receiveVideo” 命令是客户端向服务器表明期望接收视频数据。当客户端需要观看视频内容,或者在暂停接收视频后想要重新开启视频数据的接收时,就会发送此命令,然后服务器依照命令将对应的视频数据通过网络流向客户端传输,以满足客户端播放视频的需求。
-
publish
- 含义:客户端使用 “publish” 命令来向服务器发布自己的内容,一般是将本地的音频、视频或者其他数据作为数据源,通过网络连接推送到服务器端。例如在直播场景中,主播端的客户端会通过发送 “publish” 命令,把采集到的实时音频和视频数据发布到服务器上,服务器再将这些内容转发给其他订阅了该直播的客户端,实现内容的传播与共享。
-
seek
- 含义:“seek” 命令用于客户端请求在媒体流中进行定位查找操作,也就是改变播放的位置。比如在观看视频或者收听音频时,客户端想要快进到某个特定时间点或者回退到之前的某个时间点,就可以发送 “seek” 命令,并指定相应的时间戳或者相对位置等信息,服务器收到后会按照请求调整数据流的发送位置,使得客户端能够从指定的新位置开始继续接收并播放媒体内容,实现精准的播放位置控制。
-
pause
1. 含义:“pause” 命令的作用是让客户端请求暂停当前正在播放的媒体内容。当用户想要暂时停止音频、视频的播放,例如临时有事需要中断观看或者收听时,客户端向服务器发送该命令,服务器接收到后会暂停对应数据流的发送,客户端这边也就停止了播放,直到后续接收到继续播放的相关指令(比如 “play” 命令等),才会恢复正常的播放流程。
message exchange examples(消息交换例子)
展示一些具体的示例场景,来直观呈现客户端与服务器之间如何基于前文所阐述的各种协议、消息类型以及命令等进行消息交换,
Publish Recorded Video(发布视频流消息流程)
+--------------------+ +-----------+
| Publisher Client | | | Server |
+----------+---------+ | +-----+-----+
| Handshaking Done |
| | |
| | |
---+---- |----- Command Message(connect) ----->|
| | |
| |<----- Window Acknowledge Size ------|
Connect | | |
| |<------ Set Peer BandWidth ----------|
| | |
| |------ Window Acknowledge Size ----->|
| | |
| |<----- User Control(StreamBegin) ----|
| | |
---+---- |<-------- Command Message -----------|
| (_result- connect response) |
| |
---+---- |--- Command Message(createStream) -->|
Create | | |
Stream | | |
---+---- |<------- Command Message ------------|
| (_result- createStream response) |
| |
---+---- |---- Command Message(publish) ------>|
| | |
| |<----- User Control(StreamBegin) ----|
| | |
| |---- Data Message (Metadata) ------->|
| | |
Publishing| |------------ Audio Data ------------>|
Content | | |
| |------------ SetChunkSize ---------->|
| | |
| |<--------- Command Message ----------|
| | (_result- publish result) |
| | |
| |------------- Video Data ----------->|
| | | |
| | | |
| Until the stream is complete |
| | |
Message flow in publishing a video stream
-
握手完成(Handshaking Done):
这是整个流程的起始前提,表示客户端与服务器之间已经完成了握手环节,建立起了基本的通信基础,为后续具体的业务消息交互做好了准备。 -
连接相关消息交互(Connect):
- Command Message(connect) →:发布者客户端向服务器发送 “connect” 命令消息,此消息包含客户端要连接服务器应用程序实例的相关请求信息,比如客户端相关配置参数等,目的是发起与服务器的连接请求,让服务器知晓客户端想要接入并进行后续操作。
- Window Acknowledge Size ←:服务器收到 “connect” 命令后,向客户端回复 “Window Acknowledge Size” 消息,该消息用于告知客户端在发送确认消息之间应使用的窗口大小,也就是在后续数据传输过程中,客户端在未收到服务器确认的情况下可以发送的最大字节数相关信息,有助于规范双方的数据发送节奏。
- Set Peer BandWidth ←:接着服务器向客户端发送 “Set Peer BandWidth” 消息,其作用是限制客户端的输出带宽,通过设定窗口大小等机制,让客户端合理控制数据发送量,保障网络传输的稳定性和效率。
- Window Acknowledge Size →:客户端处理完 “Set Peer BandWidth” 消息后,向服务器回传 “Window Acknowledge Size” 消息,表示客户端知晓并遵循服务器设定的相关规则,同时也维持双方在窗口大小确认等数据传输协调方面的交互。
- User Control(StreamBegin) ←:随后服务器向客户端发送 “User Control (StreamBegin)” 消息,通知客户端某个流已可用,可以用于通信了,意味着服务器已经准备好进行后续的流相关操作,为创建流、发布内容等操作铺垫基础。
- **Command Message( _result - connect response) **:最后服务器向客户端发送包含连接响应的命令消息,具体响应内容在 “_result” 部分体现,告知客户端连接请求的处理结果(成功或失败以及相关详细信息等),让客户端明确连接状态是否符合预期。
-
创建流相关消息交互(Create Stream):
- Command Message(createStream) →:客户端在连接成功后,向服务器发送 “Command Message (createStream)” 消息,请求创建一个用于传输数据的流,这个流后续将承载要发布的视频等内容。
- Command Message(_result - createStream response) ←:服务器收到创建流的请求后,向客户端回复对应的命令消息,其 “_result” 部分包含了创建流请求的处理结果,告知客户端流是否创建成功以及相关的具体情况,确保客户端知晓能否继续后续的发布操作。
-
发布内容相关消息交互(Publishing Content):
- Command Message(publish) →:客户端接着发送 “Command Message (publish)” 消息,向服务器表明要发布内容,也就是准备将本地的视频等数据通过这个创建好的流推送到服务器端,正式启动发布流程。
- User Control(StreamBegin) ←:服务器收到 “publish” 命令后,再次发送 “User Control (StreamBegin)” 消息,进一步确认流开始相关情况,为即将到来的数据传输做好准备。
- Data Message (Metadata) →:客户端向服务器发送 “Data Message (Metadata)” 消息,该消息包含了要发布的视频等数据的元数据信息,例如视频的创建时间、时长、格式等基本描述性信息,方便服务器对后续接收到的具体数据进行正确的处理和分发。
- Audio Data →:随后客户端开始向服务器发送音频数据,将采集到的音频部分按照一定的格式和传输规则,通过已建立的流逐步发送给服务器,这是发布内容中音频部分的传输过程。
- SetChunkSize →:客户端还会发送 “SetChunkSize” 消息,用于通知服务器新的最大块大小,可能是基于要发布的数据量、传输效率等因素考虑,调整后续数据传输时的块大小设定,以优化数据传输过程。
- Command Message(_result - publish result) ←:服务器收到客户端的发布相关操作后,向客户端回复包含发布结果的命令消息,其 “_result” 部分明确告知客户端发布操作的处理结果,比如是否成功发布等具体情况,让客户端了解发布任务的完成状态。
- Video Data →:最后客户端持续向服务器发送视频数据,按照流的传输机制,将视频内容逐部分发送给服务器,直至整个视频流的传输完成,实现将视频内容发布到服务器的整个过程。
在完成上述消息交互流程后,视频流就成功发布到了服务器上,此时其他客户端就可以通过订阅这个已发布的流,并按照相应的播放流程(类似发送 “play” 等命令消息以及接收服务器推送的视频数据等交互过程,虽未在此示例中详细展示但为后续可能的操作)来播放该视频了
Broadcast a Shared Object Message(广播共享对象消息)
+--------------------+ +-----------+
| Publisher Client | | | Server |
+----------+---------+ | +-----+-----+
| Handshaking Done |
| | |
| | |
---+---- |----- Command Message(connect) ----->|
| | |
| |<----- Window Acknowledge Size ------|
Connect | | |
| |<------ Set Peer BandWidth ----------|
| | |
| |------ Window Acknowledge Size ----->|
| | |
| |<----- User Control(StreamBegin) ----|
| | |
---+---- |<-------- Command Message -----------|
| (_result- connect response) |
| |
---+---- |--- Command Message(createStream) -->|
Create | | |
Stream | | |
---+---- |<------- Command Message ------------|
| (_result- createStream response) |
| |
---+---- |---- Command Message(publish) ------>|
| | |
| |<----- User Control(StreamBegin) ----|
| | |
| |---- Data Message (Metadata) ------->|
| | |
Publishing| |------------ Audio Data ------------>|
Content | | |
| |------------ SetChunkSize ---------->|
| | |
| |<--------- Command Message ----------|
| | (_result- publish result) |
| | |
| |------------- Video Data ----------->|
| | | |
| | | |
| Until the stream is complete |
| | |
Message flow in publishing a video stream
这个示例旨在展示在共享对象的创建、属性更改以及共享对象消息广播过程中,客户端与服务器之间交换的各类消息情况,清晰呈现整个流程里双方的交互逻辑,帮助理解共享对象相关操作是如何通过消息传递来实现的。
-
握手及应用连接完成(Handshaking and Application connect done):
这是整个共享对象操作流程的前置基础,表示客户端与服务器之间已经完成了握手环节以及应用层面的连接操作,双方可以开始进行关于共享对象的具体业务交互了,就如同搭建好了通信的基础框架,准备在上面开展后续的功能实现。 -
创建及连接共享对象(Create and connect Shared Object):
- Shared Object Event(Use) →:客户端向服务器发送 “Shared Object Event (Use)” 消息,此消息用于通知服务器创建了一个命名的共享对象,意味着客户端发起了创建共享对象的请求,让服务器知晓要开始准备相应的资源和管理机制来处理这个即将到来的共享对象相关事务。
- Shared Object Event(UseSuccess, Clear) ←:服务器收到客户端的 “Use” 事件消息后,向客户端回复包含 “UseSuccess” 和 “Clear” 的 “Shared Object Event” 消息。其中,“UseSuccess” 表示服务器确认连接成功,告知客户端共享对象创建并连接成功这一情况;“Clear” 事件通常用于服务器向客户端传达清除共享对象等相关操作信息(可能是初始化相关的清理或者按照既定规则的清理动作),确保客户端和服务器在共享对象初始状态方面达成一致认知。
-
共享对象设置属性(Shared object Set Property):
- Shared Object Event(RequestChange) →:客户端接着发送 “Shared Object Event (RequestChange)” 消息,此消息表明客户端请求更改共享对象中某个命名参数的值,也就是客户端想要对已经创建的共享对象的特定属性进行修改,向服务器提出相应的变更请求。
- Shared Object Event(Success) ←:服务器收到客户端的 “RequestChange” 请求后,如果认可该请求并成功处理了属性更改操作,就会向客户端回复 “Shared Object Event (Success)” 消息,告知客户端其请求的属性更改已成功完成,让客户端明确其操作请求的执行结果。
-
共享对象消息广播(Shared object Message Broadcast):
- Shared Object Event(SendMessage) →:客户端向服务器发送 “Shared Object Event (SendMessage)” 消息,此消息的作用是客户端向服务器广播消息,希望服务器能将该消息转发给所有相关的客户端(包括发送方自身),以此来实现消息在多个客户端之间的传播共享。
- Shared Object Event(SendMessage) ←:服务器收到客户端的 “SendMessage” 广播消息后,会按照要求向所有客户端(包括发送该消息的客户端)广播该消息,同时也向发起广播的客户端回复 “Shared Object Event (SendMessage)” 消息,告知客户端消息已按照要求进行广播处理,确保客户端知晓广播操作的执行情况。
通过这样一系列的消息交换过程,实现了从共享对象的创建、属性更改到消息广播的完整操作流程,展示了在涉及共享对象相关功能时客户端与服务器之间紧密且有序的交互方式,使得多个客户端之间能够基于共享对象进行信息的同步和共享,这对于一些需要协同操作、数据同步的应用场景有着重要意义,例如多人在线协作编辑文档、实时共享游戏状态等场景中都可能运用到类似的共享对象消息交互机制
Publish Metadata from Recorded Stream(发布来自录制流的元数据)
+------------------+ +---------+
| Publisher Client | | | FMS |
+---------+--------+ | +----+----+
| Handshaking and Application |
| connect done |
| | |
| | |
---+--- |-- Command Messsage (createStream) ->|
Create | | |
Stream | | |
---+--- |<-------- Command Message -----------|
| (_result - command response) |
| |
---+--- |---- Command Message (publish) ----->|
Publishing | | |
metadata | |<----- UserControl (StreamBegin) ----|
from file | | |
| |---- Data Message (Metadata) ------->|
| |
Publishing metadata
这个示例着重描述了在发布来自录制流的元数据时,发布者客户端(Publisher Client)与 Flash Media Server(FMS)之间的消息交换过程,通过展示各步骤中具体的消息传递情况,呈现如何将录制流中的元数据发布到服务器端,方便后续其他相关操作(比如播放等)能依据这些元数据准确进行
-
握手及应用连接完成(Handshaking and Application connect done):
这是整个发布元数据流程的起始前提,意味着客户端与服务器(FMS)之间已经完成了握手环节以及应用层面的连接操作,双方建立起了基本的通信链路,为后续围绕元数据发布展开的具体消息交互打下了基础,就如同为后续的业务往来铺好了道路。 -
创建流(Create Stream):
- Command Messsage (createStream) →:发布者客户端向服务器(FMS)发送 “Command Messsage (createStream)” 消息,其目的是请求创建一个用于传输数据的流。这个流将在后续承载要发布的元数据等内容,是准备发布工作的第一步,通过此消息让服务器知晓客户端有创建流用于后续操作的需求。
- Command Message (_result - command response) ←:服务器(FMS)收到客户端发送的创建流请求后,会向客户端回复对应的命令消息,其中 “_result” 部分包含了对创建流这个命令的响应结果,告知客户端流是否创建成功以及相关的详细情况,例如可能包含一些流的配置参数、是否符合要求等信息,让客户端明确自己的创建流请求是否得到满足,能否继续后续的发布操作。
-
发布元数据(Publishing metadata from file):
- Command Message (publish) →:客户端在确认流创建成功后,接着向服务器发送 “Command Message (publish)” 消息,此消息表明客户端要开始发布内容了,具体在这里就是要发布来自录制流的元数据,向服务器表明自己的发布意图以及准备将相关元数据推送过去。
- UserControl (StreamBegin) ←:服务器(FMS)收到客户端的 “publish” 命令后,向客户端回复 “UserControl (StreamBegin)” 消息,该消息的作用是通知客户端某个流已经可用,可以用于进行数据的传输等通信操作了,意味着服务器已经做好准备来接收客户端即将发布的元数据,为后续的数据接收和处理营造好条件。
- Data Message (Metadata) →:随后,客户端向服务器发送 “Data Message (Metadata)” 消息,这是整个流程的核心部分,该消息中包含了来自录制流的元数据信息。这些元数据可以涵盖诸如录制流所对应的视频或音频的创建时间、时长、格式、编码方式等各类描述性内容,服务器接收到这些元数据后,就能依据其对后续完整的录制流数据(如果有进一步的数据传输需求)进行准确的处理、存储以及向其他客户端提供相应的服务(比如播放服务等)。
RTMP Message Formats(RTMP 消息格式)
rtmp message format
服务器和客户端通过网络发送 RTMP 消息来相互通信。这些消息可以包含音频、视频、数据或任何其他类型的消息。
RTMP 消息由两部分组成:头部和负载。
Message Header(消息头部)
包括:
- 消息类型:1 字节的字段,用于表示消息类型。1 - 6 的一系列类型 ID 被保留用于协议控制消息。
- 长度:3 字节的字段,以大端格式设置,表示负载的大小(以字节为单位)。
- 时间戳:4 字节的字段,包含消息的时间戳。这 4 个字节按大端顺序打包。
- 消息流 ID:3 字节的字段,唯一标识该消息所属的流。这些字节以大端格式设置。
- 区分不同媒体流:在实际的音视频传输场景中,往往会同时存在音频流、视频流,甚至可能有多条不同内容的音视频流(比如在多路直播场景下)。消息流 ID 能够清晰地将这些不同的流区分开来,使得接收端可以准确地把接收到的消息对应到相应的流上进行后续处理,比如音频消息对应到音频流进行音频解码,视频消息对应到视频流进行视频解码等。
- 从客户端到服务器或者服务器到客户端的整个数据传输过程中,无论是音视频数据,还是控制命令等相关消息,只要属于同一个流,都会携带相同的消息流 ID;
- 比如在一个在线直播平台中,有主播 A 正在进行直播,其音视频数据分别通过不同的消息封装成 RTMP Message 进行传输,音频消息对应的消息流 ID 为 “0001”,视频消息对应的消息流 ID 也是 “0001”,这样接收端就能根据这个共同的 ID 识别出它们属于同一个直播流,将音频和视频数据分别进行正确处理后同步播放,呈现出完整的直播画面和声音效果。同时,如果有另一个主播 B 也在直播,其对应的流的消息流 ID 则可能是 “0002” 等其他不同的值,从而与主播 A 的直播流区分开来。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type | Payload length |
| (1 byte) | (3 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
| (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream ID |
| (3 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
消息负载
消息的另一部分是负载,它是消息中实际包含的数据。例如,它可以是一些音频样本或压缩的视频数据。
user control messages (用户控制消息(4))
RTMP 使用消息类型 ID 为 4 的消息作为用户控制消息。这些消息包含 RTMP 流层使用的信息。ID 为 1、2、3、5 和 6 的协议消息由 RTMP 块流协议使用。
用户控制消息应使用消息流 ID 0(称为控制流),并且当通过 RTMP 块流发送时,应在块流 ID 2 上发送。用户控制消息在流中被接收的那一刻起就生效,其时间戳会被忽略。
客户端或服务器发送此消息,以通知对等方有关用户控制事件的信息。此消息携带事件类型和事件数据。
+------------------------------+-------------------------
| Event Type (16 bits) | Event Data
+------------------------------+-------------------------
“用户控制” 协议消息的负载
- 消息数据的前 2 个字节用于识别事件类型。事件类型之后是事件数据。事件数据字段的大小是可变的。然而,在消息必须经过 RTMP 块流层的情况下,最大块大小应足够大,以允许这些消息适合单个块。
RTMP Chunk Stream(RTMP块流)
实时消息传输协议块流(RTMP Chunk Stream),
- 为更高层的多媒体流协议提供多路复用和分组服务。虽然 RTMP 块流设计用于与实时消息传输协议配合使用,但它也能处理任何发送消息流的协议。
- 每个消息都包含时间戳和有效载荷类型标识。
- RTMP 块流和 RTMP 一起适用于多种音视频应用场景,包括一对一和一对多直播、视频点播服务以及交互式会议应用等。
当与可靠的传输协议(如 TCP [RFC0793])配合使用时,RTMP 块流可确保所有消息按时间戳顺序在多个流中实现端到端的可靠传输。RTMP 块流本身不提供优先级或类似的控制功能,但高层协议可利用它来实现此类功能。例如,直播视频服务器可能会为慢速客户端丢弃视频消息,以确保音频消息能及时接收。
RTMP 块流包含其自身的带内协议控制消息,同时也为高层协议嵌入用户控制消息提供了机制
message format(消息格式)
可分割为块以支持多路复用的消息格式取决于高层协议,但通常应包含以下创建块所需的字段:
chunking(消息块)
- 块流是一种逻辑上的数据流通道,用于对分割后的 RTMP Chunk(块)进行管理和传输。
- 每个块流从一个消息流中传输一种类型的消息。创建的每个块都有一个唯一的块流 ID。块通过网络传输,传输时每个块必须完整发送后才能发送下一个块。在接收端,根据块流 ID 将块组装成消息。
- 多路复用:不同的音视频数据以及各类控制消息等,在经过分割形成 RTMP Chunk 后,会被分配到不同的块流中进行传输。例如,音频数据的 Chunk 可能通过某一个块流传输,视频数据的 Chunk 通过另一个块流传输,而像控制消息的 Chunk 又会通过其他专门的块流来传送。这样,多种不同类型的数据就能在同一个网络连接上同时进行传输,实现了多路复用的功能,就如同在一条高速公路上划分出不同的车道,让不同车辆(不同类型的数据)可以并行前进,提高了网络带宽的利用率。
- 传输优先级保障:不同块流可以设置不同的传输优先级。一般来说,控制消息所在的块流优先级相对较高,这样能确保重要的控制指令(如连接建立、流控制等命令)可以优先在网络中传输,及时被接收端接收并执行,避免因大量音视频数据传输而导致控制消息被阻塞,影响整个传输流程的正常开展
- 例如:在一个在线直播场景中,假设有主播正在进行直播,其音频数据经过处理后形成的 RTMP Chunk 被分配到块流 ID 为 10 的块流中进行传输,视频数据对应的 Chunk 则通过块流 ID 为 11 的块流传送,同时,服务器发送的一些用户控制消息(如流开始、流结束等消息)的 Chunk 安排在块流 ID 为 0 的块流中传输。客户端接收到这些来自不同块流的 Chunk 后,根据块流 ID 识别出数据所属类别,分别对音频、视频等数据进行相应的组装和处理,最终实现同步播放直播内容以及对直播过程进行有效控制的效果。
分块机制允许将高层协议中的大消息分解为较小的消息,避免大的低优先级消息(如视频)阻塞小的高优先级消息(如音频或控制消息)。同时,分块也能减少小消息的传输开销,因为块头包含了部分原本需在消息中重复的信息的压缩表示。
块大小是可配置的,可使用 “设置块大小” 控制消息进行设置。较大的块大小可降低 CPU 使用率,但可能导致在低带宽连接上写入操作更大,从而延迟其他内容的传输;较小的块大小则不适用于高比特率流。块大小在每个方向上独立维护。
Chunk Format(块格式)
每个块由头部和数据组成,头部又包含三个部分:
Basic Header(块基本头部)
块基本头部对块流 ID 和块类型(由 fmt 字段表示)进行编码,基础数据头, Chunk stream ID可以配置为3~65599 这 65597 个不同标志中的其中一种。根据持有 Chunk stream ID 的长度,RTMP 规格将基础数据头分为3种:ID 在 2~63 范围内的 1-Byte 版;ID 在 64~319 范围内的 2-Byte 版;ID 在 64~65599 范围内的 3-Byte 版。。
- cs ID (6bits)(Chunk stream ID):块流ID,取值范围2 ~ 63。0和1表示该字段的2字节或3字节版本。
- FMT (2 BITS):该字段标识“块消息头MSG Header”使用的四种格式之一。也被称为 Chunk Type;
- cs ID - 64(8或16 BITs):该字段包含块流ID减去64。例如,ID 365在cs ID中用1表示,在这里用16位的301表示。值为64-319的块流id可以用头的2字节或3字节形式表示。
字节数 | 描述 | 示例 |
---|---|---|
1 字节 | 块流 ID 2 - 63 可编码在该字段的 1 字节版本中![]() | fmt:块类型标识,cs id:块流 ID(2 - 63) |
2 字节 | 块流 ID 64 - 319 可编码在该头部的 2 字节形式中,ID 计算方式为(第二个字节 + 64)![]() | fmt:块类型标识,0 0 0 0 0 0:固定位,cs id - 64:块流 ID 减去 64(8 位) |
3 字节 | 块流 ID 64 - 65599 可编码在该字段的 3 字节版本中,ID 计算方式为((第三个字节)* 256 + (第二个字节) + 64)![]() | fmt:块类型标识,0 0 0 0 0 1:固定位,cs id - 64:块流 ID 减去 64(16 位) |
MSG Header(消息头)
消息数据头的类型,是由基础数据头中的 fmt 字段来标记的。
- 其长度和具体内容根据基本头部中标识的 Chunk 格式类型有所不同。
- 它主要包含了与对应消息相关的信息,比如时间戳(timestamp,用于音视频同步等)、消息长度(message length,指明消息体的长度)、消息类型 ID(message type id,标识消息是音频、视频还是其他类型)、消息流 ID(message stream id,表明该消息所属的流 ID)等,这些信息能帮助接收端准确地解析和处理接收到的 Chunk。
四种 Chunk Message Header格式类型:
-
Type 0
- 块报头长度为11字节。
- 该类型的 Chunk 包含完整的消息头部信息(即上述提到的消息头部中的所有字段都会出现),后续的 Chunk 如果属于同一个消息,就可以参考它的头部信息,不用重复携带完整的头部内容,从而节省网络传输带宽。一般用于每个消息的第一个 Chunk。
- 这种类型必须在数据块流开始时使用,并且当数据流时间戳向后时(例如,由于向后查找)。
-
TYPE 1
- Type 1块报头长度为7字节。不包括消息流ID;这个数据块使用与前一个数据块相同的流ID。
- 具有可变大小消息的流(例如,许多视频格式)应该在每个新消息的第一个块之后使用此格式。
-
TYPE 2
- 类型2的分块头信息长度为3个字节。其中既不包含流标识,也不包含消息长度;
- 这个分块具有与前一个数据块具有相同的流标识符和消息长度。具有固定消息长度的流(例如,(某些音频和数据格式)在每条消息的前半部分,应当采用这种格式来表示前半部分的内容,而在后续部分则应采用其他格式。
- 可以复用同一个消息中前面 Chunk(通常是类型 0 的 Chunk)已经传递过的消息长度、消息类型 ID、消息流 ID 等信息,适用于同一个消息中后续连续传输的 Chunk,通过复用部分头部信息减少冗余数据传输。
-
TYPE3
- 类型3的数据块没有消息头。流标识符、消息长度和时间戳差值字段均不存在;
- 此类数据块的值取自前一个具有相同 Chunk 流标识符的数据块。
- 当将单个消息拆分成多个数据块时,除第一个数据块外,所有数据块都应采用此类型。
- 由完全相同大小、流标识以及时间间隔的消息组成的流,在遇到类型为2的数据块之后,所有后续数据块都应采用此类型。
- 如果第一条消息与第二条消息之间的差值与第一条消息的时间戳相同,那么类型3的数据块可以立即紧跟在类型0的数据块之后,因为无需类型2的数据块来记录差值。
- 如果类型3的数据块紧跟在类型0的数据块之后,那么此类型 3的数据块的时间戳差值与类型 0的数据块的时间相同;
扩展时间戳
扩展时间戳字段用于对大于16777215(即0xFFFFFF)的时戳或时戳差值进行编码;也就是说,对于不符合类型 0、1或2分块中 24 位字段范围的时戳或时戳差值而言,该字段用于对完整的32 位时戳或时戳差值进行编码。此字段通过将类型 0分块的时戳字段或类型1或2分块的时戳差值字段设置为16777215(即0xFFFFFF)来指示其存在。当同一流ID 的最近类型 0、1或2分块表明存在扩展时间戳字段时,该字段存在于类型3分块中。
handshake(RTMP握手)
在 RTMP 协议中,握手阶段的C0、C1、C2、S0、S1、S2既不是标准的Chunk也不是Message,而是一种特殊的握手数据结构,用于建立初始连接。它们的特点如下:
- 目的:握手阶段仅用于版本协商、时间戳同步和验证连接,不传输音视频或命令数据。
- 格式:握手数据是固定长度的二进制块,结构简单,与后续的 Chunk/Message 格式完全不同。
- 传输顺序:客户端发送 C0→C1→C2,服务器回应 S0→S1→S2,严格按顺序执行。
RTMP在交互过程中需要先进行RTMP握手
- 通过握手之后开始发送控制信息,例如设置窗口大小、确认窗口大小、设置带宽、创建流等,之后就能进行相应的数据交互或者是数据流传输
- RTMP 连接以握手过程开始。握手过程与协议的其他部分不同,它由三个固定大小的块组成,而非带有头部的可变大小块。
- 客户端(发起连接的端点)和服务器各自发送相同的三个块,为便于说明,客户端发送的块分别称为 C0、C1 和 C2,服务器发送的块分别称为 S0、S1 和 S2。
握手示意图
- 未初始化:在此阶段发送协议版本。客户端和服务器均未初始化,客户端在数据包 C0 中发送协议版本。如果服务器支持该版本,则发送 S0 和 S1 作为响应;否则,服务器将采取相应措施(在 RTMP 中,此措施为终止连接)。
- 版本已发送:客户端和服务器在未初始化状态之后进入版本已发送状态。客户端等待数据包 S1,服务器等待数据包 C1。收到等待的数据包后,客户端发送数据包 C2,服务器发送数据包 S2,然后状态变为确认已发送。
- 确认已发送:客户端和服务器分别等待 S2 和 C2。
- 握手完成:客户端和服务器开始交换消息。
保证: - 握手从客户端发送 C0 和 C1 块开始。
- 客户端必须在收到 S1 后才能发送 C2,且必须在收到 S2 后才能发送其他数据。
- 服务器必须在收到 C0 后才能发送 S0 和 S1,也可选择在收到 C1 后再发送。
- 服务器必须在收到 C1 后才能发送 S2,且必须在收到 C2 后才能发送其他数据(RTMP控制信息,音视频数据)。
实际为了减少通信次数:
| client | server |
|-----C0 + C1---->| C->S将C0 C1一起发送过去
|<----S0+S1+S2----| S->C将S0 S1 S2一起返回
|-------C2------->|
C0 和 S0 格式
C0 和 S0 数据包均为单字节,作为一个 8 位整数字段处理:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| version |
+-+-+-+-+-+-+-+-+
C0 and S0 bits
- 0 - 7 位
- 版本(8 位):在 C0 中,该字段标识客户端请求的 RTMP 版本;
- 在 S0 中,该字段标识服务器选择的 RTMP 版本。本规范定义的版本为 3。
- 0 - 2 为早期专有产品使用的已弃用值;4 - 31 保留用于未来实现;32 - 255 不允许使用(以便将 RTMP 与基于文本的协议区分开来,基于文本的协议通常以可打印字符开头)。
- 如果服务器不识别客户端请求的版本,应回复 3。
- 客户端可以选择降级到版本 3,或者放弃握手。
C1 和 S1 格式
C1 和 S1 数据包长度为 1536 字节,包含以下字段:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C1 and S1 bits
- 0 - 31 位
- 时间(4 字节):该字段包含一个时间戳,应作为此后从此端点发送的所有块的纪元时间。可以为 0 或任意值。为同步多个块流,端点可能希望发送其他块流时间戳的当前值。
- 32 - 63 位
- 零(4 字节):该字段必须全为 0。
- 64 - 1599 位
- 随机数据(1528 字节):该字段可包含任意值。保证此次握手的唯一性,确定握手的对象;
C2 和 S2 格式
C2 和 S2 数据包长度为 1536 字节,几乎是 S1 和 C1 的回显,包含以下字段:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time2 (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random echo |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
C2 and S2 bits
- 0 - 31 位
- 时间(4 字节):C2/S2 中发送的时间戳。
- 32 - 63 位
- 时间 2(4 字节):S1/C1发送的时间戳。
- 64 - 1599 位
- 随机回显(1528 字节):S1/C1发送的随机数,长度1528Bit
例子-音频消息流
音频消息流处理过程,涉及信息冗余及数据传输优化;
该示例展示简单音频消息流及信息冗余情况:
- 呈现了 4 条音频消息(Msg #1 - Msg #4 )的相关参数
- Message Stream ID:消息流 ID,均为 12345 ,表示这些消息属于同一流。
- Message Type ID:消息类型 ID,都是 8 。
- Time:时间戳,分别为 1000、1020、1040、1060 ,逐步递增。
- Length:消息长度,均为 32 。
下面是消息流中生成的块,从消息 3 开始,数据传输得到优化,此后每条消息仅增加 1 字节开销。
- 表格:展示了 4 个音频消息块(Chunk#1 - Chunk#4 )的参数。
- Stream ID:流 ID,均为 3 。
- Chunk Type:块类型,Chunk#1 为 0 ,Chunk#2 为 2 ,Chunk#3 和 Chunk#4 为 3 。
- Header Data:头部数据,Chunk#1 详细列出时间增量(delta: 1000 )、长度(length: 32 )、类型(type: 8 )、流 ID(stream ID: 12345 )及占用字节数(11 bytes );Chunk#2 为 20(占用 3 字节 );Chunk#3 和 Chunk#4 为 none(占用 0 字节 )。
- No.of Bytes After Header:头部之后的字节数,均为 32 。
- Total No.of Bytes in the Chunk:块中的总字节数,Chunk#1 为 44 ,Chunk#2 为 36 ,Chunk#3 和 Chunk#4 为 33 。
整体体现了音频消息在传输中从原始状态到优化后,通过分块减少数据冗余、提升传输效率的过程
例子-一条过长消息如何被拆分成多个块进行处理
展示一条过长、无法容纳在 128 字节块中的消息,如何被拆分成多个块。
原始消息表格展示了一条消息(Msg #1 )的相关参数:
- Message Stream ID:消息流 ID 为 12346 ,代表该消息所属的流编号。
- Message Type ID:消息类型 ID 为 9 ,注明是视频(video)类型。
- Time:时间戳为 1000 。
- Length:消息长度为 307 字节,超过了 128 字节,所以需要分块
原始消息表格展示了拆分后生成的 3 个块(Chunk#1 - Chunk#3 )的参数:
- Chunk Stream ID:块的流 ID 均为 4 ,用于标识块所属流。
- Chunk Type:
- Chunk#1 为 0 ,是起始块类型。
- Chunk#2 和 Chunk#3 为 3 ,这种类型有两种作用,一是表示消息延续,二是表示新消息开始(其头部可从现有状态数据推导 ) 。
- Header Data:
- Chunk#1 包含 delta: 1000(时间增量 )、length: 307(消息总长度 )、type: 9(消息类型 )、stream ID: 12346(消息流 ID ),共占用 11 字节。
- Chunk#2 和 Chunk#3 为 none (0 bytes) ,表示无额外头部数据。
- No. of Bytes after Header:头部之后的字节数。
- Chunk#1 和 Chunk#2 为 128 ,是块中除去头部可容纳的数据量。
- Chunk#3 为 51 ,因为是最后一块,容纳剩余数据。
- Total No. of bytes in the chunk:块中的总字节数。
- Chunk#1 为 140(头部 11 字节 + 数据 128 字节 + 其他开销 1 字节 )。
- Chunk#2 为 129(头部 1 字节 + 数据 128 字节 )。
- Chunk#3 为 52(头部 1 字节 + 数据 51 字节 )
可知,Chunk type 3 有两种用法:
- 指定消息延续;
- 指定新消息开始(其头部可从现有状态数据推导 ) 。
protocol control messages (协议控制消息)
- 客户端和服务器之间传递重要的控制信息,以保障数据传输的稳定和高效。
- Protocol Control Messages(协议控制消息)在 RTMP 协议中,是 Message(消息)的一种,同时在传输时会以 Chunk(块)的形式存在(分块)
- 作为 Message 的层面
- 消息结构角度:从结构组成来看,协议控制消息遵循 RTMP Message 的基本结构规范。它包含 Message Header(消息头)部分, Message Body(消息体)部分
- 功能和分类角度:在功能上,协议控制消息属于 RTMP 协议里众多消息类型中的一类,与音频消息、视频消息、命令消息等并列
- 作为 Chunk 的层面
- 传输单元角度:在实际网络传输过程中,为了便于在网络上高效地传输,协议控制消息这类 RTMP Message 会按照规则被分割成多个 RTMP Chunk。
- 复用与重组角度:分割后的 Chunk 会携带相应的头部信息(如基本头部标识 Chunk 格式类型、块流 ID,消息头部包含时间戳等必要消息相关信息等),不同协议控制消息的 Chunk 与其他音频、视频等消息的 Chunk 会交织在一起传输,并且在接收端可以根据 Chunk 的这些头部信息以及相关规则,将属于同一个协议控制消息的 Chunk 重新组装起来,还原成完整的协议控制消息(即完整的 Message)
- 作为 Message 的层面
这部分内容主要介绍了 5 种协议控制消息,每种消息都有特定的功能和格式。RTMP 块流使用消息类型 ID 为 1、2、3、5 和 6 的消息作为协议控制消息:
Set Chunk Size (1)
- 用于通知对方新的最大块大小。默认最大块大小是 128 字节,客户端或服务器可根据需求修改。比如客户端要发送 131 字节音频数据,而当前块大小为 128 字节时,就可通过此消息将块大小设为 131 字节,这样就能用一个块发送数据。最大块大小应至少 1 字节,最好不小于 128 字节。
- 该消息的有效范围是 1 到 2147483647(0x7FFFFFFF)字节,但大于 16777215(0xFFFFFF)字节的大小在实际中是等效的,因为块和消息的大小不会超过这个值。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0| chunk size (31 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
“设置块大小” 协议消息的负载
- 0:此位必须为零。
- 块大小(31 位):此字段保存新的最大块大小(以字节为单位),在收到进一步通知之前,发送方后续的所有块都将使用此大小。有效大小范围是 1 到 2147483647(0x7FFFFFFF)字节(含);不过,所有大于 16777215(0xFFFFFF)字节的大小实际上是等效的,因为没有块会大于一条消息,且没有消息会大于 16777215 字节。
Abort Message (2)
当一方在等待块以完成一条消息时,若不再需要处理该消息,可通过此消息通知对方丢弃在块流中部分接收的消息。消息的负载是块流 ID,应用程序在关闭连接等场景下,可用此消息表明不再需要处理后续消息。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chunk stream id (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
“终止消息” 协议消息的负载
- 块流 ID(32 位):此字段保存要丢弃当前消息的块流 ID。
Acknowledgement (3)
客户端或服务器在接收到等于窗口大小的字节数后,必须向对方发送确认消息。窗口大小指发送方在未收到接收方确认前可发送的最大字节数。该消息中的序列号表示目前已接收的字节数。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sequence number (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
“确认消息” 协议消息的负载
- 序列号(32 位):此字段保存到目前为止已接收的字节数。
Window Acknowledgement Size (5)
用于告知对方在发送确认消息之间使用的窗口大小。发送方发送窗口大小的字节数后,期望对方确认。接收方在收到指定数量的字节后(从上次确认后或会话开始计算),必须发送确认消息。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
“窗口确认大小” 协议消息的负载;
Set Peer Bandwidth (6)
用于限制对方的输出带宽。接收方根据此消息中的窗口大小,限制已发送但未确认的数据量,从而实现带宽限制。若窗口大小与上次发送给消息发送方的不同,接收方应回复 Window Acknowledgement Size 消息。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Limit Type |
+-+-+-+-+-+-+-+-+
“设置对等方带宽” 协议消息的负载
限制类型为以下值之一:
- 0 - 硬限制(Hard):对等方应将其输出带宽限制为指定的窗口大小。
- 1 - 软限制(Soft):对等方应将其输出带宽限制为此消息中指示的窗口大小或当前已生效的限制(取较小值)。
- 2 - 动态限制(Dynamic):如果先前的限制类型为硬限制,则将此消息视为标记为硬限制的消息进行处理;否则,忽略此消息。