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

RTSP 最要紧的特点是「有状态」——它记得你在播什么、播到哪。 其余特点(文本、控制与数据分离、可扩展)都是从这一点长出来的。

📝 本节和前面几节的关系

上一节说了 RTSP 是什么,这一节说它「为什么长这样」。 有状态这件事,直接决定了会话管理、心跳保活、连接复用三件事的写法—— 也就是 01.04.04 到 01.04.07 那几节。

一、有状态:这是总开关

HTTP 每个请求都可以当新人对待,RTSP 不行。 因为「正在播」这件事本身就是一段持续的状态:播的是哪一路、游标在哪、是播还是停。

状态存在服务端,客户端靠一个 Session 号去认领它。 这就是为什么 SETUP 之后每条请求都要带 Session 头。

带来三个直接后果: 一、必须保活。状态不能永远留着,超时就被回收(01.04.04)。 二、必须能清理。正常结束发 TEARDOWN,异常结束靠超时兜底。 三、不能用流水线。请求之间有依赖,乱序发会乱套(01.04.09)。

二、文本协议:好调试,也费带宽

RTSP 报文是纯文本,一行一个字段,CRLF 分隔。 好处是抓包一眼能读,坏处是每个字段都要多花几个字节。

文本(RTSP) 二进制(RTP)
可读性 直接看 要工具解
开销 每字段带名字 靠位置,无冗余
扩展 加个头就行 要改格式
频率 一场几十条 一秒几十上百个

这张表说明为什么两个协议一个文本一个二进制:信令少,可读性值钱;媒体多,每个字节都要省。 一条 PLAY 请求一百多字节无所谓,一个 RTP 包多十字节就是 1% 带宽。

三、控制与数据分离

RTSP 跑 TCP,RTP 跑 UDP,各走各的。 这是 RTSP 架构上最重要的一条,也是排障时第一个要分清的东西。

分离带来两个好处理: 信令断了,媒体还能撑一会儿(反过来也成立); 媒体丢包不影响控制——不会因为丢了一个视频包就按不了暂停。

但也有一个坑:两边状态可能不同步。 典型现象是「画面还在动,但暂停没反应」——说明 RTSP 那条 TCP 已经断了,RTP 还在收。 这种时候看日志会发现保活失败,但画面骗过了眼睛。

四、可扩展

RTSP 留了扩展口子,规矩是「要用的先声明」。

机制 头 作用
要扩展 Require 我必须用到这个
表支持 Supported 我支持这些
报不支持 Unsupported 这些我都不支持
私有头 X- 开头 厂商自定义

不支持就回 551,不是 501:551 是「动词认识、参数不认」,501 是「动词不认识」。 这个区别在对接时要分清。 详细展开在 01.04.08。

五、缺点也得说清

RTSP 不完美,它的问题在今天更明显:

问题 后果 今天的替代
要开一堆 UDP 端口 防火墙难放行 RTP over TCP
没有 CDN 支持 大规模分发难 HLS/HTTP-FLV
URL 不统一 对接要逐家适配 ONVIF
明文认证 密码易泄露 rtsps/ONVIF 鉴权

但它没有消失,因为监控这个场景正好不吃这些亏:局域网不怕端口、路数固定不需要 CDN、 有 ONVIF 兜住 URL、内网可以忍明文。 技术在不在,取决于场景用不用得上——不是新的就一定赢。

六、动手 Demo:RTSP 是有状态的,状态就记在这几个数里

这段把「一个会话要记住什么」逐项列出来,再算总账。 每一项后面都写了「记不住会怎样」——这些就是真实排障时的报错。

# 一个 RTSP 会话在服务端要维持的状态
rows = ((u"Session ID", 8, u"下次请求对不上,回 454"),
        (u"状态机", 4, u"PLAY 之前没 SETUP,回 455"),
        (u"RTP/RTCP 端口对", 4, u"数据发到没人听的端口"),
        (u"当前游标", 4, u"暂停再播从头开始"))

tot = sum(r[1] for r in rows)
print(tot)                 # 单个会话的字节数
print(tot * 100)           # 一百路的总量

实跑输出:

--------------------------------------------------------------------
RTSP 是有状态的,状态就记在这几个数里
--------------------------------------------------------------------
**HTTP 每来一个请求都可以当新人对待,RTSP 不行。**
  因为播放这件事本身就是一段持续的状态:已经 SETUP 了哪个流、游标在哪、是播还是停。

  特征         HTTP                       RTSP
  ----------------------------------------------------------------------
  连接         短连接为主                 长连接,整场播放不断
  状态         无状态                     有状态,靠 Session 头维系
  默认端口     80/443                    554/322
  报文格式     文本,CRLF 分隔            同 HTTP 的文本格式
  方法数       GET/POST/PUT 等 8 个     11 个,见 D14
  带实体       经常                       只有 DESCRIBE 的响应带 SDP

**一个会话要记住的东西,可以逐项点出来:**

  要记的               大小       记不住会怎样
  -------------------------------------------------------------
  Session ID           8 字节     下次请求对不上,回 454
  状态机               4 字节     PLAY 之前没 SETUP,回 455
  RTP/RTCP 端口对     4 字节     数据发到没人听的端口
  当前游标             4 字节     暂停再播从头开始
  传输方式             1 字节     该发 UDP 的发成 TCP

**一台 IPC 只接 1 路是 21 字节,接 100 路就是 2100 字节。**
  数字很小,但**每个会话都得有个独立的状态机**——这才是 RTSP 服务端吃内存的地方,不是这几十字节。

总账只有二十字节,一百路也才两千字节——这个数小得惊人。 但它不是重点:真正吃资源的是「每一路都要有一个独立的状态机实例」, 以及每一路都要占一对 UDP 端口、一个定时器。

端口才是硬约束:一路要一对(RTP+RTCP),一千路就是两千个端口。 很多设备标称「支持 64 路」不是 CPU 不行,是端口和句柄到顶了。

那张表的第三列是本节最实用的部分,把这几个状态码记住: 454 是 Session 不存在(超时被回收了,重新 SETUP); 455 是方法在当前状态下不合法(没 SETUP 就 PLAY)。 看到 454 就去查心跳,看到 455 就去查调用顺序——这两个错占了对接问题的一大半。

30 秒实验

  1. 用播放器打开一路 RTSP,抓包看 SETUP 之后有多少条请求带了 Session 头。
  2. 把播放器挂一晚上,第二天看日志里有没有 454——有就说明心跳没做好。
  3. 故意先发 PLAY 再发 SETUP,看服务端回 455 还是 454,记下这台设备的习惯。

常见误区

  1. 以为 RTSP 和 HTTP 一样是无状态的。 它靠 Session 维系一段会话,所以必须保活、必须清理、 不能流水线。
  2. 以为信令断了画面就一定断。 两条通道独立,RTSP 断了 RTP 还能撑, 表现为「画面在动但操作没反应」。
  3. 以为有状态就是「服务端记住一切」。 服务端只记会话级状态, 客户端也要记(比如自己 SETUP 了哪几路),两边不同步就出怪问题。
自测三问(点开对答案) 1. 为什么 RTSP 不能用流水线? —— 请求之间有依赖——没 SETUP 完,PLAY 没有意义。而且 CSeq 必须单调,响应乱序回来配对会乱。 2. 为什么信令用文本、媒体用二进制? —— 信令一场只有几十条,可读性值钱;媒体一秒上百个包,每个字节都要省。 频率差两个数量级,选择就相反。 3. 看到设备回 454,该查什么? —— 查会话是否已被超时回收——也就是心跳有没有按时发。重新 SETUP 一次就能恢复。

下一节把五步流程走一遍:从 OPTIONS 到 TEARDOWN,每条报文长什么样。

相关笔记