janus

RTMP协议(三)分块与块包装

2020-07-03  本文已影响0人  Seacen_Liu

RTMP 是一个多媒体数据传播协议,相比于HTTP这些超文本协议,多媒体传输的音频和视频信息都相对较大。对于单个较大的信息可能会阻塞连接,导致优先级更高的信息无法传递,因此较大的信息一般都需要分包发送

为了满足分包发送需求,RTMP 在传输的时候会对较大的数据进行分块(chunking操作,简而言之,就是将较大的信息分割成一个个的子信息,且不会超过配置的固定大小。发送端需要保证当前完整的“分包”信息发送完成再发送下一个“分包”,那么接收端就可以利用TCP有序到达的优势在对端进行重组。

“分包”由于是都来自同一个大信息,因此在包头的描述处会存在大量的冗余数据。为了解决传输过程中的冗余数据,RTMP设计出一个特殊的数据格式——块(chunk,对需要传输的信息进行了块包装,在有序可靠的TCP的基础下利用不同类型的块尽可能消除冗余数据。从而可以令RTMP用较小的开支发送更多的小消息。

块大小利弊

虽然RTMP提供了分块(chunking操作与块(chunk包装的设计,然而在块大小的选择上我们需要根据实际情况权衡利弊

块流与信息流

RTMP 握手完成之后,连接开始对一个或多个块流进行多路传输。通过块流ID(Chunk Stream ID可以将同一个块流(Chunk Stream块(Chunk整理在一起,然后将块(Chunk信息(message根据信息流ID(Messsage Stream ID重新连接在一起。当一个完整的音视频信息接收完成后,接收端就可以继续做解码等一系列工作了。

块格式

每个块包含一个块头和块数据体,其中块头包含三个部分:

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

Basic Header

Basic Header(基本头信息)用于存放整个块头的基本信息,包括chunk stream ID(块流ID)和chunk type(块类型)。同时,Basic Header 应该使用最小的容量存放chunk typechunk stream ID,进而减少引入 Header 增加的数据量。

Basic Header 字节数形式与CSID的表示范围:

一字节版本 - CSID范围为3~63

一字节中没有辅助的字段,而是直接将一字节剩下的6bits用作存放CSID

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

CSID = <第一个字节的值(fmt=0)>

二字节版本 - CSID范围为64~319

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

条件: <第一个字节的值(fmt=0)> == 0

CSID = <第二个字节的值> + 64

三字节版本 - CSID范围为64~65599

  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           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

条件: <第一个字节的值(fmt=0)> == 1

CSID = <第三个字节的值> * 256 + <第二个字节的值> + 64

64~319 既可以用二字节形式也可用三字节形式表示,为了数据大小最好用二字节,不过具体还是看实际情况

Message Header

Message Header(块信息头)用于存放实际信息的描述信息。它有四种不同的格式,由 Basic Header 中的chunk typefmt进行区分。其中,第一种格式可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前chunk的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。根据chunk typefmt取值为0123,我们将其命名为类型 0、类型 1、类型 2、类型 3。

类型 0(fmt = 0, 11 bytes)

类型 0 的块头信息长度为 11 个字节,它能表示其他三种类型的数据,并且该类型必须用在chunk stream的起始位置和流时间戳回退或重置的时候。

 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 (coutinue)  |message type id| msg stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          message stream id (coutinue)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

类型 1(fmt = 1, 7 bytes)

类型 1 的块头信息长度为 7 个字节,它省去了message stream id的 4 个字节,表示此chunkd的信息和上一次发的chunk所在的信息流相同。该块通常跟随在类型 0 的块后面,表示其信息流不变,但是其信息长度是可变的(例如一些视频格式数据)。

 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 (coutinue)  |message type id|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

类型 2(fmt = 2, 3 bytes)

类型 2 的块头信息长度为 3 个字节,它又省去了message length的 3 个字节和message type id的 1 个字节,只使用 3 个字节表示timestamp delta。该块通常用于类型 0 或类型 1 的块后面,表示其所在的信息流、信息长度和信息类型都是不变的(例如一些音频和数据格式)。

 0               1               2               
 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                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

类型 3(fmt = 3, 0 byte)

类型 3 的块头信息长度为 0 个字节,它把所有描述字段都省略了,表示这个chunk的 Message Header 和上一个chunk的完全相同。下面举例说明类型 3 的使用场景:

场景一:相同信息流中,信息的间隔相同

列子二:较大信息被分块后,每一个块中的信息处于同一时间戳

Extended Timestamp

Extended Timestamp(扩展时间戳)用于扩展 Message Header 中时间戳的表示。

举例

上面的介绍相对比较枯燥,通过下面的例子,我们就可以切身体会到块与分块的好处。

例子 1:块包装信息流的效果 - 解决信息冗余问题

本例展示的一个简单的音频信息流的信息冗余情况:

    +---------+-----------------+-----------------+-----------------+------------------+
    |         |Message Stream ID| Message Type ID | Time  | Length  | Estimated Total  |
    +---------+-----------------+-----------------+-------+---------+------------------+
    | Msg # 1 |    12345        |         8       | 1000  |   32    |   4+1+3+32=40    |
    +---------+-----------------+-----------------+-------+---------+------------------+
    | Msg # 2 |    12345        |         8       | 1020  |   32    |   4+1+3+32=40    |
    +---------+-----------------+-----------------+-------+---------+------------------+
    | Msg # 3 |    12345        |         8       | 1040  |   32    |   4+1+3+32=40    |
    +---------+-----------------+-----------------+-------+---------+------------------+
    | Msg # 4 |    12345        |         8       | 1060  |   32    |   4+1+3+32=40    |
    +---------+-----------------+-----------------+-------+---------+------------------+
              Sample audio messages to be made into chunks

Message Stream ID假设用 4 个字节表示,Message Type ID假设用 1 个字节表示,Time假设用 3 个字节表示,那么信息流分成的每一个信息的估算长度为 40 个字节。并且用这中信息表示方式,后续的信息中会越来越多冗余的信息。

下图表示RTMP中对这个信息流进行块包装的效果:

    +--------+---------+-----+------------+------- ---+------------+
    |        | Chunk   |Chunk|Header Data |No.of Bytes|Total No.of |
    |        |Stream ID|Type |            |  After    |Bytes in the|
    |        |         |     |            |Header     |Chunk       |
    +--------+---------+-----+------------+-----------+------------+
    |Chunk#1 |    3    |  0  | delta: 1000|   32      |    44      |
    |        |         |     | length: 32,|           |            |
    |        |         |     | type: 8,   |           |            |
    |        |         |     | stream ID: |           |            |
    |        |         |     | 12345 (11  |           |            |
    |        |         |     | bytes)     |           |            |
    +--------+---------+-----+------------+-----------+------------+
    |Chunk#2 |    3    |  2  | 20 (3      |   32      |    36      |
    |        |         |     | bytes)     |           |            |
    +--------+---------+-----+----+-------+-----------+------------+
    |Chunk#3 |    3    |  3  | none (0    |   32      |    33      |
    |        |         |     | bytes)     |           |            |
    +--------+---------+-----+------------+-----------+------------+
    |Chunk#4 |    3    |  3  | none (0    |   32      |    33      |
    |        |         |     | bytes)     |           |            |
    +--------+---------+-----+------------+-----------+------------+
            Format of each of the chunks of audio messages

由于Chunk Stream ID为 3,小于 64,因此 Basic Header 只占用 1 个字节。由于timestamptimestamp detal只为 1000 和 20,因此不需要使用 Extended Timestamp 字段。综上,我们在计算 Chunk Header 大小的时候只需要计算 Message Header 的大小加 1 即可。以下的字节计算式均为:Basic Header + Message Header + Extended Timestamp + Chunk Data。

综上情况,从第 3 个chunk开始,数据传输达到最优化,只用一个字节就能标识信息。

例子 2:信息流分块效果 - 避免单个包过大

本例展示了一条长消息,该信息流单独封包相对比较大,可能会因为单独传输这个包而延后了优先级更高的信息

    +-----------+-------------------+-----------------+-----------------+
    |           | Message Stream ID | Message Type ID | Time  | Length  |
    +-----------+-------------------+-----------------+-----------------+
    | Msg # 1   |       12346       |    9 (video)    | 1000  |   307   |
    +-----------+-------------------+-----------------+-----------------+
                    Sample Message to be broken to chunks

由于消息的长度超过了块的最大长度(128字节),此消息传输时需要对其做分块操作,即将大的消息分割成若干个块,效果如下图所示:

    +-------+------+-----+-------------+-----------+------------+
    |       |Chunk |Chunk|Header       |No. of     |Total No. of|
    |       |Stream| Type|Data         |Bytes after| bytes in   |
    |       | ID   |     |             | Header    | the chunk  |
    +-------+------+-----+-------------+-----------+------------+
    |Chunk#1|  4   |  0  | delta: 1000 |  128      |   140      |
    |       |      |     | length: 307 |           |            |
    |       |      |     | type: 9,    |           |            |
    |       |      |     | stream ID:  |           |            |
    |       |      |     | 12346 (11   |           |            |
    |       |      |     | bytes)      |           |            |
    +-------+------+-----+-------------+-----------+------------+
    |Chunk#2|  4   |  3  | none (0     |  128      |   129      |
    |       |      |     | bytes)      |           |            |
    +-------+------+-----+-------------+-----------+------------+
    |Chunk#3|  4   |  3  | none (0     |  51       |   52       |
    |       |      |     | bytes)      |           |            |
    +-------+------+-----+-------------+-----------+------------+
                    Format of each of the chunks

由于Chunk Stream ID为 4,小于 64,因此 Basic Header 也只占用 1 个字节。由于timestamp为 1000,因此也不需要使用 Extended Timestamp 字段。因此该例的块字节大小计算与上一例相同。

综上两个例子,类型 3 的块有两种使用方式:

  • 说明消息的继续
  • 说明新消息的头信息可以由前面存在的消息中推导出来
上一篇 下一篇

猜你喜欢

热点阅读