流媒体:RTMP 协议完全解析
at 1年前 ca 网络协议 pv 652 by touch
背景
RTMP(Real Time Messaging Protocol) 是由 Adobe 公司基于 Flash Player 播放器对应的音视频 flv 封装格式提出的一种,基于TCP 的数据传输协议。本身具有稳定、兼容性强、高穿透的特点。常被应用于流媒体直播、点播等场景。常用于推推流方(主播)的稳定传输需求。
一、RTMP 的传输:消息块 & 消息封包传输
RTMP 协议为了维持稳定连续传递,避免单次传输数据量问题,采用了传输层封包,数据流切片的实现形式。被用来对当前带宽进行划分和复用的最小传输单位,被称为 Chunk 即消息块。通常情况下,一个有效的消息,如果数据量超出当前 Chunk Size 的话,则会被拆分成多个分块来分批传输。通过指定首个 Chunk 和后续 Chunk 类型,以及 Chunk Header 其他标志性数据,来使当前被切割的消息,能够在对端得到有效的还原和执行。我们以 MetaData 类型消息(Data AFM3 16)举例:
例子 中用来作为演示的 MetaData 高达 400 Bytes(不会吧?不会吧?不会吧?),而我们用 RTMP 的默认 Chunk Size 为 128 bytes。因此,当我们使用 Message Type 为 16 的 Data AFM3 类型的数据消息,通知对端当前元数据信息的时候,就需要切片了。即如图1-1 所示。
二、RTMP 的传输:消息块的组成
想要了解 RTMP 则必须对其使用的网络传输数据封装格式有一定的了解。RTMP 协议是以分组形式传送数据包。一个完整的数据块包含两个部分:Chunk Header 和 Chunk Data,这两者组合在一起,构成了一个有效的消息类型,结构如下:
基础数据头(Basic Header):保存 CS ID、Chunk Type(决定 Msg Header 类型)
消息数据头(Message Header):包含被发送消息的相关信息,类型Chunk Type决定
扩展时间戳(Extended Timestamp)(32-bits):消息头携带的时间戳扩展位
基础数据头
基础数据头,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 版。基础数据头组成,也包含三个部分。
format message type 标志位 fmt(2-bits):用来标志消息类型,也被称为 Chunk Type
cs id 字段(6-bits):用来表示 63 以内的ID的标志位,0、1两个标记被占用做扩展标记cs id - 64字段(8 or 16-bits):用来根据扩展标志扩充的,广范围标志位
需要注意的是,Chunk stream ID 是用来区分消息信道的。因为 RTMP 协议,所有的通信都是通过同一个 TCP 来完成的,因此所有类型的通信信道需要由 Chunk stream ID 来进行区分,从而判断当前收到的消息所属的信道类型。当然,这个是由用户定义的,不做区分其实也不影响实际操作(虽然 Adobe 官方有预留,但是规范约束是一种手段,自己做双端协定的时候也可以不按这种规范来,虽然那样就要做一系列的配套实现了),Adobe 建议及目前市面上大部分采用如下的分类以便于操作分割:
每个前后都有预留编号,以便扩充使用。
消息数据头
消息数据头的类型,是由基础数据头中的 fmt 字段来标记的。总共分为4种类型:
其格式划分如下:
timestamp 消息时间戳(3-Bytes):标记当前消息绝对时间戳,有效位 24 bits,如果超出16777215(0xFFFFFF)则启用扩展时间戳(Extended Timestamp)。扩展位启用时,timestamp 位恒定为 16777215,通过还原 32 bits 的扩展位,加合为有效时间戳数据。时间戳在运用上对于不同消息类型会有区分,type 0 时为绝对时间戳,type 1/2 时为相对时间戳(时间差值)。
msg length 消息头长度(1-Byte):携带 Chunk Header 数据长度信息(单位:Byte)
msg length (cont) 消息体长度(2-Bytes):携带 Chunk Data 数据长度信息(单位:Byte)
msg type 消息类型(1-Byte):携带消息类型信息,这是实际消息的类型,区别于消息头。
ms id 字段(1-Byte):消息归属消息流 ID 标志位,指定当前消息所属信道分类
ms id (cont) 字段(3-Bytes):消息内容对应数据流 ID 标志位,指定数据所属数据流信道
需要阐明的是, Chunk Data 实际归属对应为 ms_id (cont)。ms_id (cont) 才是数据流对应的流标记,这个标记是我们定义的。通常服务端会需要 ms_id (cont) 来指定我们当前的数据流。以便于 RTMP 链接建立后,指定数据通道。进一步理解的话,cs_id、ms_id、ms_id (cont) ,这三者之间并不存在强关联,仅仅作为不同信息层区分使用。数据和消息头可以毫无关联;也可以消息头根据c-s协定指定与引用关联数据。
一般情况下,单用 cs_id + ms_id (cont) 就够双端交互和信道分割了。
那么4种 Message Header 一般都在什么情况使用呢?我们可以参考下表:
扩展时间戳
扩展时间戳(Extended Timestamp)(32-bits)主要是配合 Message Header 内的时间戳使用,用来扩展可用时间范围。具体见上文 Message Header 消息时间戳说明。
三、RTMP 的数据:可用消息类型
需要注意的是,消息往往都需要分块发送。消息的类型只是消息本身格式的设定,和分块传输的传输过程是不同的概念。理解上,应该把消息格式理解为消息的信息排列样式;把传输过程理解为物理上发送数据的方案。
RTMP 消息的头(RTMP Message Header,不是 Message Header,两个不是同一个东西)有自己的统一格式,当然这部分也是会被切割到 Chunk 里传输的。不过,因为实际意义和 Chunk Header 内容重复,实际的实现上也可以不需要考虑这个(得约定好)。统一消息头样式如下:
消息头包含以下:
Message Type 消息类型(1-Byte):类型 ID 1 - 6 被保留用于协议控制消息。
Length 长度(3-Bytes):表示有效负载的字节数。以大端格式保存。
Timestamp 消息时间戳(4-Bytes):包含了当前消息的 timestamp。以大端格式保存。
Message Stream Id 消息流(3-Bytes):消息归属消息流 ID 标志位。以大端格式保存。
不同的消息对应不同的数据体,应该由具体的消息来制定其中数据体所携带的信息。所有类型消息都被列在下表中,以便于统一讲解:
接下来,我们来分别看一下各大消息类型(下文图文中的RTMP统一消息头省略)。
协议控制类消息
协议控制类消息(Protocol Control Messages)是用来控制通信过程中,整个基础通信配置的消息。用来负责协议双端的通信控制。总共有 5 种类型,以大分类的形式存在于可用消息列表中,各自格式如下(整合到一张图里了,注意区别):
chunk size 数据包最大可接受长度(31-bits):取值范围 1~2147483647 (0x7FFFFFFF) ,但因为消息总长度取值上限为16777215 (0xFFFFFF) ,因此不能超过该值(单位:Bytes)
chunk stream id 截停的信道 ID(4-Bytes):Abort 消息想要停止的信道ID
sequence number 已接收的数据长度(4-Bytes):发送后,对应长度的本地数据将会被标为已应答(Acked)(单位:Bytes)
Ack window size 应答窗口大小(4-Bytes):设置的应答窗口大小(单位:Bytes)
Limit Type 带宽配置模式(1-Byte):带宽配置模式有三种如下:
用户控制消息
用户控制消息(User Control Messages)被用来实时通知对端,以进行一些相关操作。其通用格式如下:
这些操作大多都是一些状态通知类型消息,或者网络状态测量类型的消息。官方在自己的开源实现里,定义了7种:
如有额外的操作协定,也可以在当前官方类型之外,自行定义其他类型。不过这样的话就需要双端都去做配套实现了。官方源代码这一块儿可以参考:
命令消息
命令消息(Command Messages)是用于 C-S 进行直接交互应答的一类消息。一般情况下,命令消息的发送对端,是需要对端进行应答信号反馈的。反馈消息规定以
[ 命令消息 ] + _result 或者 [ 命令消息 ] + _error
的形式由接收命令的一方(receiver),将结果信息发送回命令的发送方(sender)。以完成一次有效的命令操作。命令构成相对简单,其中携带的复杂数据则通过AMF编码的形式,存放在命令的消息体中。
一般情况下,命令消息的消息体的通用描述内容有,name 仅用做称谓:
这个命令体系很重要,详情请参考作者的精讲:
数据消息
客户端或者服务器端通过发送这些消息以发送元数据或者任何用户数据到对端。元数据包括数据 (音频,视频等等) 的详细信息,比如创建时间,时长,主题等等。这些消息可以进行 AMF0 编码生成消息类型 18 的 Data AMF0,也可以进行 AMF3 编码生成消息类型 15 的 Data AMF3。
一般情况下,数据消息的消息体的通用描述内容有,name 仅用做称谓:
音视频消息
音视频消息在结构上是一致的,本质都是纯粹的数据传输用途,即直接将音视频 Bytes 数据分块发送即可。没有多余的信息数据。
共享对象消息
共享对象消息是持有 Flash 对象,一种为了在多端多实例保持同步而设计的名-值对的集合对象,的消息类型。消息可以进行 AMF0 编码生成消息类型 19 的 Shared Object Message AMF0,也可以进行 AMF3 编码生成消息类型 16 的 Shared Object Message AMF3。每个共享消息对象,都可以包含有多个不同的共享事件。其通用格式如下:
统一描述信息主要携带共享对象名、版本和标记数据,共享事件名-值对集合则携带对应共享对象的一系列指定操作。共享对象消息的可用共享事件类型有:
因为共享对象消息是基于 Flash 类型的消息,在 Adobe 停止Flash 的支持后,在现在的音视频处理中,不太经常使用此类共享消息事件(也因为没有太多重要的大同步操作)。
整合消息
整合消息(Aggregate Message)被用来以消息包的形式来发送一系列上文中消息类型的集成表单。相当于用一个大的消息,将一些经过可回溯标记标示后的消息,按照指定的顺序编排后发出。整合消息对应的 ms id 将会覆盖持有子消息的 ms id(官方建议)。
统计消息里的 timestamp 和第一个子消息的 timestamp 的不同点在于子消息的 timestamp 被相对流时间标调整了偏移。每个子消息的 timestamp 都被加入偏移,以达到一个统一时间流。第一个子消息的 timestamp 应该和统计消息的 timestamp 一样,所以这个偏移量应该为 0。
Back Pointer 反向指针包含有前一个消息的大小 (包含前一个消息的头)。这样子匹配了 FLV 文件的格式,用于反向查找。
使用统计消息具有以下性能优势:
块流可以在一个块中以至多一个单一完整的消息发送。因此,增加块大小并使用统计消息减少了发送块的数量。
子消息可以在内存中连续存储。在网络中系统调用发送这些数据时更高效。
四、总结
至此,RTMP 基本梳理完毕。这些规格实际上都可以自行改动,但是需要双端一致。本质上,这套 RTMP 规范更多的是 Adobe 根据 Flash 的一系列特性制定的。所以使用中,还是会涉及到一些定制化的事情。不过,也可以直接使用。
参考文献:
[1] RTMP 官方规范(https://www.adobe.com/devnet/rtmp.html)
版权声明
本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。