🎬 第二章 RTSP流媒体协议
📄 一句话概括

RTSP 的请求报文只有三行骨架:请求行、一堆头域、一个空行。 真正需要背下来的只有 CSeq——它是唯一一个错了就整条会话串掉的字段。

📝 本节和前面几节的关系

2.1.2 讲了 RTSP 有状态,这一节开始看它长什么样。 报文格式是后面所有 Demo 的地基,抓包时认的就是这几行。

一、三行骨架

把一条请求摊平,结构简单到不像话:

① 请求行:方法 URL 版本,三个字段用空格分开,以 \r\n 结尾。 ② 头域:名字: 值,一行一个,顺序无所谓。 ③ 空行:一个孤零零的 \r\n,标记头域结束。

请求没有「体」。 这是 RTSP 和 HTTP 的一个区别:HTTP 的 POST 后面跟着一堆数据,RTSP 的请求几乎永远是纯文本头域,一个字节的媒体数据都不带。

唯一的例外是 ANNOUNCE——推流时要把 SDP 正文附在请求后面,那时才会出现 Content-Length 和真正的体。但那是推流场景,拉流用不到。

二、请求行三个字段

SETUP rtsp://127.0.0.1/live/test RTSP/1.0——三个字段各有讲究:

① 方法:大写,必须是服务器 Public 头里列出来的那些。写个小写的 play 会被当成未知方法回 405。 ② URL:完整地址,含 rtsp://、主机、端口(可省略)、路径。注意 SETUP 时 URL 后面要带 trackID,因为一路流可能有视频和音频两条轨道。 ③ 版本:固定 RTSP/1.0。目前实际部署的全是 1.0,虽然标准里有 2.0,但基本没人用。

URL 里那个 trackID 是最容易漏的。 DESCRIBE 拿回来的 SDP 里每条 m= 行都有一个 a=control: 属性,它的值就是这一路的 trackID。 SETUP 时必须把它拼到 URL 后面,服务器才知道你要建哪一路的通道。

三、头域哪些必填

头域十几个,但真正必填的只有两三个:

① CSeq:所有请求必填。序号,通常从 1 开始递增。 ② Transport:只有 SETUP 必填。它说清「用什么协议传、我在哪个端口收」,格式如 RTP/AVP;unicast;client_port=5000-5001。 ③ Session:SETUP 之后的请求必填。PLAY、PAUSE、TEARDOWN 都得带上,否则 454。

选填的两三个也有用: User-Agent:客户端标识。有些服务器会按它做差异化处理,比如给 VLC 和给摄像头返回不同的 SDP。 Range:播哪一段。npt=0- 表示从头播到尾,npt=10-20 表示只播第 10 到 20 秒。回放功能就靠它。 Accept:能接受什么格式的 SDP 描述。用得不多,默认就好。

四、CSeq 为什么最要命

因为 RTSP 允许「流水线」:客户端可以不等响应,连着发好几条请求。 这时候响应回来的顺序可能是乱的,唯一的配对依据就是 CSeq。

规则只有一条:响应里的 CSeq 必须等于请求里的 CSeq。 服务器照抄一遍,客户端拿它去查「这条响应是回哪条请求的」。

错了会怎样? 轻则客户端把 PLAY 的响应当成 SETUP 的,以为没拿到 Session,于是一直在重发; 重则整条会话的状态全乱,后面每一步都基于错误的假设。

实践中的建议: ① 单调递增,不要复用(有些实现会拒绝重复的 CSeq)。 ② 每条请求都检查响应里的 CSeq,对不上就丢掉,别往下走。 ③ 不要用 0——很多库把 0 当「未设置」。

五、动手 Demo:手写一条 SETUP,再逐字段拆开

不连服务器,先把一条真实的 SETUP 请求写成字符串,再按 \r\n 切开逐行看。 这比抓包更直观:你能看到每一个字段到底在哪个位置。

req = ("SETUP rtsp://127.0.0.1/live/test/trackID=0 RTSP/1.0\r\n"
       "CSeq: 3\r\n"
       "Transport: RTP/AVP;unicast;client_port=5000-5001\r\n"
       "User-Agent: ch30-demo\r\n"
       "\r\n")

print("整条请求 %d 字节" % len(req.encode()))
print(repr(req))

line, rest = req.split("\r\n", 1)
meth, url, ver = line.split(" ")
print("方法=%s 版本=%s" % (meth, ver))
print("主机+路径 = %s" % url[len("rtsp://"):])

for h in rest.split("\r\n"):
    if not h:
        print("-- 空行,头域结束 --")
        continue
    k, v = h.split(": ", 1)
    print("  %-10s | %s" % (k, v))

# Transport 里的两个端口
cp = "5000-5001"
a, b = cp.split("-")
print("RTP 收在 %s(偶),RTCP 收在 %s(奇)" % (a, b))

实跑输出:

--------------------------------------------------------------------
请求消息——RTSP 的报文只有三行骨架
--------------------------------------------------------------------
① 一条完整的 SETUP 请求

  | SETUP rtsp://127.0.0.1/live/test RTSP/1.0
  | CSeq: 3
  | Transport: RTP/AVP;unicast;client_port=5000-5001
  | User-Agent: ch30-demo

② 拆开看

  部分       内容                             意思
  -----------------------------------------------------------------------
  请求行     SETUP rtsp://127.0.0.1/live/     方法 空格 URL 空格 版本
  方法       SETUP                            告诉服务器要干什么
  URL        127.0.0.1/live/test              对哪个资源做
  版本       RTSP/1.0                         固定 RTSP/1.0

③ 每个头域都在解决一个具体问题

  头域           必填            作用
  ---------------------------------------------------------------------
  CSeq           是              请求序号,响应必须原样带回来
  Transport      SETUP 必填      用什么协议传、客户端收在哪个端口
  User-Agent     否              客户端标识,服务器可能据此差别对待
  Session        PLAY 起必填     认领之前 SETUP 拿到的会话号
  Range          否              播哪一段,npt=0- 表示从头

  **CSeq 是最容易被忽略又最要命的一个:**
  **响应的 CSeq 必须等于请求的 CSeq,**
  **客户端就是靠它把响应和请求配对。乱了这个,整条会话就串了。**

整条请求只有一百多字节,比一个 TCP 握手包大不了多少。这就是「遥控器」的体量。 Transport 里那两个端口是一偶一奇、连号的,不是随便两个数。RTP 用偶数,RTCP 用下一个奇数。 URL 末尾的 trackID=0 是拼上去的,不是服务器给的完整地址——它来自 SDP 里的 a=control:。

30 秒实验

  1. 把 CSeq 改成 0 再跑,看打印出来的字段有没有变——报文本身不会报错,但有些服务器会拒收。
  2. 把 Transport 换成 RTP/AVP/TCP;interleaved=0-1 再跑,体会一下「RTP over TCP」只是换了个描述,请求格式完全没变。
  3. 给请求加一行 Range: npt=10-20,这就是回放第 10 到 20 秒的写法。

常见误区

  1. 「请求里要带媒体数据」——拉流的请求永远不带数据,纯文本头域。数据从 RTP 通道进来。
  2. 「URL 就是 rtsp://host/path」——SETUP 时必须带 trackID,否则服务器不知道你要建哪条轨道。多轨(音视频分开)时更是必须。
  3. 「CSeq 随便填」——服务器会原样带回、客户端靠它配对。流水线场景下填错就是整条会话错位。
自测三问(点开对答案) 1. 一条请求的三部分是什么? —— **请求行、头域、空行。**没有体(ANNOUNCE 除外)。 2. `Transport: RTP/AVP;unicast;client_port=5000-5001` 说明什么? —— **用 RTP over UDP、单播,客户端在 5000 收 RTP、5001 收 RTCP。**偶奇成对是硬规矩。 3. 哪个头域错了会让整条会话串掉? —— **`CSeq`。**它是响应与请求之间唯一的配对依据。

相关笔记