目前国内比较常见的三种直播协议 RTMP、HLS、HTTP-FLV,其中rtmp是Adobe公司为Flash播放器和服务器之间提供音视频数据传输服务而设计的应用层私有协议,也是目前各大云厂商直线直播业务所公用的基本直播推拉流协议。本文主要是学习rtmp(实时消息传输协议,Real-Time Messaging Protocol )的学习笔记,并结合livego的源码分析,和大家一起深入学习RTMP协议最核心的知识点。
RTMP关键概念
Message是RTMP协议中基本的数据单元,不同种类的消息包含不同的Message Type ID,代表不同的功能。RTMP协议中一共规定了十多种消息类型,分别发挥着不同的作用。
在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输。RTMP协议中规定,消息在网络上传输时被拆分成消息块(Chunk)。
简单来说就是在一个 TCP 连接上,将需要传递的Message分成一个或者多个 Chunk,同一个Message 的多个Chunk 组成 ChunkStream,在接收端,再把 ChunkStream 中一个个 Chunk 组合起来就可以还原成一个完整的 Message
Message被拆分成一个或多个Chunk,然后在网络上发送
拆分的时候,默认的Chunk Size是128字节,以Message大小为300字节举例,进行拆分。
Message 拆分成 Chunk举例
消息主要分为三类: 协议控制消息、数据消息、命令消息等。
协议控制消息
Message Type ID = 1 2 3 5 6 和 Message Type ID = 4 两大类,主要用于协议内的控制,此部分后续详细分析。
数据消息
Message Type ID = 8 9 18
8: Audio 音频数据
9: Video 视频数据
18: Metadata 包括音视频编码、视频宽高等信息。
命令消息
Command Message (20, 17) 此类型消息主要有NetConnection 和 NetStream两个类,两个类分别有多个函数,该消息的调用,可理解为远程函数调用。
常见消息格式
Chunk格式
下面分别看看每个字段的含义
包含 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 是控制信息和一些命令信息。
Basic Header:1 byte
0 1 2 3 4 5 6 7 - - - - - - - - |fmt| cs id | - - - - - - - -
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。
0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - - - - - - - - - - - - - - - - |fmt| 0 | cs id - 64 | - - - - - - - - - - - - - - - -
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。
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 - - - - - - - - - - - - - - - - - - - - - - - - |fmt| 1 | cs id - 64 | - - - - - - - - - - - - - - - - - - - - - - - -
包含了要发送的实际信息(可能是完整的,也可能是一部分)的描述信息。Message Header的格式和长度取决于Basic Header 的chunk type(即fmt)共有四种不同的格式。其中第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。下面按字节从多到少的顺序分别介绍这四种格式的 Message Header。
Chunk Type(fmt) = 0:11 bytes
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。