RTSP 是遥控器,不是送货车——它只发命令,画面走另一条通道。 一条 OPTIONS 请求就能问清服务器会哪八招,客户端不必硬编码。
第一章讲了「直播是一条链路上挑一段深挖」,RTSP 挖的就是「控制」那一段。 它是所有协议里唯一带暂停、带回放、带拖动的,因为它是为监控发明的。
一、RTSP 是遥控器,不是送货车
一句话说清 RTSP 的定位:它只负责「让画面开始、暂停、停下」,自己不搬运画面。
一条会话里其实跑着三个协议: ① RTSP:控制。文本协议,跑在 TCP 上,默认端口 554。 ② RTP:搬运。二进制,通常跑在 UDP 上,端口在会话里临时协商(第 3 章)。 ③ RTCP:反馈。和 RTP 成对出现,报丢包和抖动(第 3 章)。
正因为分工这么清楚,RTSP 的报文都很小——几十到几百字节,一条请求往往还没一个 TCP 握手包大。
这和 HLS、HTTP-FLV 是根本区别:那两个协议是「请求一次,换回一大段数据」,RTSP 是「下个命令,数据从别的门进来」。 所以拿浏览器直接打开一个 rtsp:// 地址是播不了的——浏览器只会 HTTP,而 RTSP 根本不是 HTTP。
二、OPTIONS:一条请求问清全部本事
OPTIONS 是所有方法里最「便宜」的一个:它什么都不改,只是问一句「你会什么」。
请求只有两行:
① 第一行:OPTIONS rtsp://127.0.0.1/live/test RTSP/1.0,方法、URL、版本。
② 第二行:CSeq: 1,序号,响应要原样带回来。
③ 一个空行:表示头域到此结束。
响应里关键是 Public 头,它把服务器支持的方法列成一串:
OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER。
从这串里能立刻判断出这台设备能干什么: 有 PLAY 没有 RECORD → 只能看,不能推。 有 RECORD → 这是一台可以推流的设备。 没有 PAUSE → 便宜的网络摄像头,通常只实现最小的四步。
这个思路和 HTTP 的 OPTIONS 完全一样:先探测能力,再决定流程。 工程上的好处是客户端不用为每个厂家写一套适配。
三、八个方法分三组记
八个方法死记很难,按「改不改状态」分三组就清楚了:
① 协商组(只问不改) OPTIONS 问能力;DESCRIBE 要一份 SDP,看清有几路流、什么编码。这两个方法调多少次都不影响会话。 ② 会话组(一建一拆) SETUP 建传输通道并分配 Session 号;TEARDOWN 拆掉它、释放端口。这两条是「端到端」的一对,中间不能凭空跳。 ③ 播放组(改传输状态) PLAY 开始发流,PAUSE 停发但保留会话,RECORD 是反向的推流。这三个可以在会话里反复来回。
剩下的 GET_PARAMETER/SET_PARAMETER 是读写参数的,实际用得最多的是「当心跳」。 因为 RTSP 会话有超时(通常 60 秒),不发东西就会被服务器拆掉,而这两个方法改动最小、最安全。
四、它在协议栈的哪一层
RTSP 是应用层协议,和 HTTP 平级,通常跑在 TCP 上。 这一点常有人问:既然追求实时,为什么不用 UDP?
因为命令不能丢。 一条 PLAY 丢了,画面就永远不开始;一条 TEARDOWN 丢了,端口就一直被占着。控制信令的可靠性比速度重要得多。
而真正的数据(RTP)默认走 UDP——丢一帧画面花一下,总比停下来等重传造成卡顿好。一个要可靠,一个要快,这正是把控制与数据分开设计的理由。
实际部署里也允许 RTP over TCP(Transport 头里写 RTP/AVP/TCP;interleaved=0-1)。穿防火墙时常用这招:反正只有 554 一个端口对外开放。
五、动手 Demo:OPTIONS 一次问清服务器会哪几招
写一个只认 OPTIONS 的服务器,再发一条真实的 OPTIONS 请求过去,把响应原样打出来。
看 Public 头是怎么把八个方法一次交代清楚的。
import socket, threading
PUB = ("OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, "
"PAUSE, GET_PARAMETER, SET_PARAMETER")
def serve(sock):
c, _ = sock.accept()
req = c.recv(8192).decode()
cseq = "1"
for l in req.split("\r\n")[1:]:
if l.lower().startswith("cseq:"):
cseq = l.split(":", 1)[1].strip()
resp = ("RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"Public: %s\r\n\r\n" % (cseq, PUB))
c.sendall(resp.encode())
c.close()
srv = socket.socket()
srv.bind(("127.0.0.1", 0))
srv.listen(4)
port = srv.getsockname()[1]
threading.Thread(target=serve, args=(srv,), daemon=True).start()
req = "OPTIONS rtsp://127.0.0.1/live/test RTSP/1.0\r\nCSeq: 1\r\n\r\n"
s = socket.create_connection(("127.0.0.1", port), 3.0)
s.sendall(req.encode())
print(s.recv(8192).decode())
s.close()
实跑输出:
--------------------------------------------------------------------
RTSP 支持——OPTIONS 一次问清服务器会哪几招
--------------------------------------------------------------------
① 请求长什么样
| OPTIONS rtsp://127.0.0.1/live/test RTSP/1.0
| CSeq: 1
② 服务器回什么
| RTSP/1.0 200 OK
| CSeq: 1
| Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
序号 方法 干什么
-------------------------------------------------------
1 OPTIONS 问「你支持哪些方法」
2 DESCRIBE 要媒体描述(SDP)
3 SETUP 建传输通道,定端口
4 TEARDOWN 拆掉会话
5 PLAY 开始发流
6 PAUSE 暂停
7 GET_PARAMETER 读参数(也常被当心跳用)
8 SET_PARAMETER 写参数
**一共 8 个方法,前 6 个是 RTSP 的核心,后 2 个是参数读写。**
**注意它是「先问再答」的——客户端不必硬编码,服务器支持什么自己会说。**
**这一点和 HTTP 的 OPTIONS 是同一个思路。**
八个方法在一行 Public 头里全交代了,客户端不用猜、不用试,读一遍就知道该怎么走。
注意 CSeq: 1 被原样带回来了——这是配对用的,下一条请求就该是 CSeq: 2。
服务器其实什么都没做,只回了一个能力清单,但这就是 RTSP 会话的起点。
30 秒实验
- 把请求里的方法换成
DESCRIBE,服务器会回什么?它还是会回 200 加同一串 Public——因为这个最小实现根本没分支。 - 把
CSeq改成7再跑,看响应里的CSeq是不是跟着变。这就是「响应必须原样带回 CSeq」的含义。 - 把
PUB里的PAUSE删掉再跑,想象一下客户端拿到这个清单后,界面上的暂停按钮应该怎么处理。
常见误区
- 「RTSP 是一种视频编码」——它是控制协议,一行画面都不编。编码是 H.264/H.265 的事,搬运是 RTP 的事。
- 「OPTIONS 是可有可无的」——不调也能跑,但你就不知道对端支不支持 PAUSE,只能靠试错。工业设备差异很大,先探测一下最省事。
- 「RTSP 只能看不能推」——支持
RECORD的服务器就能收推流,监控摄像头的「录像上传」、部分 NVR 的回传都走这条路。
自测三问(点开对答案)
1. RTSP 会话里一共有几个协议在跑?各管什么? —— **三个:RTSP 管控制、RTP 搬数据、RTCP 报统计。**只有 RTSP 是文本协议。 2. 为什么 RTSP 的命令走 TCP,数据却走 UDP? —— **命令丢一条会话就废了,必须可靠;数据丢一帧只是花一下,重传反而造成卡顿。**一个要可靠,一个要快。 3. 服务器 `Public` 头里没有 PAUSE,意味着什么? —— **这台设备不支持暂停。**客户端不该把暂停按钮做成可用的,否则点了会收到 405。- ☐ (本节完,下一节 2.1.2 RTSP特点)