RTCP 只有五种包,PT 从 200 数到 204:SR、RR、SDES、BYE、APP。 其中 SR 和 RR 是主干,SDES 是必需的陪衬。
3.1 讲完 RTP 怎么搬数据,这一节开始讲 RTCP 怎么报账。 3.2.2 会把 SR 拆到字段级,3.2.3 讲发送规则。
一、五种包各干什么
① SR(PT=200,发送方报告) 告诉对方「我发了多少包多少字节」以及「NTP 与 RTP 时间戳的对应关系」。 ② RR(PT=201,接收方报告) 告诉对方「我丢了多少、抖动多大」。 它是反向的:让发送方知道链路好不好。 ③ SDES(PT=202,源描述) 带 CNAME 等文本,让接收方把 SSRC 归到「同一个人」。 ④ BYE(PT=203,告别) 我要走了。丢了也不影响,超时照样能发现。 ⑤ APP(PT=204,应用自定义) 厂商扩展用,标准不定义内容。
二、为什么 PT 从 200 开始
因为 0~127 被 RTP 的载荷类型占了。 RTCP 用 200~204 是为了「一眼分辨」:看第一个字节的高位就知道是 RTP 还是 RTCP。
具体怎么分辨?看 PT 的数值范围就行。 RTP 的 PT 只有 7 位(0~127),RTCP 的 PT 用满 8 位(200~204)。 所以同一个端口上收到包,PT ≥ 200 就是 RTCP。
但实际部署里它们很少共用端口。 标准约定 RTCP 端口 = RTP 端口 + 1,所以「按端口分」比「按 PT 分」更常见。 共用端口只在 RTP over TCP 时才出现(靠 interleaved 通道号分)。
三、SR 与 RR 的分工
这两个包的区别只有一个:谁在发数据。
① 发送方发 SR:因为我发了 RTP,所以我得告诉对方「我发了多少」。SR 里多出 20 字节的发送统计,以及 NTP/RTP 时间戳的配对。 ② 接收方发 RR:因为我只收,没什么可报的,就报「收得怎么样」。RR 就是 SR 去掉那 20 字节发送统计。
所以它们的头部结构完全一样,只差一个 20 字节的块。 这也解释了为什么标准说「复合包必须以 SR 或 RR 开头」——两者是同一种东西的两种形态。
双向通信时两边都既发又收,所以两边都发 SR,而 SR 里可以带报告块(RC>0),等于「SR 顺便干了 RR 的活」。
四、SDES 为什么是必需的
因为 SSRC 是个随机数,本身没有任何意义。 接收方看到 0x1A2B3C4D,不知道这是「摄像头」还是「麦克风」。
SDES 里的 CNAME 就是「稳定标识」。 同一个人的音视频两路流,SSRC 不同但 CNAME 相同——接收方靠 CNAME 把它们配对,才能做音画同步。
没有 CNAME 会怎样? 音视频被当成两个人,同步算法根本不会启动,现象是「画面和声音各走各的,越差越远」。 这是自制 RTP 实现最常见的坑之一:只发 RR 不发 SDES。
标准要求每个复合包都必须带 CNAME,就是为了堵这个洞。 所以「复合包」不是「可选的优化」,是硬性规定。
五、动手 Demo:真造五个包,验证 PT 与长度字段
按 RFC 3550 手工拼五种包,验证 PT 值、头字节与长度字段的换算。 重点是那个「以 4 字节为单位减一」的长度算法。
import struct
def mk(pt, body=b"", count=0):
n = (4 + len(body)) // 4 - 1
return struct.pack("!BBH", 0x80 | count, pt, n) + body
pk = [(200, mk(200, b"\x00" * 24, 0)), # SR,无报告块
(201, mk(201, b"\x00" * 24, 1)), # RR,一个报告块
(202, mk(202, b"\x01\x00\x00\x00", 1)), # SDES
(203, mk(203, b"\x1a\x2b\x3c\x4d" + b"\x00" * 4, 1)),
(204, mk(204, b"\x63\x68\x33\x30" + b"\x00" * 8, 0))]
for pt, d in pk:
b0, b1, ln = struct.unpack("!BBH", d[:4])
print("PT=%d 头=0x%02X 长度字段=%d 实际=%d 字节"
% (b1, b0, ln, len(d)))
# 长度字段的含义:(长度 + 1) * 4 = 总字节
for pt, d in pk:
ln = struct.unpack("!H", d[2:4])[0]
print("PT=%d 按长度字段推算总长 = %d" % (pt, (ln + 1) * 4))
实跑输出:
--------------------------------------------------------------------
RTCP 的 5 种分组类型——PT 从 200 数到 204
--------------------------------------------------------------------
**① 五种包的 PT 值**
PT 名字 全称 作用
---------------------------------------------------------------------------
200 SR Sender Report 发送方报告:发了多少、时钟对齐
201 RR Receiver Report 接收方报告:丢了多少、抖动多大
202 SDES Source Description 源描述:CNAME 等,用来认人
203 BYE Goodbye 告别:我要走了
204 APP Application-defined 应用自定义
**② 真造五个包,验证 PT 与长度字段**
PT 头字节 长度字段 实际字节
--------------------------------------------
200 0x80 6 28
201 0x81 6 28
202 0x81 1 8
203 0x81 2 12
204 0x80 3 16
**长度字段的单位是「4 字节减一」,不是字节数。**
**长度写 6 表示 7 个 32 位字,也就是 28 字节。**
**这个减一是 RTCP 最容易算错的地方——它把「包含头部的总长」按 4 字节折算后再减 1。**
**③ 五种的用法差别**
**SR 和 RR 是「必发」的**:发送方定期发 SR,接收方定期发 RR。**它们是 RTCP 的主干。**
**SDES 必须跟着 SR/RR 一起发**(复合包里至少要有一个 CNAME)。
**作用是让接收方知道「这个 SSRC 是谁」,否则多路混流没法区分。**
**BYE 只在退出时发一次**,**APP 是给厂商扩展用的**,**大部分开源服务器的 APP 都留空。**
PT=200 的包 28 字节,长度字段是 6 ——因为 28÷4−1=6。不是 28,也不是 7。
头字节 0x80 与 0x81 差在最低位,那是 RC(报告块个数)。RC=1 就是带一个报告块。
第二段验证了换算公式:(长度+1)×4 正好等于实际字节,一个不差。
30 秒实验
- 把 SR 的 body 加到 40 字节,看长度字段变成多少——
(44÷4)−1 = 10。 - 把 RR 的 RC 改成 2,body 要跟着加 24 字节,否则解析时会越界。
- 造一个 PT=199 的包,看你能不能一眼判断它不是标准 RTCP——标准只有 200~204。
常见误区
- 「RTCP 有很多种包」——标准只用五个 PT,其余都是扩展。
- 「SDES 可有可无」——复合包必须带 CNAME,否则音视频配不上对。
- 「长度字段就是字节数」——是「4 字节为单位再减一」。这个减一是最容易算错的地方。
自测三问(点开对答案)
1. 五种 RTCP 包的 PT 分别是多少? —— **SR=200、RR=201、SDES=202、BYE=203、APP=204。** 2. SR 和 RR 的结构差在哪? —— **SR 多 20 字节发送统计(NTP 秒/小数、RTP 时间戳、包数、字节数)。**头部结构完全一样。 3. 没有 CNAME 会出什么问题? —— **音视频被当成两个人,音画同步根本不启动,越播越不同步。**- ☐ (本节完,下一节 3.2.2 RTCP包结构)