龙空技术网

rtmp 协议详解

Linux特训营 388

前言:

当前看官们对“nginxrtmp减少延迟”大约比较讲究,兄弟们都需要剖析一些“nginxrtmp减少延迟”的相关内容。那么小编在网摘上收集了一些关于“nginxrtmp减少延迟””的相关文章,希望姐妹们能喜欢,兄弟们一起来学习一下吧!

1. handshake

免费视频资料关注+后台私信;资料;两个字可以免费视频领取+文档+各大厂面试题 资料内容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,嵌入式 等

1.1 概述

rtmp 连接从握手开始。它包含三个固定大小的块。客户端发送的三个块命名为 C0,C1,C2;服务端发送的三个块命名为

S0,S1,S2。

握手序列:

客户端通过发送 C0 和 C1 消息来启动握手过程。客户端必须接收到 S1 消息,然后发送 C2 消息。客户端必须接收到

S2 消息,然后发送其他数据。服务端必须接收到 C0 或者 C1 消息,然后发送 S0 和 S1 消息。服务端必须接收到 C2 消息,然后发送其他数据。握手示意图

1.2 complex handshake1.2.1 C0 和 S0 格式

C0 和 S0 包由一个字节组成,下面是 C0/S0 包内的字段:

version(1 byte):RTMP 的版本,一般为 3。1.2.2 C1 和 S1 格式

C1和S1包含两部分数据:key和digest,分别为如下

key 和 digest 的顺序是不确定的,也有可能是:(nginx-rtmp中是如下的顺序):

764 bytes key 结构:

random-data: (offset) byteskey-data: 128 bytesrandom-data: (764 - offset - 128 - 4) bytesoffset: 4 bytes

764 bytes digest 结构:

offset: 4 bytesrandom-data: (offset) bytesdigest-data: 32 bytesrandom-data: (764 - 4 - offset - 32) bytes
1.2.3 C2 和 S2 格式

1.3 simple handshake1.3.1 C0 和 S0 格式

C0 和 S0 包由一个字节组成,下面是 C0/S0 包内的字段:

version(1 byte):版本。在 C0 包内,这个字段代表客户端请求的 RTMP 版本号。在 S0 包内,这个字段代表服务端选

择的 RTMP 版本号。当前使用的版本是 3。版本 0-2 用在早期的产品中,如今已经弃用;版本 4-31 被预留用于后续产

品;版本 32-255 (为了区分 RTMP 协议和文本协议,文本协议通常是可以打印字符)不允许使用。如果服务器无法识别

客户端的版本号,应该回复版本 3,。客户端可以选择降低到版本 3,或者终止握手过程。1.3.2 C1 和 S1 格式

C1 和 S1 包长度为 1536 字节,包含以下字段:

time(4 bytes):本字段包含一个时间戳,客户端应该使用此字段来标识所有流块的时刻。时间戳取值可以为零或其他

任意值。为了同步多个块流,客户端可能希望多个块流使用相同的时间戳。zero(4 bytes):本字段必须为零。random (1528 bytes):本字段可以包含任意数据。由于握手的双方需要区分另一端,此字段填充的数据必须足够随机

(以防止与其他握手端混淆)。不过没有必要为此使用加密数据或动态数据。1.3.3 C2 和 S2 格式

C2 和 S2 包长度为 1536 字节,作为 C1 和 S1 的回应,包含以下字段:

time(4 bytes):本字段必须包含对端发送的时间戳。time2(4 bytes):本字段必须包含时间戳,取值为接收对端发送过来的握手包的时刻。random(1528 bytes):本字段必须包含对端发送过来的随机数据。握手的双方可以使用时间 1 和时间 2 字段来估算

网络连接的带宽和/或延迟,但是不一定有用。2. 组块2.1 块格式

块的基本头(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:1 byte

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。

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.

2.3 Message Header

包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和长度取决于Basic Header

的chunk type,即fmt,共有四种不同的格式。其中第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基

于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意

义的数据。下面按字节从多到少的顺序分别介绍这四种格式的 Message Header。

Message Header 四种消息头格式。一、Chunk Type(fmt) = 0:11 bytes

type=0时Message Header占用11个字节,其他三种能表示的数据它都能表示,但在chunk stream 的开始第一个chunk和头信息

中的时间戳后退(即值与上一个chunk相比减小,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式。

timestamp(时间戳):占用3个字节,因此它最多能表示到16777215=0xFFFFFF=2^24-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代表视频数据。message stream id(消息的流id):4个字节,表示该chunk所在的流的ID,和Basic Header

的CSID一样,它采用小端存储方式。二、Chunk Type(fmt) = 1:7 bytes

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

type 为 2 时占用 3 个字节,相对于 type = 1 格式又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此 chunk

和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同type=1。

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

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

本示例展示了一个音频消息流。流中包含有冗余信息。

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

最后实际发送的chunk如下面表格所示,该表格展示了由此音频流产生的块信息。从第 3 条信息开始,数据传输达到最大优

化。每条消息的头部只增加了 1 字节长度。

2.5.2 chunk 示例2

本示例展示了一条长消息,由于消息的长度超过了块的最大长度(128字节),此消息在传输时将被分割成若干个块。

由表格知 data 的长度 307 > 128,因此这个 Message 要分割成几个 chunk 发送:

第一个 chunk:type = 0,timestamp = 1000,承担 128 个字节的 data,因此共占用 140 = 11 + 1 + 128 个字节。第二个 chunk:同样要发送 128 字节,其他字段(即Message Header 中的几个字段)都与第一个相同,因此采用 类型 3,共 129 = 1 + 128 字节。第三个 chunk:要发送的 data 的长度为 307 - 128 - 128 = 51 字节,还是采用 类型 3,共 1 + 51 = 52 字节。

下面是消息分割后产生的块:

第一个块的头数据显示了消息的长度为 307 字节。

在这两个示例中,类型为 3 的块有两种使用方式。第一种是说明消息的继续。第二种是说明新消息的头信息可以由前面已经存

在的消息推到出来。

3. 协议控制消息

RTMP 块流使用消息类型 ID 1、2、3、5、6 作为控制消息。这些消息包含了必要的 RTMP 块流协议信息。

这些协议控制消息必须使用 0 作为消息流ID(作为已知的控制流ID),同时使用 2 作为块流ID。协议控制消息接收立即生效;

解析时,时间戳字段被忽略。

3.1 设置块大小 (1)

协议控制消息(1),设置块大小,被用来通知对方新的最大的块大小。

默认最大的块大小为 128 字节,客户端和服务器可以使用此消息来修改默认的块大小。例如,假设客户端想要发送的音频数据

大小为131 字节,而块大小为 128 字节。在这种情况下,客户端可以通知服务器新的块大小为 131 字节,然后就可以使用一

个块来发送完整的音频数据了。

最大的块大小至少为 128 字节,块至少携带 1 个字节的内容。通信的每一个方向(例如从客户端到服务器)拥有独立的块大小

设置。

0:当前比特位必须为零。chunk size(31 bits): This field holds the new maximum chunk size, in bytes, which will be used for all of the sender's

subsequent chunks until further notice. Valid sizes are 1 to 2147483647(0x7FFFFFFF) inclusive; however, all sizes

greater than 16777215(0xFFFFFF) are equivalent since no chunk is larger than onemessage, and no message is larger than

16777215 bytes.块大小(31比特):本字段标识了新的最大块大小,以字节为单位,发送端之后将使用此值作为最大的块大小。本字段的

有效值为 1 - 2147483647(0x7FFFFFFF),由于消息的最大长度为 16777215(0xFFFFFF),而一个块最多只能携带一条消

息,因此本字段的实际有效值为 1~16777215(0xFFFFFF)。send chunk size3.2 中断消息 (2)

协议控制消息(2),中断消息,用来通知通信的对方,如果正在等待一条消息的部分块(已经接收了一部分),那么可以丢弃

之前已经接收到的块。通信的一方将接收到块流ID作为当前协议消息的有效数据。应用程序可以发送此消息来通知对方,当前

正在传输的消息没有必要再处理了。

块流ID(32比特):本字段包含了块流ID,用来标识哪个块流ID的消息将被丢弃。3.3 应答(3)

客户端和服务器在接收到与接收窗口大小相等的数据后,必须发送应答消息给对方。窗口大小的定义为发送方在接收到接收方

的任何应答前,可以发送的最大数据量。本消息包含了序列号,序列号为截至目前接收到的数据总和,以字节为单位。

序列号(32比特):本字段包含了截止目前接收到的数据总和,以字节为单位。3.4 应答窗口大小(5)

客户端和服务器发送这个消息来通知对方应答窗口的大小。发送方在发送了等于窗口大小的数据之后,等待接收对方的应答消

息(在接收到应答之前停止发送数据)。接收方必须发送应答消息,在会话开始时,或从上一次发送应答之后接收到了等于窗

口大小的数据。

3.5 设置流带宽(6)

客户端和服务器发送此消息来说明对方的出口带宽限制。接收方以此来限制自己的出口带宽,即限制未被应答的消息数据大

小。接收到此消息的一方,如果窗口大小与上次发送的不一致,应该回复应答窗口大小的消息。

限制类型的取值为下面之一:

硬限制(0):应该限制出口带宽为指明的窗口大小。软限制(1):应该限制出口带宽为指明的窗口大小,或已经生效的小一点的窗口大小。动态限制(2):如果上一次为硬限制,此消息被视为硬限制,否则忽略此消息。send bandwidth4. Command Message (17 或 20)

Command Message(命令消息,Message Type ID = 17 或 20):表示在客户端和服务器间传递的在对端执行某些操作的命令

消息,connect 表示连接对端,对端如果同意连接的话就会记录发送端信息并返回连接成功消息,publish 表示开始向对方

推流,接收端接收到命令后准备好接收对端发送的流信息。当信息使用 AMF0 编码时,Message Type ID = 20,AMF3 编码

时 为 17。

服务器和客户端之间使用 AMF 编码的命令消息交互。 一些命令消息被用来发送操作指令,比如 connect,createStream,

public,play,pause。另外一些命令消息被用来通知发送方请求命令的状态,比如 onstatus,result 等。一条命令消息

包括命令对称、交互 ID、包含相关参数的命令对象。服务器和客户端通过在创建的流中远程调用的方式,使用命令消息来

进行交互。

服务器发送给客户端的命令结构如下:

总结:

免费视频资料关注+后台私信;资料;两个字可以免费视频领取+文档+各大厂面试题 资料内容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,嵌入式 等

标签: #nginxrtmp减少延迟 #nginxrtmp时间戳