SR 包 28 字节:头 4、SSRC 4、NTP 8、RTP 时间戳 4、计数 8。 NTP 纪元是 1900 年不是 1970 年,差 2208988800 秒——这个数要背下来。
上一节把五种包认全了,这一节把最有信息量的 SR 拆到底。 SR 是音视频同步的基准:两路流靠它换算到同一时间轴,这是本章最后一个知识点, 也是后面所有同步话题的地基。 同步在播放器里的实现,看 34 课的第九章: 34 课
一、28 字节,七段
一个不带报告块的 SR,正好 28 字节:
| 段 | 字节 | 内容 |
|---|---|---|
| 头 | 4 | V/P/RC/PT/length |
| SSRC | 4 | 发送方身份 |
| NTP 高 32 位 | 4 | 从 1900 年起的整秒 |
| NTP 低 32 位 | 4 | 秒的小数部分 |
| RTP 时间戳 | 4 | 对应这个 NTP 时刻的采样计数 |
| 发送包数 | 4 | 累计发了多少 RTP 包 |
| 发送字节数 | 4 | 累计负载字节,不含头 |
7 段 × 4 字节 = 28,对上了。 如果 RC(报告块计数)大于 0,后面每块再加 24 字节。
二、NTP 时间戳:为什么是 1900 年
NTP 的纪元是 1900-01-01,Unix 是 1970-01-01。 两者差 2208988800 秒——这个数在音视频里到处出现,值得背。
| 量 | 值 | 说明 |
|---|---|---|
| NTP 纪元 | 1900-01-01 | 比 Unix 早 70 年 |
| 纪元差 | 2208988800 秒 | 换算要加它 |
| 高 32 位 | 整秒 | 到 2036 年会溢出 |
| 低 32 位 | 秒的小数 | 精度约 200 皮秒 |
为什么精度要这么高:音视频同步的误差容忍是毫秒级,200 皮秒的精度远超需要。 但这不浪费——它只占 4 个字节,而且换算简单。
2036 年溢出是个真问题:高 32 位到那时候会回绕。 现在的处理办法是「靠低 32 位和上下文推断」,这是个已知的、被推迟的问题。
三、RTP 时间戳和 NTP 的配合
SR 里同时给了两个时间:NTP(绝对时间)和 RTP(采样计数)。 这一对就是「某一采样时刻对应的绝对时间」。
它的用途非常具体: 视频流说我这一帧 RTP 时间戳是 90000; 音频流说我这一帧 RTP 时间戳是 8000; 两路时钟不同,没法直接比; 但两路都给了 NTP,就能换算到同一时间轴。
这就是音视频同步的全部原理:用 NTP 把两路的本地时钟对齐,再算差值。 没有 SR,两路流永远对不齐。
四、接收报告块:24 字节的账本
RC 大于 0 时,SR 后面跟着报告块,每块 24 字节。 里面最有用的是丢包率和抖动。
| 字段 | 字节 | 怎么算 |
|---|---|---|
| SSRC_1 | 4 | 这个块说的是哪一路 |
| 丢包率 | 1 | 丢包数 ÷ 期望 × 256,取整 |
| 累计丢包 | 3 | 24 位有符号 |
| 扩展最高序号 | 4 | 收到过的最大序列号 |
| 抖动 | 4 | 时间戳刻度单位的抖动 |
| 最后 SR 时间 | 4 | 上次收到 SR 的中间 32 位 |
| 距上次 SR | 4 | 过了多久 |
丢包率只有 1 个字节,存的是「256 分之几」——0 到 255 表示 0 到 99.6%。 所以低丢包率下它不灵敏:真实值低于 1/256 就显示成 0。 要看精确值,得用「累计丢包」那个 24 位字段。
五、动手 Demo:SR 包 28 字节,丢包率和抖动都在这 28 字节里
这段按当前时间真算一次 NTP 时间戳,把 SR 的七段列出来,再算一次丢包率的定点值。 注意「两个纪元差多少」那个数,后面会一直用到。
NTP_EPOCH = 2208988800 # 1900 到 1970 的秒数
now = datetime.datetime.now(datetime.timezone.utc)
ntp = now.timestamp() + NTP_EPOCH
sec = int(ntp)
frac = int((ntp - sec) * (1 << 32))
print(sec, frac) # 高 32 位与低 32 位
# 丢包率:定点 256 分之几
expect, got = 2560, 2550
lost = expect - got
print(lost, int(lost * 256.0 / expect))
实跑输出:
--------------------------------------------------------------------
SR 包 28 字节,丢包率和抖动都在这 28 字节里
--------------------------------------------------------------------
**SR 包是最有信息量的一个:它把发送端的账本全摊开。**
一个不带报告块的 SR 正好 28 字节,逐段拆:
位置 值 意思
--------------------------------------------------------------------------
头 4 字节 V/P/RC/PT/length PT=200,RC=0
SSRC 4 字节 0x1A2B3C4D 发送方身份
NTP 高 32 位 4001819376 从 1900 年起的整秒
NTP 低 32 位 1477472256 秒的小数部分,2 的 32 次方分之几
RTP 时间戳 1000000 跟 NTP 对应的那个采样时刻
发送包数 3000 从开始到现在发了多少 RTP 包
发送字节数 3000000 负载字节,不含头
字节数合计:4 + 4 + 4 + 4 + 4 + 4 + 4 = 28,对上了。
**NTP 时间戳为什么这么绕:它用 1900 年当纪元,不是 1970 年。**
两个纪元的差是 2208988800 秒。所以:Unix 时间 1792830576 加 2208988800 才是 NTP 秒。
高 32 位是整秒,低 32 位是小数——精度约 200 皮秒,远超需要。
**它是全系统对时的基准**:音视频两路流靠它换算到同一时间轴。
**接收端那半在 RR 的报告块里,24 字节一个,最有用的两个数是:**
字段 算法 本例
-----------------------------------------------------------------
丢包数 期望 − 实收 2560 − 2550 = 10
丢包率 丢包数 ÷ 期望 × 256(定点) 1 ÷ 256 = 0.39%
累计丢包 32 位有符号,可能为负 10
**丢包率存的是「256 分之几」而不是百分比**:8 位字段,0 到 255 刚好表示 0 到 99.6%,
比存百分比省事又够准。
本例 10 ÷ 2560 = **0.39%**,存进去的定点值 1 除以 256 也是 0.39%,两边对得上。
**这个字段有量化误差,得知道:**
真实丢包率 0.39% 时,10 × 256 ÷ 2560 取整正好是 1,没有损失;
但真实值要是偏低一些,比如千分之二,乘出来是 0.5,取整变 0,**这一档就直接显示成没丢包**。
所以低丢包率下这个字段不灵敏——**要看累计丢包数,那个是精确的**。
**抖动(jitter)不在 8 位里,它是 32 位的无符号数,单位是时间戳刻度。**
算法是逐包递推:拿「到达间隔」减「发送间隔」,取绝对值,再做指数平滑(除以 16 累加)。
所以它反映的是**近期**的抖动,不是全程平均——网络刚变差,这个数会很快爬上来。
高 32 位那个数看着像个天文数字,因为它从 1900 年数起,已经数了一百二十多年。 低 32 位是秒的小数部分,乘以 2 的 32 次方取整——这就是「定点小数」的写法。
丢包率那段是本节的实用部分:10 ÷ 2560 正好等于 1/256,所以定点值是 1,除以 256 得 0.39%, 两边完全对得上。 这个「正好对上」是特意挑的:为了让你看清定点存储的原理,而不是被量化误差干扰。
量化误差那一段必须记:低丢包率下这个字段会显示 0。 真实值千分之二时,乘出来 0.5,取整变 0,看着像没丢包。 所以监控大屏上「丢包率 0」不等于真的没丢,要看累计丢包数——那个是精确的整数。
30 秒实验
- 用当前时间算一次 NTP 时间戳,看高 32 位是多少。
- 把纪元差改成 0(当成 Unix 纪元),算出来的数会差多少。
- 算出 1% 的丢包率对应的定点值,看取整误差有多大。
常见误区
- 以为 NTP 纪元是 1970 年。 是 1900 年,差 2208988800 秒。用 Unix 时间直接填会差七十年。
- 以为丢包率字段是百分比。 是 256 分之几的定点值,低丢包率下会量化成 0。
- 以为 SR 只用来看丢包。 它最重要的作用是提供 NTP 与 RTP 时间戳的对应关系——没有它, 音视频两路没法对齐。
自测三问(点开对答案)
1. SR 包为什么正好 28 字节? —— 七段各 4 字节:头、SSRC、NTP 高低各一、RTP 时间戳、发送包数、发送字节数。7 × 4 = 28。 2. NTP 与 Unix 的纪元差是多少?为什么重要? —— 2208988800 秒。换算 NTP 时间戳必须加它,否则时间差七十年,音视频同步彻底错乱。 3. 为什么「丢包率 0」不代表真的没丢包? —— 这个字段只有 8 位,存的是 256 分之几。真实值低于 1/256 时取整变 0。 要看精确值得用 24 位的累计丢包数。第一章完了。 下一章从 VLC 开始,看一个现成的播放器怎么用这些协议。
- ☐ (本章完,下一节 02.01.01 VLC播放器)