Chunk
每条RTMP消息在传输前都先被分割为若干数据块,这些数据块被称为分块(chunk).
不同消息的分块可以交替发送,同一条信息的分块按时间戳顺序依次发送,一条信息的传递通道叫做块流(chunk stream),流传递中的每个分块都包含一个流ID,用于分块传输后重组成完整的流.
注意:chunk stream ID不等于stream ID.
块大小在128Byte-65536Byte之间。
大分块cpu负载降低,在低带宽环境传输会延迟;小分块延迟低,但cpu消耗大,不利于高码率传输。
块流格式
分块基本头(chunk basic header)+ 分块消息头(chunk message header) + 拓展时间戳(extended timestamp) + 分块数据(chunk data==Message Payload)
分块头(分块基本头+分块消息头+拓展时间戳):属于 传输层结构,包含 fmt(复用类型)和 Chunk Stream ID(物理通道标识),用于分块传输。
- 分块基本头(chunk basic header):分块流ID和分块类型。
- 分块消息头(chunk message header):表示该分块所属的RTMP消息的数据。
- 拓展时间戳(extended timestamp):当普通时间戳为0xFFFFFF,必须添加拓展时间戳,4字节。
Chunk Data( Message Payload ):当前分块所承载的实际数据(RTMP消息),属于 应用层 消息(Message)的负载(Payload)的分段数据. 定义消息的完整元数据(如时间戳、长度、类型)。
Chunk Size:每个 Chunk Data 的最大长度(默认128字节),可通过控制消息动态调整。
当消息长度大于Chunk Size时,无法通过一个Chunk Data传递完所有数据,此时需要拆分成多个Chunk Data进行传输。拆分规则如下:
分块传输规则
- 消息拆分:
- 若消息负载(Message Payload)长度超过 Chunk Size,消息将被拆分为多个块(Chunk)。
- 示例:消息长度=300字节,Chunk Size=128 → 拆分为3块(128+128+44)。
- 分成多个Chunk传输:
- 首块(Type 0或1):Type=分块基本头中的fmt, 携带完整的元数据(Message Header),后续块复用元数据(Type 2或3)。
- 后续块(Type 3):仅包含 Chunk Data,无额外头部(复用前序块的元数据)。
- 重组逻辑:
- 接收端根据 传输层
Chunk Stream ID和应用层Stream ID重组完整消息。 - 按顺序拼接所有 Chunk Data,恢复原始消息负载。
- 接收端根据 传输层
分块基本头(chunk basic header)
Chunk Basic Header = fmt + Chunk Stream ID
fmt(Format Type): 取值范围 [0~3],占用 高2位(bit 7-6),决定了后续 chunk Message Header 的结构
块流ID(Chunk Stream ID):取值范围[3,65599], 决定了chunk basic header的长度
Chunk Basic Header 长度可以是 1、2 或 3 字节,具体由第一字节的后6bit决定的:
- 后6bit取值为0时,2字节,streamId=第2字节整型+64。
| 第1字节 | 第2字节 |
|fmt(2位)|(6位),取值[0]|streamId=第2字节整型+64,取值[64,319]|
- 后6bit取值为1时,3字节,streamId=(第2和第3字节组成的16位整型) + 64。
| 第1字节 | 第2字节 | 第3字节 |
|fmt(2位)|(6位),取值[1]| streamId=(第2和第3字节组成的16位整型) + 64,取值[64,65599]|
- 后6bit取值为2时,保留。
- 后6bit取值为3-63时,1字节,streamID=[3-63]。
| 第1字节 |
|fmt(2位)|streamId(6位),取值[1-62]|
也就是说:
| 块流ID范围 | Basic Header长度 | 编码方式 |
|---|---|---|
| 2–63 | 1字节 | 直接存储块流ID |
| 64–319 | 2字节 | 首字节后6位为0,第二字节为ID+64 |
| 64–65599 | 3字节 | 首字节后6位为1,后两字节为ID+64 |
分块消息头(chunk message header)
表示分块所属的RTMP消息的数据,长度取决于Chunk Basic Header的fmt,类型为0的分块消息头最复杂,类型值越大,结构越简单。
fmt 值 | 类型 | 长度(字节) | 适用场景 |
|---|---|---|---|
0b00 | Type 0 | 11 | 新消息的第一个块(完整元数据) |
0b01 | Type 1 | 7 | 同一流的新消息(省略流ID) |
0b10 | Type 2 | 3 | 同一消息的后续块(仅时间戳增量) |
0b11 | Type 3 | 0 | 完全复用前一个块的元数据 |
Type 0(11字节)
用于新消息的第一个块,一般在分块流的开端或者时间戳回跳(如向后拖动播放)的位置,携带完整元数据:
| 字段 | 字节数 | 说明 |
|--------------------|--------|----------------------------------------------------------------------|
| Timestamp | 3 | 消息的绝对时间戳(单位:毫秒)。若 ≥ 0xFFFFFF,则固定为0xFFFFFF,需扩展为4字节(见后) |
| Message Length | 3 | 消息的总长度(单位:字节) |
| Message Type ID | 1 | 消息类型(如 8=音频,9=视频,18=AMF命令等) |
| Stream ID | 4 | 消息所属流的ID(小端序存储) |
Type 1(7字节)
用于同一流(Stream ID相同)的新消息,省略 Stream ID,表示当前分块与前序分块(携带chunk steam ID)属于一个信息流(相同Stream ID):
| 字段 | 字节数 | 说明 |
|--------------------|--------|----------------------------------------------------------------------|
| Timestamp Delta | 3 | 相对于前一个消息的时间戳增量:表示当前分块的时间戳与前序分块的时间戳的差值 |
| Message Length | 3 | 消息的总长度 |
| Message Type ID | 1 | 消息类型 |
Type 2(3字节)
用于同一消息的后续块(分块传输时),仅携带时间戳增量,表示当前分块的时间戳和前序分块的时间戳的差值,与前序分块下相同stream id, 且其中信息均为固定长度:
| 字段 | 字节数 | 说明 |
|--------------------|--------|----------------------------------------------------------------------|
| Timestamp Delta | 3 | 时间戳增量(相对于前一个块) |
Type 3(0字节)
完全复用前一个块的元数据,不携带任何字段(适用于连续分块传输)。
当一条信息被拆分为多个分块传输时,后续分块均使用此种分块
Extended Timestamp(扩展时间戳)
当 Type 0/1/2 的 Timestamp 或 Timestamp Delta 值 ≥ 0xFFFFFF(即3字节最大值)时:
- Type 0/1/2 中的 Timestamp 或 Timestamp Delta 固定为 0xFFFFFF。
- 在 Message Header 后添加 4字节的扩展时间戳字段,存储完整的32位时间戳(大端序)。
Basic Header: 0x40 (fmt=0b01, Chunk Stream ID=0)
Message Header:
Timestamp Delta: FF FF FF → 原始值 ≥ 0xFFFFFF,需扩展
Message Length: 00 00 80 → 总长度 0x8000 = 32768字节
Message Type: 09 → 视频数据
Extended Timestamp: 00 01 00 00 → 扩展时间戳 0x01000000 = 16777216ms
Payload: 消息体