Rtmp Chunk详解

一、简述

RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)。

每个块必须完整的发送后才能发送下一个块。接收端根据快流ID把块组装成完整的小消息。块允许把更高层协议的大消息分割成更小的消息分片,例如为了防止低优先级的大消息(如视频消息)阻碍高优先级的小消息(如音频和控制消息)。

二、Rtmp Chunk Header

   +--------------+----------------+--------------------+--------------+
   | Basic Header | Message Header | Extended Timestamp |  Chunk Data  |
   +--------------+----------------+--------------------+--------------+
   |<------------------- Chunk Header ----------------->|
                               Chunk Format

Chunk Header 分为4部分,

2.1基本头:Base Header(1-3字节)

RTMP 协议最多支持 65597 个流(chunk),CS ID(块流ID) 范围为: 3 ~ 65599。ID 0、1、2 被保留。

基本头分为三种情况由Base Header第一个字节后六个bits决定

  • (后六个bits==0):基本头长度为2字节第二个字节表示块流ID
  • (后六个bits==1):基本头长度为3字节,第二三个字节表示快流ID
  • (1<后六个bits<=64):基本头长度为1字节,后6bits表示块流ID

1).第一个字节后六bits为0的基本头结构如下: 

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |fmt|     0     |   cs id - 64  |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk basic header 2

 Basic Header 为 3字节的情况 (第一个字节后 6 位为 000001)

块流ID从64到65599。块流ID= 第三个byte*256+(第二个byte+64)

2). 第一个字节后六bits为1的基本头结构如下: 

 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt|     1     |        cs id - 64             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           Chunk basic header 3

3).第一个字节后六bits为cs id的基本头结构如下:

 

 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+
 |fmt|   cs id   |
 +-+-+-+-+-+-+-+-+
 Chunk basic header 1

Basic Header 为 1字节的情况 (第一个字节后 6 位为 cs id)

块流ID从2到63

CSID 保留值

0、1、2 被保留, 3 ~ 8 基本都是固定用途,所以 9 ~ 65599 才用于自定义 csid,但一般我们用不到。

  • 0 表示 Basic Header 总共要占用 2 个字节
  • 1 表示 Basic Header 总共要占用 3 个字节
  • 2 代表该 chunk 是控制信息和一些命令信息
  • 3 代表该 chunk 是客户端发出的 AMF0 命令以及服务端对该命令的应答
  • 4 代表该 chunk 是客户端发出的音频数据,用于 publish
  • 5 代表该 chunk 是服务端发出的 AMF0 命令和数据
  • 6 代表该 chunk 是服务端发出的音频数据,用于 play;或客户端发出的视频数据,用于 publish
  • 7 代表该 chunk 是服务端发出的视频数据,用于 play
  • 8 代表该 chunk 是客户端发出的 AMF0 命令,专用来发送: getStreamLength, play, publish

2.2.消息头:Message Header(0-11字节)

2.2.1.消息头结构有4种类型

  • 类型0:fmt == 0:Message Header 长度为 11
  • 类型1:fmt == 1:Message Header 长度为 7
  • 类型2:fmt == 2:Message Header 长度为 3
  • 类型3:fmt == 3:Message Header 长度为 0

1).类型0

timestamp(4bytes)+message length(3bytes)+message type id(1byte)+message stream id(4bytes)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                   timestamp                   |message length |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     message length (cont)     |message type id| msg stream id |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           message stream id (cont)            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       Chunk Message Header - Type 0

注意: 此时 timestamp 为绝对时间戳

2).类型1

timestamp(4bytes)+message length(3bytes)+message type id(1byte)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |              timestamp delta                  |message length |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     message length (cont)     |message type id| 
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk Message Header - Type 1

fmt为1的情况下省略了message stream id,这个id与上一个chunk message相同

注意: 此时 timestamp 为相对时间戳

3).类型2

timestamp(4bytes)

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |              timestamp delta                  |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 Chunk Message Header - Type 3

fmt为2的情况下省略了message length、message type id和message stream id,省略的这个与上一个chunk message 相同

该类型的消息是固定长度与固定类型的消息,也就是说每次发的消息大小与格式相同,例如音频流格式,在第一个新的 chunk 以后使用该类型。

注意: 此时 timestamp 为相对时间戳

3).类型3

fmt为3的情况下Message Header全部省略了,跟上一个chunk Message完全相同

类型3使用的情况大多是因为消息太大比如视频消息,一个chunk中放不下,要分chunk发送,第一个chunk后的消息使用该类型,比如关键帧,动不动就上万字节的数据,而且时间戳类型都是固定的,所以使用该类型发送数据。

2.2.2.时间戳

只有在类型0的情况下时间戳是绝对时间戳其他类型的时间戳都是相对时间戳(时间戳增量)。从librtmp与srs中观察分析得到服务器与客户端推流的时间戳是相同的(本人都实现了,所以认为相同,如有其它情况请大神告知)

在此说下在rtmp中时间戳怎么打:

一般情况下这个时间戳都是从绝对时间戳开始也就是从0开始,音视频都有自己的时间戳(这一点很重要),音视频采集过来的时候会自带采集时间戳,伪代码如下:

private int GetVideoTimestamp(MediaFrame mediaFrame)
{
    if (视频时间戳== 0)
    {
        _timestampVideo = mediaFrame.采集时间戳;
        return 0;
    }
    else
    {
        return mediaFrame.采集时间戳 - 视频时间戳;
    } 
}

2.2.3.消息长度

消息长度指的是Header以后的所有字节数

2.2.4.消息类型

  • 1 设置块大小
  • 2 中断消息,丢弃旧数据
  • 3 确认
  • 4 用户控制消息
  • 5 设置确认窗口大小
  • 6 设置流带宽
  • 7 音频数据
  • 9 视频数据
  • 15(0x0f). AMF3 数据
  • 16(0x10) AMF3 共享对象事件
  • 17(0x11) AMF3 命令
  • 18(0x12) AMF0 数据
  • 19(0x13) AMF0 共享对象事件
  • 20(0x14) AMF0 命令,Invoke 方法调用
  • 22(0x16) 聚合消息, H.264, 类似 FLV 文件存储格式,每个音视频包作为一个 Tag, 许多的 Tag 组成了这个 AMFType=0x16 的数据类型

2.2.5.消息ID

这个ID是四个字节的小端数据,这个ID默认置1即:0x01 0x00 0x00 0x00

2.3扩展时间戳:Extended Timestamp(0-4字节)

类型为1或2的块里,本字段代表当前块和上一个块的时间戳之差。如果时间间隔大于等于16777215(0xFFFFFF),此字段的取值必须为16777215,并且与扩展时间戳一起组成32比特的完整时间戳。如果时间戳小于16777215,那么此字段代表了完整的时间戳。

三、示例

3.1音频示例

一下是普通的音频帧发送方式,音频消息长度比较小,一般不用分包,直接全包发送即可,但是如下方式发送会导致部分消息的冗余。

 +---------+-----------------+-----------------+-----------------+
 |         |Message Stream ID| Message Type ID | Time  | Length  |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 1 |    12345        |         8       | 0     |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 2 |    12345        |         8       | 32    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 3 |    12345        |         8       | 64    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 4 |    12345        |         8       | 96    |   512   |
 +---------+-----------------+-----------------+-------+---------+

如下方式解决了Chunk的消息冗余。消息按类型0、类型2、类型3、类型3... 的方式发送,第二包因为不确定时间戳增量因此用类型二来确定,从第三包以后因为时间戳增量也是相同的就可以用类型三来发送。

 +---------+-----------------+-----------------+-----------------+
 |         |Message Stream ID| Message Type ID | Time  | Length  |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 1 |    12345        |         8       | 0     |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 2 |    12345        |         8       | 32    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 3 |    12345        |         8       | 64    |   512   |
 +---------+-----------------+-----------------+-------+---------+
 | Msg # 4 |    12345        |         8       | 96    |   512   |
 +---------+-----------------+-----------------+-------+---------+

注意:本人在实现服务器的过程中,发现从各个其他组发来的流的时间戳是各式各样的,尤其音频的时间戳更是奇葩的厉害,根本不会出现使用类型3的情况,干脆就直接用类型1来传输音频,再者音视频都是相间交错发的。

3.2视频示例

视频消息由于太长即使你把Chunk Size设的再大也不行,一般Chunk Size都设成4096(0x1000)。

 +-----------+-------------------+-----------------+-----------------+
 |           | Message Stream ID | Message Type ID | Time  | Length  |
 +-----------+-------------------+-----------------+-----------------+
 | Msg # 1   |       12346       |    9 (video)    |   0   | 20000   |
 +-----------+-------------------+-----------------+-----------------+

下面是分割后的Chunk。消息按类型0、类型3、类型3、类型3... 的方式发送,因为整个帧的时间戳都是一个时间戳。

 +-------+------+-----+-------------+-----------+------------+
 |       |Chunk |Chunk|Header       |No. of     |Total No. of|
 |       |Stream| Type|Data         |Bytes after| bytes in   |
 |       | ID   |     |             | Header    | the chunk  |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#1|  4   |  0  | delta: 0    |  4096     | 4096+12    |
 |       |      |     | length:9192 |           |            |
 |       |      |     | type: 9,    |           |            |
 |       |      |     | stream ID:  |           |            |
 |       |      |     | 12346       |           |            |
 |       |      |     | (11 bytes)  |           |            |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#2|  4   |  3  | none        | 4096      | 4096+1     |
 |       |      |     | (0 bytes)   |           |            |
 +-------+------+-----+-------------+-----------+------------+
 |Chunk#3|  4   |  3  | none        | 1000      | 1000+1     |
 |       |      |     | (0 bytes)   |           |            |
 +-------+------+-----+-------------+-----------+------------+

注意:在类型0的的Chunk中一定要设定整个消息的长度,在本实例中的长度为9192(4096+4096+1000)这个别算错了。这里的视频帧是按RTMP发送视频帧格式拼装完后再整体进行Chunk 分块发送的。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章