RTMP协议详解及Wiresahrk抓包分析(一)https://developer.aliyun.com/article/1472344
2) Message Header(消息头消息)
包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header 的格式和长度取决于 Basic Header 的 chunk type,共有 4 种不同的格式,由上面所提到的 Basic Header 中的 fmt 字段控制。以下按照字节数从多到少的顺序分别介绍这 4 种格式的 Message Header。
Type=0
type=0
时 Message Header 占用 11 个字节,其他三种能表示的数据它都能表示,但在 chunk stream 的开始的第一个 chunk 和头信息中的时间戳后退(即值与上一个 chunk 相比减小,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式。timestamp
(时间戳):占用 3 个字节,因此它最多能表示到 16777215=0xFFFFFF=2 24 − 1 2^{24} -1224−1,当它的值
超过这个最大值时,这三个字节都置为 1,这样实际的 timestamp 会转存到 Extended Timestamp 字段中,接收端在判断 timestamp 字段 24 个位都为 1 时就会去 Extended timestamp 中解析实际的时间戳。message length
(消息数据的长度) :占用 3 个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是 Message 的长度,也就是 chunk 属于的 Message 的总数据长度 ,而不是 chunk 本身 Data 的数据的长度。message type id
(消息的类型 id):占用 1 个字节,表示实际发送的数据的类型,如 8 代表音频数据、9 代表视频数据。msg stream id
(消息的流 id) :占用 4 个字节,表示 该 chunk 所在的流的 ID(注:我还是不甚明白这个 ID 到底是指什么 stream ID,既然 basic header 里已经有 chunk stream id 了,为什么这又冒出个 msg stream id,下面提到了“省去了表示 msg stream id 的 4 个字节,表示此 chunk 和上一次发的 chunk 所在的流相同”,是针对流链接说的,那么意思就是说 msg stream id 和 chunk stream id 指的是一个东西咯?) ,和 Basic Header 的 CSID 一样,它采用小端存储的方式。
Type=1
type=1
时 Message Header 占用 7 个字节,省去了表示 msg stream id 的 4 个字节,表示此 chunk 和上一次发的 chunk 所在的流相同,如果在发送端只和对端有一个流链接的时候可以尽量去采取这种格式。timestamp delta
:占用 3 个字节,注意这里和 type=0 时不同,存储的是和上一个 chunk 的时间差。类似上面提到的 timestamp,当它的值超过 3 个字节所能表示的最大值时,三个字节都置为 1,实际的时间戳差值就会转存到 Extended Timestamp 字段中,接受端在判断 timestamp delta 字段 24 个位都为 1 时就会去 Extended timestamp 中解析时机的与上次时间戳的差值。
Type=2
type=2
时 Message Header 占用 3 个字节,相对于 type=1 格式又省去了表示消息长度的 3 个字节和表示消息类型的 1 个字节,表示此 chunk 和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同 type=1。
Type=3
- 0 字节!!!好吧,它表示这个 chunk 的 Message Header 和上一个是完全相同的,自然就不用再传输一遍了。当它跟在 Type=0 的 chunk 后面时,表示和前一个 chunk 的时间戳都是相同的。什么时候连时间戳都相同呢?就是一个 Message 拆分成了多个 chunk,这个 chunk 和上一个 chunk 同属于一个 Message。而当它跟在 Type=1 或者 Type=2 的 chunk 后面时,表示和前一个 chunk 的时间戳的差是相同的。比如第一个 chunk 的 Type=0,timestamp=100,第二个 chunk 的 Type=2,timestamp delta=20,表示时间戳为 100+20=120,第三个 chunk 的 Type=3,表示 timestamp delta=20,时间戳为 120+20=140。
3) Extended Timestamp(扩展时间戳)
上面我们提到在 chunk 中会有时间戳 timestamp 和时间戳差 timestamp delta,并且它们不会同时存在,只有这两者之一大于 3 个字节能表示的最大数值 0xFFFFFF=16777215 时,才会用这个字段来表示真正的时间戳,否则这个字段为 0。扩展时间戳占 4 个字节,能表示的最大数值就是 0xFFFFFFFF=4294967295。
当扩展时间戳启用时,timestamp 字段或者 timestamp delta 要全置为 1,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值。
4) chunk data(块数据)
用户层面上真正想要发送的与协议无关的数据,长度在 (0,chunkSize]
之间。
5) chunk 表
示例一
- 首先包含第一个 Message 的 chunk 的 Chunk Type 为 0,因为它没有前面可参考的 chunk,timestamp 为 1000,表示时间戳。type 为 0 的(注:应该是 message header)header 占用 11 个字节,假定 chunkstreamId 为 3<127,因此 Basic Header 占用 1 个字节,再加上 Data 的 32 个字节,因此第一个 chunk 共 44=11+1+32 个字节
- 第二个 chunk 和第一个 chunk 的 CSID,TypeId,Data 的长度都相同,因此采用 Chunk Type=2,timestamp delta=1020-1000=20,因此第二个 chunk 占用 36=3+1+32 个字节。
- 第三个 chunk 和第二个 chunk 的 CSID,TypeId,Data 的长度和时间戳差都相同,因此采用 Chunk Type =3 省去全部 Message Header 的信息,占用 33=1+32 个字节。
- 第四个 chunk 和第三个 chunk 情况相同,也占用 33=1+32 个字节。
示例二
- 注意到 Data 的 Length=307 > 128,因此这个 Message 要切分成几个 chunk 发送,第一个 chunk 的 Type=0,Timestamp=1000,承担 128 个字节(注:Chunk 的默认大小是 128 字节)的 Data,因此共占用 140=11+1+128 个字节。
- 第二个 chunk 也要发送 128 个字节,其他字段也同第一个 chunk,因此采用 Chunk Type=3,此时时间戳也为 1000,共占用 129=1+128 个字节。
- 第三个 chunk 要发送的 Data 的长度为 307-128-128=51 个字节,还是采用 Type=3,共占用 1+51=52 个字节
④、协议控制消息(Protocal Control Message)
在 RTMP 的 chunk 流会用一些特殊的值来代表协议的控制消息,它们的 Message Stream ID 必须为 0(代表控制流信息),CSID 必须为 2,Message Type ID(注:message type ID 在 message header 里面)可以为 1,2,3,5,6,具体代表的消息会在下面依次说明。控制消息的接收端会忽略掉 chunk 中的时间戳,收到后立即生效。(注:我所理解的协议控制消息应该是放在 chunk data 里的)
Set Chunk Size(Message Type ID=1)
:设置 chunk 中 Data 字段所能承载的最大字节数,默认为 128B,通信过程中可以通过发送该消息来设置 chunk Size 的大小(不得小于 128B),而且通信双方会各自维护一个 chunkSize,两端的 chunkSize 是独立的。比如当 A 想向 B 发送一个 200B 的 Message,但默认的 chunkSize 是 128B,因此就要将该消息拆分为 Data 分别为 128B 和 72B 的两个 chunk 发送,如果此时先发送一个设置 chunkSize 为 256B 的消息,再发送 Data 为 200B 的 chunk,本地不再划分 Message,B 接受到 Set Chunk Size 的协议控制消息时会调整的接受的 chunk 的 Data的大小,也不用再将两个 chunk 组成为一个 Message。
- 以下为代表 Set Chunk Size 消息的 chunk 的 Data: 其中第一位必须为 0,chunk Size 占 31 个位,最大可代表 2147483647=0x7FFFFFFF=2 31 − 1 2^{31}-1231−1,但实际上所有大于 16777215=0xFFFFFF 的值都用不上,因为 chunk size 不能大于 Message 的长度,表示 Message 的长度字段是用 3 个字节表示的,最大只能为 0xFFFFFF。
Abort Message(Message Type ID=2)
:当一个 Message 被切分为多个 chunk,接受端只接收到了部分 chunk 时,(注:这里缺少主语,我所理解的是 “发送端发送该控制消息”)发送该控制消息表示发送端不再传输同 Message 的 chunk,接受端接收到这个消息后要丢弃这些不完整的 chunk。Data 数据中只需要一个 CSID,表示丢弃该 CSID 的所有已接收到的 chunk。Acknowledgement(Message Type ID=3)
:当收到对端的消息大小等于窗口大小(Window Size)时接受端要回馈一个 ACK 给发送端告知对方可以继续发送数据。窗口大小就是指收到接受端返回的 ACK 前最多可以发送的字节数量,返回的 ACK 中会带有从发送上一个 ACK 后接收到的字节数。(注:这里其实我没怎么看懂。大概是对窗口大小的一个确认信息吧,暂不深究。)Window Acknowledgement Size(Message Type ID=5)
:发送端在接收到接受端返回的两个 ACK 间最多可以发送的字节数Set Peer Bandwidth(Message Type ID=6)
:限制对端的输出带宽。接受端接收到该消息后会通过设置消息中的 Window ACK Size 来限制已发送但未接受到反馈的消息的大小来限制发送端的发送带宽。如果消息中的 Window ACK Size 与上一次发送给发送端的 size 不同的话要回馈一个 Window Acknowledgement Size 的控制消息。Hard(Limit Type=0)
:接受端应该将 Window Ack Size 设置为消息中的值Soft(Limit Type=1)
:接受端可以讲 Window Ack Size 设为消息中的值,也可以保存原来的值(前提是原来的 Size 小与该控制消息中的 Window Ack Size)Dynamic(Limit Type=2)
:如果上次的 Set Peer Bandwidth 消息中的 Limit Type 为 0,本次也按 Hard 处理,否则忽略本消息,不去设置 Window Ack Size。
5、RTMP message
不同类型的 RTMP message
Command Message(命令消息,Message Type ID=17 或 20)
:表示在客户端和服务器间传递的在对端执行某些操作的命令消息(注:简单来说就是通知对方要开始干啥的命令),如 connect 表示连接对端,对端如果同意连接的话会记录发送端信息并返回连接成功消息,publish 表示开始向对方推流,接受端接到命令后准备好接受对端发送的流信息,后面会对比较常见的 Command Message 具体介绍。当信息使用 AMF0 编码时,Message Type ID=20,AMF3 编码时 Message Type ID=17。Data Message(数据消息,Message Type ID=15 或 18)
:传递一些元数据(MetaData,比如视频名,分辨率等等)或者用户自定义的一些消息。当信息使用 AMF0 编码时,Message Type ID=18,AMF3 编码时 Message Type ID=15。Shared Object Message(共享消息,Message Type ID=16 或 19)
:表示一个 Flash 类型的对象,由键值对的集合组成,用于多客户端,多实例时使用。当信息使用 AMF0 编码时,Message Type ID=19,AMF3 编码时 Message Type ID=16。Audio Message(音频信息,Message Type ID=8)
:音频数据。Video Message(视频信息,Message Type ID=9)
:视频数据。Aggregate Message (聚集信息,Message Type ID=22)
:多个 RTMP 子消息的集合User Control Message Events(用户控制消息,Message Type ID=4)
:告知对方执行该信息中包含的用户控制事件,比如 Stream Begin 事件告知对方流信息开始传输。和前面提到的协议控制信息(Protocol Control Message)不同,这是在 RTMP 协议层的,而不是在 RTMP chunk 流协议层的,这个很容易弄混。该信息在 chunk 流中发送时,Message Stream ID=0,Chunk Stream Id=2,Message TypeId=4。
①、Command Message(命令消息,Message Type ID=17 或 20)
发送端发送时会带有命令的名字,如 connect,TransactionID 表示此次命令的标识,Command Object 表示相关参数。接受端收到命令后,会返回以下三种消息中的一种:_result 消息表示接受该命令,对端可以继续往下执行流程,_error 消息代表拒绝该命令要执行的操作,method name 消息代表要在之前命令的发送端执行的函数名称。这三种回应的消息都要带有收到的命令消息中的 TransactionId 来表示本次的回应作用于哪个命令。
可以认为发送命令消息的对象有两种,一种是 NetConnection,表示双端的上层连接,一种是 NetStream,表示流信息的传输通道,控制流信息的状态,如 Play 播放流,Pause 暂停。
1) NetConnection Commands(连接层的命令)
用来管理双端之间的连接状态,同时也提供了异步远程方法调用(RPC)在对端执行某方法,以下是常见的连接层的命令。
connect
:用于客户端向服务器发送连接请求,消息的结构如下
消息的回应有两种,_result 表示接受连接,_error 表示连接失败。Call
:用于在对端执行某函数,即常说的 RPC:远程进程调用,消息的结构如下:
如果消息中的 TransactionID 不为 0 的话,对端需要对该命令做出响应,响应的消息结构如下:
Create Stream
:创建传递具体信息的通道,从而可以在这个流中传递具体信息,传输信息单元为 Chunk
2) NetStream Commands(流连接上的命令)
Netstream 建立在 NetConnection 之上,通过 NetConnection 的 createStream 命令创建,用于传输具体的音频、视频等信息。在传输层协议之上只能连接一个 NetConnection,但一个 NetConnection 可以建立多个 NetStream 来建立不同的流通道传输数据。以下会列出一些常用的 NetStream Commands,服务端收到命令后会通过 onStatus 的命令来响应客户端,表示当前 NetStream 的状态。
onStatus 命令的消息结构如下
:
play(播放)
: 由客户端向服务器发起请求从服务器端接受数据(如果传输的信息是视频的话就是请求开始播流),可以多次调用,这样本地就会形成一组数据流的接收者。注意其中有一个 reset 字段,表示是覆盖之前的播流(设为 true)还是重新开始一路播放(设为 false)。
play2(播放)
: 和上面的 play 命令不同的是,play2 命令可以将当前正在播放的流切换到同样数据但不同比特率的流上,服务器端会维护多种比特率的文件来供客户端使用 play2 命令来切换。
deleteStream(删除流)
:用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流。
receiveAudio(接收音频)
:通知服务器端该客户端是否要发送音频,receiveAudio 命令结构如下:
receiveVideo(接收视频)
:通知服务器端该客户端是否要发送视频,receiveVideo 命令结构如下:
publish(推送数据)
:由客户端向服务器发起请求推流到服务器。publish 命令结构如下:
seek(定位流的位置)
: 定位到视频或音频的某个位置,以毫秒为单位。seek 命令的结构如下:
pause(暂停)
:客户端告知服务端停止或恢复播放。pause 命令的结构如下:
如果 Pause 为 true 即表示客户端请求暂停的话,服务端暂停对应的流会返回 NetStream.Pause.Notify 的 onStatus 命令来告知客户端当前流处于暂停的状态,当 Pause 为 false 时,服务端会返回 NetStream.Unpause.Notify 的命令来告知客户端当前流恢复。如果服务端对该命令响应失败,返回_error 信息。
RTMP协议详解及Wiresahrk抓包分析(三)https://developer.aliyun.com/article/1472346