协议分析与还原 RTMP协议

Posted on Tue 23 March 2021 in Protocol • 2 min read

introduction

RTMP协议全称为Adobe's Real Time Message Protocol(实时消息协议). 是一个建立在可靠网络链接上的双向消息多路复用的数据传输协议. 其设计用于传输视频, 音频以及数据消息

Definitions

这里对协议中一些特有的术语进行说明: - Message stream ID: 每一条消息都有一个独立的ID, 用于标记其自身. - Chunk: 一条消息的数据分片. 每条消息都会被拆分成为小的片段并在网络中发送. 每个Chunk保证其按照时间顺序, 端到端的进行发生以及重组. - Chunk Stream: Chunk在网络通信中的逻辑通道. - Chunk stream ID: 每片Chunk的ID, 用于标记其自身属于那条Message. - AMF(Action message Format): 一种用于序列化ActionScript(其Flash产品开发的一种基于ECMAScript的面向对象编程语言, 但现在Flash已被废弃, 并且现在RTMP协议也多用来单纯传输音视频文件, 不再传输FLASH的SWF文件)的Object Graph的二进制兼容格式, 其拥有2中版本, AMF0以及AMF3.

Overview

Overview

上图为RTMP协议的概览, 其上半部分被称为Chunk Stream(包括TimeStamp部分), 下半部分则为Message.

Chunk Stream

Chunk Stream 在术语说明中容易被认为其是消息的分片, 但实际上Chunk Stream 还除了包括消息分片外, 还可以作为一条"消息"独立使用(控制报文, 握手报文, 这些报文本身并不含有Message).

Handshake

一个RTMP协议开始于握手(握手就是典型的不含有Message的报文), 其分为三个阶段C0/S0, C1/S1, C2/S2. 其中C表示客户端, S表示服务, 每个阶段的客户端和服务端的握手报文结构相同. RTMP握手流程如下 handshake 每个阶段的报文结构如下C0/S0 CS0 这里的一个字节的数据用于标记本次通讯所使用的版本号, 目前有0, 1, 2, 3 四个版本, 但目前仅3为有效版本. CS1 C1和S1数据包长度固定为1536字节 CS2 其长度也固定为1536字节, 其中Time为C1/S1中的时间. 随机数回应(Random Echo)这一字段包含接收到C1/S1阶段的随机数.

Chunking

握手完成后,网络链接会复用一个或多个分块流,每个分块流承载来自同一个消息流的一类消息。每一个Chunk的格式如下图所示: chunk Basic Header: 1-3个字节, 该长度取决于Chunk Stream ID. Chunk Stream ID是一个变长的字段. 其有三种结构, 如下图所示. 当2-7bit数据为0, Basic Header长度为2字节, 2-7bit数据为1时Basic Header长度为3字节. 其余情况Basic Header长度为1字节. BasicHeader0 csid取值范围2-63 BasicHeader1 csid取值范围64-319 BasicHeader2 csid范围64-63399 注:这里的csid-64, 意为这8/16个字节的二进制数在加上64为真实的csid. 例如 ID为365时, 2-7bit为1, 余下的16bit为301.

Message Header: 11/7/3/0个字节, 该长度取决于Basic Header中指定的fmt字段. fmt有4种取值0, 1, 2, 3. 当fmt为0, Message Header结构如下图 fmt0 0号类型的header占11个字节, 必须用于一个块流的开头以及时间戳后退的情况 fmt1 1号类型的header占7个字节, 用于开头块流的后续消息(变长的消息载荷, 如视频) fmt2 2号类型的header占3个字节, 用于开头块流的后续消息(定长的消息载荷, 如部分音频或数据格式)

对于3号类型的header, 其占0个字节即没有Message Header, 用于装载后续的分片.

Extended Timestamp: 3个字节, 但是一个可选的字段 当Timestamp delta大于0xFFFFFF时, 该字段就会出现, 用来共同表示32位的时间戳. Chuck Data: 有效载荷

Message Stream

一条报文可以被分为Chunk Stream部分和Message Stream部分, 上一节中描述了Chunk Stream部分的字段. 本节讲解Message Stream部分的字段. Message Stream部分的格式如下图. 注: 下图的字段布局并不是真实的字段布局, 并且出现的Payload Length和TimeStamp字段是公共字段同时属于Chunk Message和Stream Message. 准确的字段布局见Overview中的图像. MessageStream

Message Type

Message Type 有Chunk header中的Message Type ID字段确定.

当Message Type ID为1, 2, 3, 5, 6时, 其代表的都为控制消息. |Message Type ID|效用| |--|--| |1|用来控制最大块的大小, 大小为32bit, 第0位恒定位0. 默认情况下Chunck的大小为128字节, 该报文可以将Chunk的大小设置为1-1677215(1 - 0xFFFFFFF). 但改控制消息的取值为31位二进制数.| |2|用来终止消息, 大小为32bit, 内容为要终止的Chunk Stream ID. | |3|用来确认消息, 大小为32bit, 内容为当前时间接受到的字节数总数.| |5|用来确认Windows大小, 大小为32bit, 内容为windows的大小. 接收端在接收到windows大小后必须发送确认消息(Message Type ID为3)| |6|用来设置对等带宽, 大小为40bit, 内容为32bit的windows大小和8bit的限制类型. 接收端收到消息后,通过将已发送但尚未被确认的数据总数限制为该消息指定的windows大小,来实现限制输出带宽的目的。 限制消息有3中类型, 0 - hard: 接受端的输出带宽限制为windows大小.1 - soft: 接收端的输出带宽限制为接收到的和当前的windows中较小的一方. 2 - Dynamic: 若上一条为Hard, 则本条也为hard. 否则本条无效.|

当Message Type ID为4时作为用户控制消息. 该消息携带事件类型和事件数据两部分. 其格式如下图 MTID4 其支持7种事件:

事件类型 & Event Type 描述
流开始 = 0 服务端发送该事件, 用来通知客户端一个流已经可以用来通讯了. 默认情况下, 该事件是在收到客户端连接指令并成功处理后发送的第一个事件. 事件的数据使用4个字节来表示可用的流的ID
流结束 = 1 服务端发送该事件, 用来通知客户端其在流中请求的回放数据已经结束了. 如果没有额外的指令, 将不会再发送任何数据, 而客户端会丢弃之后从该流接收到的消息. 事件数据使用4个字节来表示回放完成的流的ID
流枯竭 = 2 服务端发送该事件, 用来通知客户端流中已经没有更多的数据了. 如果服务端在一定时间后没有探测到更多数据, 它就可以通知所有订阅该流的客户端, 流已经枯竭. 事件数据用4个字节来表示枯竭的流的ID
设置缓冲区大小 = 3 客户端发送该事件, 用来告知服务端用来缓存流中数据的缓冲区大小(单位毫秒). 该事件在服务端开始处理流数据之前发送. 事件数据中, 前4个字节用来表示流ID, 之后的4个字节用来表示缓冲区大小(单位毫秒).
流已录制 = 4 服务端发送该事件, 用来通知客户端指定流是一个录制流. 事件数据用4个字节表示录制流的ID
ping请求 = 6 服务端发送该事件, 用来探测客户端是否处于可达状态. 事件数据是一个4字节的时间戳, 表示服务端分发该事件时的服务器本地时间. 客户端收到后用ping响应回复服务端
ping响应 = 7 客户端用该事件回复服务端的ping请求, 事件数据为收到的ping请求中携带的4字节的时间戳.

当Message Type ID为15/18时作为数据消息. 15表示AMF3编码, 18表示AMF0编码. 通过该消息发送元数据和其他用户数据元数据包括数据(音频、视频)的创建时间、时长、主题等详细信息

当Message Type ID为16/19时作为共享对象消息. 16表示AMF3编码, 19表示AMF0编码. 注: 共享对象是一个在多个客户端、示例之间进行同步的Flash对象(键值对集合). Flash已经废弃这里不再描述

当Message Type ID为20/17时作为指令消息. 17表示AMF3编码, 20表示AMF0编码. 发送这些消息来完成连接、创建流、发布、播放、暂停等操作

当Message Type ID为22作为组合消. 其格式如下 MTID22

当Message Type ID为8, 表示音频消息

当Message Type ID为9, 表示视频消息

Message Payload

Message Payload本身并不是RTMP协议标准种的内容, 但作为还原的一部分. 这里额外写入.

Message Payload Control

Control字段占1个字节, 其由2个4bit数据组成. 前4bit表示是哪一类型的数据, 后4bit表示其控制消息.

Audio

前4bit 中2bit 中1bit 后1bit
音频格式 采样率 音频大小 音频类型
0 = Linear PCM, 大顶端 0 =5.5Khz 0 = snd8bit 0 = sndMono
1 = ADPCM 1 = 11Khz 1 = snd16bit 1 = sndStereo
2 = MP3 2 = 22Khz 2 = snd16bit
3 = Linear PCM, 小顶端 3 = 44Khz
4 = Nellymoser 16khz mono
5 = Nellymoser 8khz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = Reserved
10 = AAC
11 = Speex
14 = MP3 8Khz
15 = Device-Specific sound

Video

前4bit 后4bit
帧类型 编码ID
1 = 关键帧, AVC 1 = JPEG
2 = 内部帧, AVC 2 = H263
3 = 一次性的内部帧, 仅H263 3 = ScreenVideo
4 = 生产的关键帧, 仅服务端可用 4 = On2 VP6
5 = 视频信息 控制帧 5 = 带Alpha通道的On2 VP6
6 = Screen Video 2
7 = AVC(H264)

结语

本文档主要用于协议还原, RTMP协议的详细过程并未准确描述. Payload中的Control 类型亦为描述完整. 若有兴趣看完全部文档请参见RTMP官方文档以及FLV官方文档. FLV文档中细致描述了Payload Control的类型.