Impl. live streaming system - RTMP: Chunking
Introduction
Chunking provides multiplexes multiple chunk stream.
(def row-header-fn {})
(def boxes-per-row 12)
(draw-box "Basic Header" {:span 3})
(draw-box "Message Header" {:span 4})
(draw-box "Extended Timestamp" {:span 5})
(draw-gap "Payload")
(draw-bottom)
Default Chunk size is 128 bytes. Chunk size is maximum length of payload. So, If you want to send 200 bytes. than that message split into 2 chunk. (like below diagram)
(def row-header-fn {})
(def boxes-per-row 12)
(draw-box "Basic Header" {:span 3})
(draw-box "Message Header" {:span 4})
(draw-gap "Payload (128 bytes)")
(draw-bottom)
(draw-box "Basic Header" {:span 3})
(draw-gap "Payload (72 bytes)")
(draw-bottom)
Header
Basic header
cs_id
is chunk stream id that indicates following chunk’s stream context.cs_id
0, 1 and 2 are reserved.cs_id
2 is low-level protocol control messages and commands.
Basic header has 3 type. 1, 2, 3 bytes.
1 byte header can represent cs_id
range 2-63
{reg: [
{"bits": 2, "name": "fmt", "type": 2},
{"bits": 6, "name": "cs_id"},
{"bits": 16},
], config: {vflip: true, bits: 24}}
Calculation: cs_id $= 0b00111111 | \text{(1st byte)}$
else if cs_id
range 64~319(64+255(0xFF)), use 2 bytes
{reg: [
{"bits": 2, "name": "fmt", "type": 2},
{"bits": 6, "name": 64},
{"bits": 8, "name": "cs_id - 64"},
{"bits": 8},
], config: {vflip: true, bits: 24}}
Calculation: cs_id $= \text{(2nd byte)} + 64$
else cs_id
range 64~65599(64+65535(0xFFFF))
{reg: [
{"bits": 2, "name": "fmt", "type": 2},
{"bits": 6, "name": 63},
{"bits": 16, "name": "cs_id - 64"},
], config: {vflip: true, bits: 24}}
Calculation: cs_id $= \text{(2nd byte)} + 64 + \text{(3rd byte)} \times 256$
3 bytes basic header can cover even 2 bytes. but, when send chunk message prefer shorter is better performance.
An implementation SHOULD use the smallest representation that can hold the ID.
Message header
Type 0
(def box-width 160)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp" {:span 3})
(draw-box "message length" {:span 1})
(draw-box "message length" {:span 2})
(draw-box "message type id" {:span 1})
(draw-box "message stream id" {:span 1})
(draw-box "message stream id" {:span 3})
- 11 bytes long
- All message reqiore timestamp, message lenagh, message type, and message stream id. Nothing optional.
- All chunk stream start with Type 0 header
MUST be used at the start of a chunk stream
Type 1
(def box-width 160)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp delta" {:span 3})
(draw-box "message length" {:span 1})
(draw-box "message length" {:span 2})
(draw-box "message type id" {:span 1})
-
7 bytes long
-
Same message stream id from previous chunk that has same
cs_id
-
Timestamp
delta
in Type 1, 2.
the difference between the previous chunk’s timestamp and the current chunk’s timestamp is sent here.
Type 2
(def box-width 160)
(def boxes-per-row 4)
(draw-column-headers)
(draw-box "timestamp delta" {:span 3})
Type 3
- When Message spliting(Fragmentation), then this type sent
When a single message is split into chunks, all chunks of a message except the first one SHOULD use this type.
- Using previous received chunk’s header information
A stream consisting of messages of exactly the same size, stream ID and spacing in time SHOULD use this type for all chunks after a chunk of Type 2.
- Use previous received timestamp setup
If the delta between the first message and the second message is same as the timestamp of the first message, then a chunk of Type 3 could immediately follow the chunk of Type 0 as there is no need for a chunk of Type 2 to register the delta. If a Type 3 chunk follows a Type 0 chunk, then the timestamp delta for this Type 3 chunk is the same as the timestamp of the Type 0 chunk.
Example
Example 1: Keep message header
Given case is Audio stream.
Message Stream ID | Message Type ID | Time | Length | |
---|---|---|---|---|
#1 | 12345 | 8 | 1000 | 32 |
#2 | 12345 | 8 | 1020 | 32 |
#3 | 12345 | 8 | 1040 | 32 |
#4 | 12345 | 8 | 1060 | 32 |
Chunk Stream ID | Chunk Type | Header Data | Payload Length | Chunk Size | |
---|---|---|---|---|---|
#1 | 3 | 0 | Timestamp: 1000; Length: 32; Type: 8; Stream ID: 12345 | 32 | 44 |
#2 | 3 | 2 | Timestamp Delta: 20 | 32 | 36 |
#3 | 3 | 3 | - | 32 | 33 |
#4 | 3 | 3 | - | 32 | 33 |
Message #1~#4 has same Stream ID
, Type ID
, and Length
;
Difference is Timestamp
and payload data.
So, Chunk stream keep message header that recorded at #1 and keep using determine by chunk stream id.
Chunk #2 setting timestamp delta, is affect #2~#4.
#3~#4, Only transmit payload data .
Example 2: Message spliting(Fragmentation)
Given case is Video stream.
Message Stream ID | Message Type ID | Time | Length | |
---|---|---|---|---|
#1 | 12346 | 9 | 1000 | 307 |
Chunk Stream ID | Chunk Type | Header Data | Payload Length | Chunk Size | |
---|---|---|---|---|---|
#1 | 3 | 0 | Timestamp: 1000; Length: 307; Type: 9; Stream ID: 12346 | 128 | 140 |
#2 | 3 | 3 | - | 128 | 129 |
#3 | 3 | 3 | - | 51 | 52 |
Default chunk size is 128 bytes. (Chunk size meaning maximum payload length, not chunk packet size)
307 bytes is greater than 128.
Chunk #1 Setup message information(Type 0: fully described header), with 128 bytes payload.
Chunk #2, Using same message header. with 128 bytes payload.
Chunk #2, Left payload = (message lenth 307 bytes) - (previous received payload 256 bytes( = 128 + 128)) = 51 bytes.
Read 51 bytes. then process received message.
Discussion
- THe primary objective of RTMP Chunk Stream is multiplexing and packetizing.
- Multiplexing is shown as
cs_id
, than identify each stream. - Packetizing is shown as Fragmentaion(message spliting).
- Prevent occupy connection by large message passing.
- Chunk stream keeping previous message header to reduce transmition same information.