rtmp协议详解

版权声明:本文为卫伟学习总结文章,转载请注明出处!

1.handshake
1.1.概述

rtmp连接从握手开始。它包含三个固定大小的块。客户端发送的三个块命名为c0,c1,c2;服务端发送的三个块命名为S0,S1,S2。
握手序列:

  • 客户端通过发送c0和c1消息来启动握手过程。客户端必须收到S1消息,然后发送C2消息。客户端必须接收到S2消息,然后发送其他数据。
  • 服务端必须接收到C0或者C1消息,然后发送S0和S1消息。服务端必须接收到C2消息,然后发送其他数据。
握手示意图
rtmp协议详解_第1张图片
1.2.complex handshake
1.2.1 C0和S0格式

C0和S0包由一个字节组成,下面是C0/S0包内的字段:
rtmp协议详解_第2张图片
  • version(1 byte) : RTMP的版本,一般为3
1.2.2 C1和S1格式

C1和S1包含两部分数据: key和digest,分别为如下:
rtmp协议详解_第3张图片

key 和 digest 的顺序是不确定的,也有可能是:(nginx-rtmp中是如下的顺序):
rtmp协议详解_第4张图片

764 bytes key 结构:
random-data: (offset) bytes
  • key-data: 128 bytes
  • random-data: (764 - offset - 128 - 4) bytes
  • offset: 4 bytes

764 bytes digest 结构:

  • offset: 4 bytes
  • random-data: (offset) bytes
  • digest-data: 32 bytes
  • random-data: (764 - 4 - offset - 32) bytes
1.2.3 C2和S2格式

rtmp协议详解_第5张图片
hanshake:S0 + S1 + S2
rtmp协议详解_第6张图片

1.3 simple handshake

1.3.1 C0和S0格式

version(1byte):版本。在C0包内,这个字段代表客户端请求的RTMP版本号。在S0包内,这个字段代表服务端选择的RTMP版本号。当前使用的版本是3。在版本0-2用在早期的产品中,如今已经弃用;版本4-31被预留用于后续产品;版本32-255(为了区分RTMAP协议和文本协议,文本协议通常是可以打印字符)不允许使用。如果服务端无法识别客户端的版本号,应该回复版本3。客户端可以选择降低到版本3,或者终止握手过程。

1.3.2 C1和S1格式

C1和S1包长度为1536字节,包含以下字段:
rtmp协议详解_第7张图片
  • time(4 bytes) : 本字段包含一个时间戳,客户端应该使用此字段来标识所有流块的时刻。时间戳取值可以为零或者任意值。为了同步多个块流,客户端可能希望多个块流使用相同的时间戳。
  • zero (4 bytes): 本字段必须为零。
  • random (1528 bytes):本字段可以包含任意数据。由于握手的双方需要区分另一端,此字段填充的数据必须足够随机(以防止与其他握手端混淆)。不过没有必要为此使用加密数据或动态数据。
1.3.3 C2和S2格式

C2 和 S2 包长度为 1536 字节,作为 C1 和 S1 的回应,包含以下字段:
rtmp协议详解_第8张图片
  • time(4 bytes):本字段必须包含对端发送的时间戳。
  • time2(4 bytes):本字段必须包含时间戳,取值为接收对端发送过来的握手包的时刻。
  • random(1528 bytes):本字段必须包含对端发送过来的随机数据。握手的双方可以使用时间 1 和时间 2 字段来估算网络连接的带宽和/或延迟,但是不一定有用。
2.组块
2.1块格式
rtmp协议详解_第9张图片
  • 块的基本头(1-3字节): 这个字段包含块流ID和块类型。块的类型决定了编码过的消息头的格式。这个字段是一个变长字段,长度取决于块流ID。
  • 消息头(0,3,7,11字节):这个字段包含被发送的消息信息(无论是全部,还是部分)。字段长度由块头中的块类型来决定。
  • 扩展时间戳(0,4字节): 这个字段是否存在取决于块消息头中编码的时间戳。
  • 块数据(可变大小):当前块的有效数据,上限为配置的最大块大小。
2.2 Basic Header

包含chunk stream ID(流通道id)和chunk type(即fmt), chunk stream id 一般被简写为CSID,用来唯一标识一个特定的流通道,chunk type决定了后面的Message Header的格式。Basic Header的长度可能是1,2,或者3个字节,其中chunk type的长度是固定的(占2位,单位是bit),Basic Header的长度取决于CSID的大小,在足够存储这两个字段的前提下最好使用最少的字节从而减少由于引入Header增加的数据量。

RTMP协议支持用户自定义[3,65599] 之间的 CSID,0, 1, 2 由协议保留表示特殊信息。0 代表 Basic Header 总共要占用 2 个字节,CSID 在 [64,319] 之间; 1 代表占用 3 个字节,CSID 在 [64,65599] 之间; 2 代表该 chunk 是控制信息和一些命令信息。

2.2.1 Basic Header: 1byte
rtmp协议详解_第10张图片
2.2.2 Basic Header: 2 byte, csid == 0

CSID占14bit,此时协议将于chunk type所在字节的其他bit都置为0,剩下的一个字节表示CSID - 64,这样共有8个bit来存储 CSID,8 bit 可以表示 [0,255] 个数,因此这种情况下 CSID 在 [64,319],其中 319 = 255 + 64。
rtmp协议详解_第11张图片
1571629877(1).jpg
2.2.3 Basic Header: 3 bytes, csid == 1

CSID占22bit,此时协议将第一个字节的[2,8]bit置1,余下的16个bit表示CSID - 64,这样共有16个bit来存储CSID,16bit可以表示[0,65535]共 65536 个数,因此这种情况下 CSID 在 [64,65599],其中65599=65535+64,需要注意的是,Basic Header是采用小端存储的方式,越往后的字节数量级越高,因此通过3个字节的每一个bit的值来计算CSID时,应该是: <第三个字节的值> * 256 + <第二个字节的值> + 64.
rtmp协议详解_第12张图片
2.3 Message Header

包含了要发送的实际消息(可能是完整的,也可能是一部分)的描述消息。Message Header的格式和长度取决于Basic Header的chunk type,即fmt,共有四种不同的格式。其中一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。下面按字节从多到少的顺序分别介绍这四种格式的 Message Header。

Message Header四种消息头格式
一、Chunk Type(fmt)=0:11bytes
rtmp协议详解_第13张图片

type=0时Message Header占用11个字节,其他三种能表示的数据它都能表示,但Chunk stream的开始第一个chunk和头信息中时间戳后退(即值与上一个chunk相比减少,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式。

  • timestamp(时间戳):占用3个字节,因此它最多能表示16777215=0xFFFFFF=2^24-1,当它的值超过这个最大值时,这三个字节都置为1,这样实际的timestamp会转存到 ExtendedTimestamp 字段中,接收端在判断timestamp字段24个位都为1时就会去Extended Timestamp中解析实际的时间戳。
  • message length(消息数据长度):占用3个字节,表示实际发送的消息的数据如音频、视频帧等数据的长度,单位时字节。注意这里时Message的长度,也就是chunk属于Message的总长度,而不是chunk本事data的长度。
  • message type id(消息的类型id): 1个字节,表示实际发送的数据的类型,如8代表音频数据,9代表视频数据。
  • message stream id(消息的流id): 4个字节,表示该chunk所在的流的ID,和Basic Header的CSID一样,采用小端存储方式。
二、Chunk Type(fmt)=1:7bytes
rtmp协议详解_第14张图片

type为1时占用7个字节,省去了表示message stream id的4个字节,表示此chunk和上一次发的chunk所在的流相同,如果在发送端和对端有一个流链接的时候可以尽量采用这种格式。

  • timestamp delta:3 bytes,这里和type=0时不同,存储的是和上一个chunk的时间差。类似上面提到的timestamp,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际的时间戳差值就会转存到Extended Timestamp字段中,接收端在判断timestamp delta字段24个bit都为1时就会去Extended Timestamp 中解析实际的与上次时间戳的差值。
  • 其他字段与上面的解释相同。
三、Chunk Type(fmt)=2: 3 bytes
rtmp协议详解_第15张图片

type 为 2 时占用 3 个字节,相对于 type = 1 格式又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此 chunk和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同type=1。

四、Chunk Type(fmt)=3: 0byte

type=3时,为0字节,表示这个chunk的Message Header和上一个是完全相同的。当它跟在type=0的chunk后面时,表示和前一
个 chunk 的时间戳都是相同。什么时候连时间戳都是相同呢?就是一个 Message 拆分成多个 chunk,这个 chunk 和上一个 chunk 同属于一个 Message。而当它跟在 type = 1或 type = 2 的chunk后面时的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。

2.4 Extended Timestamp(扩展时间戳)

在 chunk 中会有时间戳 timestamp 和时间戳差 timestamp delta,并且它们不会同时存在,只有这两者之一大于3字节能表示的最大数值 0xFFFFFF = 16777215 时,才会用这个字段来表示真正的时间戳,否则这个字段为 0。扩展时间戳占 4 个字节,
能表示的最大数值就是 0xFFFFFFFF = 4294967295。当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,而不是减去时间戳或者时间戳差的值。

2.5 chunk 示例
2.5.1 chunk 示例1

本示例展示了一个音频消息流。流中包含有冗余信息。
rtmp协议详解_第16张图片
  • 分析第一个chunk:
    -1 首先包含第一个Message的chunk的chunk type为0,因为它前面没有可参考的chunk,timestamp为1000,表示时间戳。
    -2 type为0的header占用11个字节,假定chunk stream id为3 < 127,因此basic header占用1个字节;
    -3 再加上data的32字节,因此第一个chunk共44个字节=11+1+32个字节。
  • 分析第二个chunk:
    -1. 第二个chunk与第一个chunk的cs id和chunk type id,以及data的长度都相同,因此采用类型2;
    -2. 可知timestamp delta = 1020 -1000 = 20;
    -3. 因此第二个chunk占用36 = 3 (message header) + 1(basic header) +32
  • 分析第三个chunk:
    -1. 第三个 chunk 和第二个 chunk 的 cs id ,chunk type id,以及 data 的长度和时间戳的差值都相同,因此采用 类型 3,省去全部的 Message Header 的信息;
    -2. 因此占用 33 = 1 + 32
  • 分析第四个chunk:
    -1.第四个 chunk 和第三个 chunk 情况相同,也占用 33 = 1 + 32 个字节。

最后实际发送的chunk如下面表格所示,该表格展示了由此音频流产生的块信息。从第 3 条信息开始,数据传输达到最大优化。每条消息的头部只增加了 1 字节长度。

你可能感兴趣的:(rtmp协议详解)