📹 第一章 流媒体与RTSP/RTP/RTCP简介
📄 一句话概括

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 秒实验

  1. 用当前时间算一次 NTP 时间戳,看高 32 位是多少。
  2. 把纪元差改成 0(当成 Unix 纪元),算出来的数会差多少。
  3. 算出 1% 的丢包率对应的定点值,看取整误差有多大。

常见误区

  1. 以为 NTP 纪元是 1970 年。 是 1900 年,差 2208988800 秒。用 Unix 时间直接填会差七十年。
  2. 以为丢包率字段是百分比。 是 256 分之几的定点值,低丢包率下会量化成 0。
  3. 以为 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 开始,看一个现成的播放器怎么用这些协议。

相关笔记