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

HTTP 有流水线,RTSP 明确不推荐——因为请求之间有依赖,状态会乱。 串行六轮往返,只有两路 SETUP 之间没有依赖,可以并发。

📝 本节和前面几节的关系

这是 01.04 的最后一节,把「怎么发请求」这件事收尾。 前面讲的会话、连接、扩展,最后都要落到「一次发几条」这个操作层面。 下一节 01.05 讲具体的方法,那是这一节的弹药。

一、三种模式

模式这个词说的是「请求和连接怎么配」。

模式 做法 RTSP 的建议
持久连接 连一次,多条请求串行发 默认,就这么用
非持久连接 每条请求重连 不推荐,开销大
流水线 不等响应就发下一条 不推荐,状态会乱

注意第二、三种是两种不同的「不推荐」: 非持久是性能问题——多花握手挥手的时间; 流水线是正确性问题——可能把会话搞乱。 后者严重得多。

二、为什么不能流水线

两个理由,第一个是硬约束,第二个是软约束。

理由 说明 严重程度
请求之间有依赖 没 SETUP 完,PLAY 无意义 硬:会失败
CSeq 必须单调 响应乱序回来配对会乱 软:能实现但复杂

第一条是根本的:RTSP 的请求不是独立的。SETUP 给某一路分配了端口,PLAY 才有意义; DESCRIBE 拿到 SDP,才知道要 SETUP 几路。 这种依赖链决定了它天然是串行的。

第二条是工程上的:理论上可以给每个请求编号、按号重组响应, 但这样做的好处远小于复杂度——信令本来就没几条,省下的那点往返不值。

三、串行要几轮

一次完整播放的最小轮数,逐条数出来:

顺 方法 要等前面几条
1 OPTIONS 0(可以省)
2 DESCRIBE 1
3 SETUP 视频 1
4 SETUP 音频 1
5 PLAY 1
6 TEARDOWN 0

串行下来六轮,公网上每轮 30 毫秒,信令部分就是 180 毫秒。 对「点开就要看」的监控客户端,这个数已经能感觉到了。

能优化的只有第 3、4 条:两路 SETUP 之间没有依赖,可以同时发出去,省一轮。 省完是五轮,150 毫秒——这就是理论下限了。

四、并发 SETUP 的注意事项

两路 SETUP 并发是唯一安全的并行位置,但有两个前提:

前提 为什么
两路都已经从 SDP 里拿到 trackID 不知道 trackID 就没法拼 URL
各自用独立的 CSeq 否则响应配不上号

还有一个坑:并发发出去之后,两条响应的顺序是不确定的。 代码里不能假设「先发的先回」——必须按 CSeq 或按 trackID 认领,而不是按到达顺序。

这个错很常见:写的时候两条请求顺序发,读的时候按顺序读,在局域网里一直没事, 换到有抖动的网络就随机出错。

五、动手 Demo:RTSP 不做流水线,一条一条来

这段把串行六轮的依赖关系列出来,再算公网上的信令耗时。 最后对比「两路 SETUP 并发」能省多少。

steps = ((u"OPTIONS", 0), (u"DESCRIBE", 1), (u"SETUP(视频)", 1),
         (u"SETUP(音频)", 1), (u"PLAY", 1), (u"TEARDOWN", 0))
total = 0
for name, dep in steps:
    total += 1
    T([name, u"要等前面 %d 条" % dep, u"累计 %d 轮" % total], [0, 0, 0])

print(total * 30)        # 公网每轮 30 毫秒,串行总耗时
print((total - 1) * 30)  # 两路 SETUP 并发后

实跑输出:

--------------------------------------------------------------------
RTSP 不做流水线,一条一条来
--------------------------------------------------------------------
**HTTP/1.1 有流水线(pipelining):不等响应就发下一个请求。RTSP 明确不推荐这么干。**
  原因很实在:CSeq 必须单调递增,而响应可能乱序回来,配对逻辑会变复杂;
  而且 RTSP 的请求之间有依赖——没 SETUP 完,PLAY 就没意义。

  模式            做法                 RTSP 的建议
  -----------------------------------------------------------
  持久连接        连一次,多条请求     默认就这么用
  非持久连接      每条请求重连         不推荐,开销大
  流水线          不等响应发下一条     不推荐,有状态会乱

**算一笔依赖账:一次完整播放的最小串行步数。**

  OPTIONS         要等前面 0 条        累计 1 轮
  DESCRIBE        要等前面 1 条        累计 2 轮
  SETUP(视频)     要等前面 1 条        累计 3 轮
  SETUP(音频)     要等前面 1 条        累计 4 轮
  PLAY            要等前面 1 条        累计 5 轮
  TEARDOWN        要等前面 0 条        累计 6 轮

**串行下来至少 6 轮往返**(OPTIONS 那步可以省,很多设备不要求)。
  走公网每轮 30 毫秒,光信令就是 180 毫秒——对「点开就要看」的监控客户端,这已经能感觉出来了。
  所以有的实现会**把两路 SETUP 并发发出去**(它们之间没有依赖),这是唯一能安全并行的位置。

六轮、一百八十毫秒,这是最保守的算法(连 OPTIONS 都算上)。 省掉 OPTIONS 是四轮还是五轮? 数一下:DESCRIBE、两个 SETUP、PLAY,加 TEARDOWN 是五轮, 但如果 TEARDOWN 在退出时发,用户等待的只有前四轮——一百二十毫秒。

两路 SETUP 并发后省一轮,用户等待变成三轮九十毫秒。 再往下就省不动了:DESCRIBE 必须先于 SETUP(不知道 trackID),PLAY 必须最后。

所以「打开一个监控画面要多久」这个指标,信令部分的下限就是「三次往返」—— 剩下的时间全花在等第一个关键帧、等解码器初始化、等渲染器创建上。 优化的时候要分清是哪一段慢,别一上来就怀疑协议。

30 秒实验

  1. 抓一次播放的完整信令,数一下实际用了几轮往返。
  2. 把两路 SETUP 改成并发发出去,看总时间少了多少。
  3. 故意把 PLAY 提到 SETUP 前面,看设备回 455 还是别的东西。

常见误区

  1. 以为 RTSP 支持 HTTP 那种流水线。 请求之间有依赖,流水线会把状态搞乱——这是正确性问题, 不只是性能问题。
  2. 以为所有请求都必须严格串行。 两路 SETUP 之间没有依赖,可以并发,这是唯一能安全并行的位置。
  3. 以为并发发出就按顺序回来。 响应顺序不确定,必须按 CSeq 或 trackID 认领,不能按到达顺序。
自测三问(点开对答案) 1. 为什么 RTSP 不推荐流水线? —— 两个理由:请求之间有依赖(没 SETUP 完 PLAY 无意义),以及 CSeq 必须单调、 响应乱序会打乱配对。前者是硬约束。 2. 一次完整播放最少几轮往返? —— 用户等待部分是三轮:DESCRIBE、两路 SETUP 并发、PLAY。再往下省不动了,因为依赖链固定。 3. 并发发两路 SETUP,读响应时要注意什么? —— 不能假设先发先回。必须按 CSeq 或 trackID 认领响应,否则在有抖动的网络上会随机出错。

下一节把 11 个方法逐个过一遍:常用的就五个,其余的知道存在就行。

相关笔记