当 message type id 是 1,2,3,5,6 时,表示该 message 是一条协议控制消息(Protocol Control Message)。
这些类型的消息用来控制 chunk 层级的一些行为,具体类型名称如下:
- 1:Set Chunk Size
- 2:Abort Message
- 3:Acknowledgement
- 5:Windon Acknowledgement Size
- 6:Set Peer Bandwidth
Protocol control message 有几个重要的规定:
- Chunk stream id 必须是 2,message stream id 必须是 0。
- 接收端在收到 protocol control message 后必须立即处理,处理之后才能接收处理后续的 chunk。
- timestamp 字段会被忽略。
开始之前,先约定下几个定义:
- client:发起 handshake 的一方。
- server:client 的对端。
- 发送端:发送 Protocol Control Message 的一方。
- 接收端:接收 Protocol Control Message 的一方。
client/server,发送端/接收端是两组概念。client/server 是链接层面的,链接建立后就不会改变了。发送端/接收端是消息层面的,分别是指一条消息的发送者和接受者。client 或 server 均可是发送端或接收端。
Set Chunk Size (1)
前文提到过,maximum chunk size 是单个 chunk 的 payload size 的上限,默认值是 128,单位 byte。发送端可通过 Set Chunk Size 类型的消息调整 maximum chunk size 的大小。
Set Chunk Size 消息携带一个 32-bit 的整数—— chunk size,该字段的取值范围为[1, 0x7FFFFFFF]
。实际上,chunk size 的值不应该超过 0xFFFFFF
。因为 message length 字段只有 24-bit,其值不可能超过 0xFFFFFF
。
注意,这里是发送端提醒接收端,我发送该消息之后,后续 chunk 的 payload size 上限就已经调整啦,但是你发给我的 chunk 的 payload size 上限不会变。
举个例子,假设要发送如下两个 message:
Message Stream ID | Message Type ID | Timestamp | Length | |
---|---|---|---|---|
MSG # 1 | 10 | 8 | 1000 | 280 |
MSG # 2 | 10 | 8 | 1020 | 150 |
在 maximum chunk size 是 128 时,会封装为 5 个 chunk,如下表示:
Chunk Stream ID | Chunk Format | Timestamp(Delta) | Message Length | Message Type ID | Message Stream ID | Chunk Data Size | |
---|---|---|---|---|---|---|---|
CHK # 1 | 3 | 0 | 1000 | 280 | 8 | 10 | 128 |
CHK # 2 | 3 | 3 | \ | \ | \ | \ | 128 |
CHK # 3 | 3 | 3 | \ | \ | \ | \ | 24 |
CHK # 4 | 3 | 1 | 20 | 150 | 8 | 10 | 128 |
CHK # 5 | 3 | 3 | \ | \ | \ | \ | 22 |
如果,发送端在 CHK#1 后加入一条 Set Chunk Size,通知对端上限调整为 152,则上述 chunk 变为:
Chunk Stream ID | Chunk Format | Timestamp(Delta) | Message Length | Message Type ID | Message Stream ID | Chunk Data Size | |
---|---|---|---|---|---|---|---|
CHK # 1 | 3 | 0 | 1000 | 280 | 8 | 10 | 128 |
CHK # S | 2 | 0 | 0 | 4 | 1 | 0 | 4 (值依次为 0,0,0,152) |
CHK # 2 | 3 | 3 | \ | \ | \ | \ | 152 |
CHK # 4 | 3 | 1 | 20 | 150 | 8 | 10 | 150 |
显然,接收端在收到 CHK#S 后,就要按照 maximum chunk size 为 152 读取后续 chunk 啦。
Abort Message (2)
该消息携带一个 32-bit 的整数—— chunk stream id,用于通知接收端丢弃该 chunk stream 上尚未接收完的 message。
不过该消息好像被废弃了,文档里面也画上了删除线。
FFmpeg 也未支持该消息类型,在收到该类型的消息后会返回错误码 AVERROR_UNKNOWN
。
// libavformat/rtmpproto.c
434 } else if (pkt.type == RTMP_CTRL_ABORT_MESSAGE) {
435 av_log(s, AV_LOG_ERROR, "received abort message\n");
436 ff_rtmp_packet_destroy(&pkt);
437 return AVERROR_UNKNOWN;
438 } else if (pkt.type == RTMP_PT_BYTES_READ) {
Acknowledgement (3)
该消息携带一个 32-bit 的整数—— sequence number,用于通知接收端我(发送端)已经收到的字节数。
几个小细节:
- 统计的是在所有 chunk stream 上读取的字节数。
- chunk header 的 size 也会统计在内。
- 每收到
w
个字节,就要发送一次。w
由对端通过 Window Acknowledgement Size 指定。关于w
的默认值,文档中并未提及,FFmpeg 将 2 20 2^{20} 220 作为默认值。
Window Acknowledgement Size (5)
该消息携带一个 32-bit 的整数 —— window size,用于设置接收端发送 Acknowledgement 消息的频率。接收端每收到 window size 个字节,至少发送一条 Acknowledgement 消息。
发送-等待机制:该消息的发送端期望每发送 window size 个字节就收到一条 Acknowledgement 消息,若未收到则会停止发送数据,直到 Acknowledgement 到来。
在 FFmpeg 的实现中,每收到 w i n d o w s i z e 2 \frac{window\ size}{2} 2window size 个字节,就发送一次 Acknowledgement 消息,具体代码如下,如此实现的原因也容易理解,可参见 1596 -1598 行的注释。
// libavformat/rtmpproto.c
1578 static int handle_window_ack_size(URLContext *s, RTMPPacket *pkt)
1579 {
1580 RTMPContext *rt = s->priv_data;
1581
1582 if (pkt->size < 4) {
1583 av_log(s, AV_LOG_ERROR,
1584 "Too short window acknowledgement size packet (%d)\n",
1585 pkt->size);
1586 return AVERROR_INVALIDDATA;
1587 }
1588
1589 rt->receive_report_size = AV_RB32(pkt->data);
1590 if (rt->receive_report_size <= 0) {
1591 av_log(s, AV_LOG_ERROR, "Incorrect window acknowledgement size %d\n",
1592 rt->receive_report_size);
1593 return AVERROR_INVALIDDATA;
1594 }
1595 av_log(s, AV_LOG_DEBUG, "Window acknowledgement size = %d\n", rt->receive_report_size);
1596 // Send an Acknowledgement packet after receiving half the maximum
1597 // size, to make sure the peer can keep on sending without waiting
1598 // for acknowledgements.
1599 rt->receive_report_size >>= 1;
1600
1601 return 0;
1602 }
Set Peer Bandwidth (6)
该消息包含两个字段:
- Window size:32-bit 的整数。
- Limit type:8-bit 整数。
Limit type 的取值有如下三种:
- 0 - Hard:接收端应使用 window size 作为控制带宽的参数。
- 1 - Sort:使用 window size 和正在生效的 window size 中的最小值。
- 2 - Dynamic:如果前一个 Limit Type 是 Hard,则该消息按照 Hard 规则生效,反之该消息被丢弃。
Set Peer Bandwidth 需配合 Acknowledgement 与 Window Acknowledgement Size 一起工作。设有两个握手成功的端点 A 和 B,其工作流程如下:
- A 向 B 发送 Set Peer Bandwidth,window size = 1024。
- B 收到后,向 A 发送 Window Acknowledgement Size,window size 需相同,亦为 1024。
- A 收到 Window Acknowledgement Size 后,每收到 1024 个字节,至少发送一次 Acknowledgement。
- B 在发送 Window Acknowledgement Size 后,会触发 发送-等待机制 以达到控制带宽的效果。
3,5,6 的例子
设有两个端点 A 和 B 已经握手成功,现在 A 要通过 Set Peer Bandwidth 控制 B 的发送速率,过程如下: