RTMP协议

Posted 只会哈哈哈的Yoo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RTMP协议相关的知识,希望对你有一定的参考价值。

RTMP

​ RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议属于应用层协议,依赖于可靠的传输层协议,一般为TCP。RTMP是一种设计用来进行实时通信的网络协议,主要用来在Flash平台和支持RTMP协议的流媒体/交互服务器之间进行以视频和数据通信。直播场景中使用RTMP协议比较多。

​ RTMP 中给定的时间戳是从一个未指定的时间点开始的相对时间的整数毫秒数。通常,每个流都将从时间戳为 0 为起始时间,但这不是必须的,只要两端的能在时间点上达成一致。这意味着不同流(尤其是来自不同的主机)的同步需要 RTMP 之外的同步机制。此外,由于时间戳数据项为32位,其表示最大值大约将近50天左右,而实际应用中流被要求持续不断的传输,时间甚至可达一年之久,故其实现的应用程序在处理时间戳时应采用序列号算法,合理的解决回绕问题。而时间戳增量同样被定义成相对于前一个时间戳的无符号相对毫秒数。

handshake 握手

​ RTMP协议建立连接的过程,也需要有握手的过程。RTMP协议是基于TCP的,因此首先是TCP建立连接有三次握手,在TCP连接建立以后,再进行RTMP协议层次的握手。RTMP的握手顺序为:

客户端发送 C0 和 C1 块开始握手。

客户端必须等接收到 S1 后才能发送 C2。

客户端必须等接收到 S2 后才能发送其它数据。

服务器必须等接收到 C0 才能发送 S0 和 S1,也可以等接到 C1 一起之后。

服务器必须等到 C1 才能发送 S2。

服务器必须等到 C2 才能发送其它数据。

​ 在实际协议应用中,一般是由客户端发送C0 和 C1 块,服务器收到 C1后同时发送S0、S1、S2给客户端,在客户端收到S1后,发送C2给服务器,完成握手。rtmp协议握手交换的数据报文是固定大小的,客户端向服务端发送的3个报文为C0、C1、C2,服务端向客户端发送的3个报文为S0、S1、S2。C0与S0的大小为1个字节,C1与S1的大小为1536个字节,C2与S2的大小为1536个字节。以上为RTMP标准协议中约定内容,被称为简单握手,但在实际实现时Adobe采用一种被复杂握手。

C0和S0格式

+++++++++++++++++++

version

C0 和 S0 包只有1个字节,可以看成一个 1字节的整数,表示RTMP所采用的版本,0、1、2是旧版本号,已经弃用;4-31被保留为rtmp协议的未来实现版本使用;32-255不允许使用。

简单握手 C0和S0一致,都是一个字节,都代表当前使用的rtmp协议的版本号。如果服务器端或者客户端收到的C0/S0字段解析出为非03,对端可以选择以版本3来响应,也可以放弃握手。

复杂握手 说明是明文还是密文。如果使用的是明文(0X03),同时代表当前使用的rtmp协议的版本号。如果是密文,该位为0x06。

C1和S1格式

++++++++++++++++++++++

time(4 bytes)
zero(4 bytes)
random bytes
random bytes
· · ·

C1 和 S1 包有 1536 个长节长。

简单握手 time(4字节)+zero(4字节)+random data(1528字节)

time(4字节): 包含一个timestamp,用于本终端发送的所有后续块的时间起点。其值可以是0,或者任意值。要同步多个块流,终端可以发送其他块流当前的timestamp的值,以此让当前流跟要同步的流保持时间上的同步。

zero(4字节): 必须都是0。如果不是0,代表要使用complex handshack。

random data(1528字节) : 可以包含任意值。终端需要区分出响应来自它发起的握手还是对端发起的握手,这个数据应该发送一些足够随机的数。这个不需要对随机数进行加密保护,也不需要动态值。

复杂握手 time(4字节)+version(4字节)+key(764字节)+digest(764字节)

​ 其中,key和digest可能会交换位置,也就有两种格式:schemal0 & schemal1

time(4字节) : 同上。

version(4字节): 4bytes 为程序版本。C1一般是0x80000702,S1是0x04050001,要采用非0值与简单握手区分

key(764字节) : random-data:长度由这个字段的最后4个byte决定,即761-764

​ key-data:128个字节。Key字段对应C1和S1有不同的算法,发送端(C1)中的key应该是随机 的,接收端(S1)的key需要按照发送端的key去计算然后返回给发送端。

​ random-data:(764 - offset - 128 - 4)个字节

​ key_offset:4字节, 最后4字节定义了key的offset(相对于KeyBlock开头而言,相当于第一个 random_data的长度)

digest(764字节): offset:4字节, 开头4字节定义了digest的offset

​ random-data:长度由这个字段起始的4个byte决定

​ digest-data:32个字节。Digest字段对应C1和S1有不同的算法,其由key构造。

​ random-data:(764 - 4 - offset - 32)个字节

C2和S2格式

++++++++

time(4 bytes)
time2(4 bytes)
random echo
random echo
· · ·

C2 和 S2 包有 1536 个长节长。

简单握手 time(4字节)+time2(4字节)+random echo(1528字节)

Time(4个字节) :必须包含终端在S1 (给 C2) 或者 C1 (给 S2) 发的 timestamp。

Time2 (4个字节):必须包含终端先前发出数据包 (s1 或者 c1) timestamp。

Randomecho (1528个字节):必须包含终端发的 S1 (给 C2) 或者 S2 (给 C1) 的随机数。两端都可以一起使用 time 和 time2 字段再加当前 timestamp 以快速估算带宽和/或者连接延迟,但这不太可能是有多大用处。

复杂握手 random data(1504字节)+digest-data(32字节)

random-data和digest-data都应来自对应的数据(对C2来说是S1,对S2来说是C1)

​ C2、S2digest算法如下:

​ 第一步:计算一个临时key,var temp_key = HmacSHA256(FMSKey, 68, c1.Digest);

​ 第二步:计算joinedArray;

​ 第三步:使用临时key计算digest,var digest = HmacSHA256(temp_key, 32, joinedArray);

chunk 块流

块流是通信信道的抽象 握手结束后,连接对一个或者多个块流进行合并。每个块流承载了一个消息流中的一种类型的消息。每一个块在创建时都有一个唯一的 ID,称为块流 ID。块通过网络进行传输。传输过程中,每一个块必须完全发送才能发送下一个块。在接收端,通过块流 ID 块被组装成消息。

消息流是数据流的抽象 通常情况下,一个块流在一段时间内仅传输一个消息流,但也有可能将不同消息流混合到相同的块流中,这会使得不能有效的压缩头部信息。当一个消息流关闭后,可以由其他消息流复用其块流。

++++++++++++

每一个块由块头和数据组成。块头由三个部分组成:

Basic HeaderMessage HeaderExtended TimestampChunk Data

|<---------------------------------------- Chunk Header ------------------------------------------------>|

基本头(Basic Header,1 到 3 字节):将对块流 ID 和块类型进行了编码。块类型标明了消息头的编码格式。字段的长度取决于块流 ID,因为块流是一个变长的字段。

消息头(Message Header,0、3、7 和 11 字节):对发送的消息(部分或和全部)的信息进行了编码。这个字段的长度取决于块头的块类型。

扩展时间戳(Extended Timestamp,0和 4 字节):是否出现取决于块消息头中的时间戳(timestamp)或时间戳增量(timestamp delta)。

块数据(Chunk Data,可变大小):块的有效负载,最大长度为配置的最大的块的大小。

Basic Header 基本头

+++++++++++++++++++

​ 基本头对块流ID(csid)和块类型(fmt)进行了编码,fmt决定了Chunk Header的编码格式,块基本头的长度取决于块流ID,可能是1,2,3个字节长度。协议支持ID为365599,共65597个流,0,1,2为保留值,0表示基本头为2字节形式,ID范围为64319;1表示基本头为3字节形式,ID范围为6465599;而363范围内的ID采用1字节基本头。块流ID为2保留用做底层协议的控制消息和命令。

块流ID在2~63之间编进 1 字节版本的基本头的流块ID字段

fmtcsid

块流ID在64~319之间编进 2 字节版本的基本头的流块ID字段

fmt0csid - 64

块流ID在64~65599之间编进 3 字节版本的基本头的流块ID字段

fmt1csid - 64

csid (6 bits) : 包含了块流 ID,值范围是 2-63。值为 0 和 1 用来表示这个字段是 2 或 3 字节的版本。

fmt (2 bits) : 段标识了“块消息头”4 种格式中的 1 种。

csid - 64 (8bits / 16 bits) : 块流 ID 减去 64。ID =(第二个字节+64)/ (第三个字节)*256+第二个字节+64)

Message Header 消息头

+++++++

Type0

timestampmessage lengthmessage type idmessage stream id

Chunk Type(fmt) = 0:type=0时,msg header占用11个字节,其他三种能表示的数据它都能表示。

timestamp : 时间戳,占用3个字节,它最多能表示2^24-1= 16777215,当它的值超过这个最大值时,这三个字节都置为1,而实际的timestamp会转存到Extended Timestamp字段中,接收端发现timestamp字段24位全为1时,就会去Extended Timestamp字段解析实际的时间戳。

message length : 消息数据的长度,占用3个字节,表示实际发送消息数据的长度,单位是字节。这个长度是message的长度,也就是chunk所属message的总大小,而不是chunk本身Data的长度。(message在发送时,可能被会分割成多个chunk发送。)

message type id : 消息类型id,占用1个字节,表示实际发送数据的类型,如8代表音频数据,9代表视频数据。

message stream id : 占用4个字节,表示该chunk所在流的id,采用小端存储。通常情况下,相同的chunk stream中的所有消息都来自同一个message stream。

Type1

timestamp deltamessage lengthMessage type id

Chunk Type(fmt) = 1:type=1时,Message Header占用7个字节,省去了表示message stream id的4个字节,表示此chunk和上一次发送的chunk所在流相同。

timestamp delta : 时间戳差,占用3个字节,注意这里和type=0时不同,存储的是和上一个chunk的时间差。类似timestamp,当它的值超过3个字节所能表示的最大值时,三个字节全置为1,实际的时间戳差值就会转存到Extended Timestamp字段中,接收端发现timestamp delta字段全为1时,就会去Extended timestamp中解析时间戳差值。

Type2

timestamp delta

Chunk Type(fmt) = 2:type=2时,Message Header占用3个字节,相对于type=1又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此chunk和上一次发送的chunk所在的流、消息长度和消息的类型都相同。timestamp delta同type=1。

Type3

Chunk Type(fmt) = 3:type=3时,Message Header占用0个字节,表示这个chunk的Message Header和上一个完全相同。当它跟在type=0的chunk后面时,表示和上一个chunk的时间戳都是相同的,就是一个Message拆分成了多个chunk,这个chunk和上一个chunk同属于一个Message。而当它跟在type=1或type=2的chunk后面时,表示和前一个chunk的时间戳差是相同的。比如第一个chunk的type=0,timestamp=1000,第二个chunk的type=2,timestamp delta=20,表示时间戳为1000+20=1020,第三个chunk的type=3,表示timestamp delta=20,时间戳为1000+20+20=1040。

Extended Timestamp

+++++++++++++++

在chunk中会有时间戳timestamp和时间戳差值timestamp delta,并且它们不会同时存在,当这两者大于3个字节能表示的最大数值0xFFFFFF=16777215时,才会用这个字段来表示真正的时间戳,否则这个字段长度为0。当扩展时间戳启用时,timestamp字段或者timestamp delta字段需要全置为1,表示使用扩展时间戳字段。

Protocol Control Message 协议控制消息

RTMP块流采用类型为1,2,3,4,5,6的消息用于协议控制消息,其包含了RTMP块流协议需要的信息。这个协议控制消息必须使用ID为0的消息流,并使用ID为2的块流发送,协议控制消息在接收到后被优先处理,其时戳忽略不用。

Set Chunk Size 设置块流格式


协议控制消息1,被用来通知对端一个新的最大块的大小。

最大快大小默认你为128字节,但客户端或服务器可以修改这个值,并利用此消息通知对方。块的最大值大小应该至少为128字节,必须至少大于1个字节,且每个方向上独立维护块的最大值大小。其有效负载长度为32bit,第1位必须为0,后续31位表示块的最大值,单位是字节,合法大小为1到2147483647(0x7fffffff)。发送者随后的所有快均采用这个值,直到再次发送发送该消息去设置块的最大值。此外,所有大于1677215(0xfffff)的值都是等价的,因为块的长度并不会超过所在消息的长度,没有消息会超过1677215字节。

Abort Message 中止消息


协议控制消息2,被用来通知对端如果正在等待接受消息的块那么就丢弃已通过块流接收的消息。对端通过这个控制消息的负载接收到流块ID(4字节)。应用程序在关闭的时候将发送这个消息,表示不需要在继续处理消息。

Acknowledgement 确认


协议控制消息3,客户端和服务端必须在收到字节数等于窗口大小后向对端发送确认消息。窗口大小是发送者收到确认前能发送的最大字节数。消息的负载为序列号(4字节),指到目前为止接受到的字节数。

User Control Message 用户控制消息


协议控制消息4,被用于用户控制消息。客户端或服务器发送这个消息以通知对端用户控制事件。该消息包含了事件类型和时间具体数据。消息体前2字节用以表示时间类型,事件数据紧随其后,且长度可变。

Windows Acknowledgement Size 窗口确认大小


协议控制消息5,客户端和服务端发送该消息用以告诉对端两次确认发送间的窗口大小。发送者在发送了窗口大小的字节数后期望收到对方的确认消息。接受方必须在接收到指定大小的数据后进行一次确认,如果之前没有发送过确认,则在该消息后开始计算字节数发送确认。其负载为确认窗口大小(4字节)

Set Peer Bandwidth 设置对端带宽


协议控制消息6,客户端和服务端发送该消息用以限制对端的输出带宽。接受方在接收到该消息后应限制自己的输出带宽。除去还没有做出确认的消息外,接收方应限制其输出带宽为消息指定的大小。如果上一个窗口大小与最后一次收到的确认大小不同,该消息的接受这应回复一个窗口确认大小的消息。

RTMP Message Formats 消息格式

服务器或者客户端通过网络发送RTMP消息相互通信,消息可以包括音频、视频、控制数据等各种类型。RTMP消息可以分成两部分,一个是头部,一个是负载。

Message Header 消息头


Message TypePayload LengthTimestampStream ID

消息头由以上几个字段组成:

消息类型(Message Type):1个字节,表示消息类型,类型ID(1-6)保留给协议控制消息。

负载长度(Payload Length):3个字节,表示有效负载的长度,大端格式。

时间戳(Timestamp):4个字节,表示消息的时间戳,大端格式。

消息流ID(Stream ID):3个字节,表示消息流的ID,大端格式。

Message Payload 消息负载


消息的其他部分就是有效负载,其包含了实际要传输的数据,例如可能是编码后的视频或音频数据。具体形式协议并不关心,有服务器和客户端保持一致即可。

RTMP Command Message 命令消息

其类型丰富,功能多样,且同一消息具有不同编码方式(AMF0/AMF3),其消息类型也不相同。

命令消息(Command Message(AMF0/20, AMF3/17))

数据消息(Data Message(AMF0/18, AMF3/15))

共享对象消息(Share Object Message(AMF0/19, AMF3/176)

音频消息(Audio Message(8))

视频消息(Video Message(9))

复合消息(Aggregate Message(22))

用户控制消息事件(User Control Message Events)

客户端与服务器交换数据使用AMF编码的命令,发送者发送一个由命令名称、事务ID以及相关参数的命令对象构成的命令消息,用以rpc调用、传递音频视频数据、控制信息等等。

其具体内容请参考《Adobe’s Real Time Messaging Protocol》相关协议标准,同时各个流媒体服务器在实现上均有各自的取舍,可能仅保证最基础功能,不一定会覆盖全部协议内容。

RTMP规范协议

 

本文参照rtmp协议英文版,进行简单的协议分析

1、什么是RTMP

关于 Adobe 的实时消息协议(Real Time Messaging Protocol,RTMP),是一种多媒体的复用和分组的应用层协议,通过某种可靠的传输协议(例如 TCP)传输数据流(例如音频,视频和交互数据)。

2、字节序、对齐和时间戳

字节序:所有整数字段的表示都使用网络字节序,零字节在最前面显示,同时零位是字或字段的最重要的位(注:零位是符号位)。这种字节序通常被称为大端(big-endian)。

对齐:除了其它指定外,所有的 RTMP 协议都是单字节对齐的;比如说,一个 16 位的字段起始位置可能是一个奇数偏移。如果需要填充,填充字节应该(SHOULD)是 0。

时间戳:RTMP 中给定的时间戳是从一个未指定的时间点开始的相对时间的整数毫秒数。通常,每个流都将从时间戳为 0 为起始时间。

注:因为时间戳是 32 位长,它们将在 49 天,17 小时,2 分 47.296 秒后重新开始。由于流被要求持续不断的传输,可能要运行几年后才结束,所以 RTMP 应用程序在处理时间戳时应该(SHOULD)使用序列号算法(Serial NumberArithmetic),应该(SHOULD)能处理这个环绕的情形。比如

,应用程序应该认为所有相邻的时间戳之差都在 2^31-1 之内(译者注:2^31 表示 2 的 31 次方,32 位的无符号数最大能表示 2^32 - 1=4294967295),所 1000 应该在 4000000000 之后,而 3000000000 在 4000000000 之前。

3、消息

消息是RTMP协议中基本的数据单元。不同种类的消息包含不同的Message Type ID,代表不同的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不同的作用。例如,Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需操作其中的数据。Message Type ID为8,9的消息分别用于传输音频和视频数据。Message Type ID为15-20的消息用于发送AMF编码的命令,负责用户与服务器之间的交互,比如播放,暂停,停止等。消息首部(Message Header)有四部分组成:标志消息类型的Message Type ID,标志消息长度的Payload Length,标识时间戳的Timestamp,标识消息所属媒体流的Stream ID。消息的报文结构如图所示。

 

 4、消息块

在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输。RTMP协议中规定,消息在网络上传输时被拆分成消息块(Chunk)。消息块首部(Chunk Header)有三部分组成:用于标识本块的Chunk Basic Header,用于标识本块负载所属消息的Chunk Message Header,以及当时间戳溢出时才出现的Extended Timestamp。消息块的报文结构如图所示。

在消息被分割成几个消息块的过程中,消息负载部分(Message Body)被分割成大小固定的数据块(默认是128字节,最后一个数据块可以小于该固定长度),并在其首部加上消息块首部(Chunk Header),就组成了相应的消息块。消息分块过程如图5所示,一个大小为307字节的消息被分割成128字节的消息块(除了最后一个)。

RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去。接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。

 

以上是关于RTMP协议的主要内容,如果未能解决你的问题,请参考以下文章

视频接入网关是什么?RTSP转RTMP作用

流媒体协议RTMP、RTSP与HLS有啥不同?

RTMP协议

RTMP规范协议

RTMP和GB28181两种视频上云协议的选择

流媒体传输协议之 RTMP