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

一次完整播放就五步:OPTIONS → DESCRIBE → SETUP → PLAY → TEARDOWN。 记住这五步和它们的顺序,RTSP 就懂一半了。

📝 本节和前面几节的关系

前面两节讲了 RTSP 是什么、为什么长这样,这一节把流程走通。 这一节是本章的骨架:01.04 的九个概念、01.05 的十一个方法,都是往这五步里填细节。 播放器里这条流程怎么落地, 看 34 课 的第七章。

一、五步,各要一句话

每一步都有一个明确的目的,跳过任何一步都会失败。

步 方法 目的 关键产出
1 OPTIONS 问对方会什么 Public 头
2 DESCRIBE 要说明书 SDP
3 SETUP 建数据通道 Session 号
4 PLAY 开播 RTP 开始到达
5 TEARDOWN 收摊 端口释放

顺序不能乱,但 OPTIONS 可以省:很多客户端直接发 DESCRIBE,设备也照样响应。 省它是为了少一轮往返,不是因为它不重要。

SETUP 可能不止一次:有视频有音频就要 SETUP 两次(trackID=0 和 1),但 PLAY 只发一次, 作用于整组。

二、逐条报文长什么样

下面五条是真报文,可以直接拿去粘:

OPTIONS rtsp://192.168.1.64:554/Streaming/Channels/101 RTSP/1.0
CSeq: 1
User-Agent: python-rtsp/1.0

DESCRIBE rtsp://192.168.1.64:554/Streaming/Channels/101 RTSP/1.0
CSeq: 2
Accept: application/sdp

SETUP rtsp://192.168.1.64:554/Streaming/Channels/101/trackID=0 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001

PLAY rtsp://192.168.1.64:554/Streaming/Channels/101 RTSP/1.0
CSeq: 4
Session: 1A2B3C4D
Range: npt=0.000-

TEARDOWN rtsp://192.168.1.64:554/Streaming/Channels/101 RTSP/1.0
CSeq: 5
Session: 1A2B3C4D

注意三处细节: 每条的首行都是「方法 + 完整 URL + RTSP/1.0」; CSeq 从 1 开始逐条加一,响应必须原号返回; SETUP 之后的每条都带 Session,PLAY 还多了个 Range(从 0 秒开始)。

三、CSeq 是唯一的配对手柄

RTSP 允许一条连接上有多个请求在飞(虽然不推荐),所以响应必须能对上号。 CSeq 就是干这个的,它是整个协议里最简单的机制,也是最能出错的机制。

常见错误三种: 从 0 开始(应该从 1 开始,0 保留); 不递增(重发时保持原号是允许的,但新请求必须加一); 响应里改了号(客户端直接丢弃,表现为「发了请求没反应」)。

排障时先看 CSeq:请求发出去、响应回来了,但客户端说超时,八成就是 CSeq 对不上。

四、状态码:记住六个就够

RTSP 借了 HTTP 的状态码体系,1xx 到 5xx。

| 码 | 含义 | 常见原因 | |:---:|---|---|---| | 200 | OK | 正常 | | 401 | 需要认证 | 没带 Authorization | | 404 | 找不到 | URL 路径或通道号错 | | 454 | 会话不存在 | 超时被回收,或 Session 写错 | | 455 | 方法在此状态不合法 | 没 SETUP 就 PLAY | | 551 | 选项不支持 | Require 里要了设备没有的扩展 |

401 是监控对接的第一道坎:设备默认开认证,第一次 DESCRIBE 必回 401, 要求你带 Authorization 重发。 两种认证方式:Basic(用户名密码 base64)和 Digest(挑战响应)。 海康大华默认多是 Digest,Basic 在它们身上常常直接 401。

五、失败的长什么样

真实排障里,失败比成功常见。三条典型分支:

分支一:401 循环。 带了 Authorization 还是 401,通常是 Digest 的 nonce 算错, 或者用户名里混了域。

分支二:SETUP 返回 461。 461 是「不支持的传输方式」,多半是设备关了 UDP, 只认 RTP/AVP/TCP;interleaved=0-1。 改成 TCP 就通了——这也是穿透 NAT 时最常用的招。

分支三:PLAY 回来了但没数据。 这是最坑的一种:信令全通,RTP 一个包都不来。 常见原因是端口被防火墙挡了、或者客户端告诉设备的端口没人监听。 验证办法:在同一个端口上开个 UDP 监听, 看有没有包进来——分不清是「设备没发」还是「本机没收到」,就没法往下查。

六、动手 Demo:一次完整的 RTSP 播放,来回几句话

这段用真实的 RFC 报文格式,把五条请求逐条造出来,并量出字节数。 每条都带 CSeq,服务端的响应必须原号返回。

base = u"rtsp://192.168.1.64:554/Streaming/Channels/101"
steps = [(u"OPTIONS", base, [u"User-Agent: python-rtsp/1.0"]),
         (u"DESCRIBE", base, [u"Accept: application/sdp"]),
         (u"SETUP", base + u"/trackID=0",
          [u"Transport: RTP/AVP;unicast;client_port=8000-8001"]),
         (u"PLAY", base, [u"Session: 1A2B3C4D", u"Range: npt=0.000-"]),
         (u"TEARDOWN", base, [u"Session: 1A2B3C4D"])]

for i, (m, url, hdrs) in enumerate(steps, 1):
    lines = [u"%s %s RTSP/1.0" % (m, url), u"CSeq: %d" % i] + hdrs
    txt, nb = _crlf(lines)      # 拼 CRLF 并数字节
    T([m, u"%d" % i, why, u"%d" % nb], [0, 0, 0, 0])

实跑输出:

--------------------------------------------------------------------
一次完整的 RTSP 播放,来回几句话
--------------------------------------------------------------------
**下面五条请求是真报文,格式按 RFC 2326,可以直接拿去粘。**
  注意每条都带 `CSeq`,服务端的响应必须原号返回,靠它配对。

  步骤         CSeq     做什么                     字节
  ---------------------------------------------------------
  OPTIONS      1        问服务端支持哪些方法       105
  DESCRIBE     2        要一份 SDP,看有几路流     102
  SETUP        3        给这一路流分配端口         134
  PLAY         4        开播,从头放到尾           111
  TEARDOWN     5        收摊,释放资源             96

**五句话加起来 548 字节。**
  一次播放的信令就这么点,**真正的流量全在后面的 RTP 上**。

**SETUP 那条最值得看,逐字拆开:**

  SETUP rtsp://…/trackID=0 RTSP/1.0
  CSeq: 3
  Transport: RTP/AVP;unicast;client_port=8000-8001

  `RTP/AVP` 是「用 RTP 传,负载类型按 AVP 的约定」;`unicast` 是单播;
  `client_port=8000-8001` 是**客户端**告诉服务端「往我这两个端口发」——8000 收 RTP,8001 收 RTCP。
  **注意是客户端先开口**,服务端照着发。这是 RTSP 和 HTTP 一样的地方:永远客户端发起。

五条加起来才一千多字节,一次播放的信令就这么多。 对比一下:1080p 一秒就是 500 KB 的媒体数据——信令占的比例小到可以忽略。 所以 RTSP 从来不是带宽问题,是「通不通」的问题。

SETUP 那条最值得逐字看(正文里拆过了):客户端主动告诉服务端「往我这两个端口发」。 这个方向容易记反——很多人以为服务端选端口再通知客户端,实际上端口是客户端定的, 服务端只是回一个 server_port 告诉你它从哪发。

为什么客户端定端口:因为在 NAT 后面,只有本机知道自己哪个端口已经打了洞。 服务端定的话,包根本进不来。 这条设计在 1996 年就有了,比 NAT 穿透这个词流行早得多。

30 秒实验

  1. 用 ffplay 打开一路 RTSP,同时抓包,把五条请求按顺序找出来。
  2. 把 TEARDOWN 去掉,等 Session 超时,看服务端多久回收(通常是 60 秒)。
  3. 故意把 CSeq 改成不变,看客户端会不会报超时——体会这个字段的作用。

常见误区

  1. 以为五步必须全部走完。 OPTIONS 可以省,很多客户端直接 DESCRIBE; SETUP 则可能要走两次(视频一次、音频一次)。
  2. 以为服务端决定 RTP 端口。 是客户端在 SETUP 里指定 client_port, 服务端照着发——这样 NAT 后面的客户端才收得到。
  3. 以为收到 200 就说明能播。 PLAY 返回 200 只代表信令通了,RTP 有没有来是另一回事, 得看端口上有没有包。
自测三问(点开对答案) 1. 五步里哪一步可以省,哪一步可能要走两次? —— OPTIONS 可以省;SETUP 可能走两次——视频 trackID=0 一次,音频 trackID=1 一次。 PLAY 只发一次,作用于整组。 2. SETUP 里的 client_port 是谁定的?为什么? —— 客户端定的。 因为客户端可能在 NAT 后面,只有它知道自己哪个端口已经打了洞,服务端指定的话包进不来。 3. 信令全通但没画面,先查什么? —— 先查端口上有没有 UDP 包进来。 分不清「设备没发」还是「本机没收到」,就没法往下查——这是二分的第一步。

下一节开始拆概念:从「集合控制」讲起,搞清一个 URL 到底能管几路流。

相关笔记