RTSP 协议基础:语法结构、请求方法与状态码

RTSP 这个协议,说白了就是视频流的遥控器。你想想看,我们平时看视频,要点播放、暂停、拖动进度条——RTSP 干的就是类似的事,只不过它控制的是网络上的实时流媒体。

我记得刚接触 RTSP 时,总觉得它跟 HTTP 长得太像了。确实,它们都用文本格式,都有请求行和头部字段。但本质区别在哪?HTTP 是「请求-响应」一次完事,RTSP 是「建立会话-持续控制」——它维护着一个状态机。这一点,很多新手会栽跟头。

2.1 RTSP 的语法结构

RTSP 的报文分两种:请求和响应。结构非常清晰,我拆开来说。

请求报文

方法 URI RTSP版本
头部字段1: 值
头部字段2: 值
...
空行
消息体(可选)

举个例子,一个典型的 SETUP 请求长这样:

SETUP rtsp://example.com/media.mp4/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001

这里有个关键点:CSeq 字段。每个请求都必须带一个序列号,服务器响应时会把同样的 CSeq 带回来。这样客户端就能把请求和响应一一对应上。我在项目中遇到过有人忘了递增 CSeq,结果服务器一直返回 400——排查了半天才发现是序列号没变。

响应报文

RTSP版本 状态码 原因短语
头部字段1: 值
头部字段2: 值
...
空行
消息体(可选)

比如:

RTSP/1.0 200 OK
CSeq: 3
Session: 12345678
Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001

注意那个 Session 字段。服务器会在 SETUP 响应里分配一个会话 ID,后续所有操作都得带上它。嗯,这个 ID 就是整个推流会话的「身份证」。

核心要点:RTSP 报文是文本格式,每行以 CRLF(\r\n)结尾。空行是头部和消息体的分界线,不能省略。

2.2 五大核心请求方法

RTSP 定义了十几种方法,但实际推流场景中,90% 的交互靠下面这五个方法完成。我按调用顺序给你捋一遍。

方法 作用 调用时机
OPTIONS 查询服务器支持哪些方法 第一步,握手
DESCRIBE 获取媒体描述信息(SDP) 第二步,了解媒体
SETUP 建立传输通道,协商参数 第三步,准备传输
PLAY 开始播放/推流 第四步,开始传输
PAUSE 暂停播放/推流 播放中,临时暂停
TEARDOWN 终止会话,释放资源 最后一步,清理

OPTIONS — 先打个招呼

客户端发 OPTIONS,服务器回复它支持哪些方法。这步不是必须的,但我建议你加上。为什么?因为不同服务器实现有差异,有的不支持 PAUSE,有的不支持 GET_PARAMETER。提前摸清底细,后面少踩坑。

C -> S: OPTIONS rtsp://example.com/media.mp4 RTSP/1.0
         CSeq: 1

S -> C: RTSP/1.0 200 OK
         CSeq: 1
         Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE

你看,服务器在 Public 字段里列了它支持的方法。如果它没列 PAUSE,你就别发 PAUSE 了。

DESCRIBE — 看看媒体长什么样

DESCRIBE 用来获取媒体描述信息,服务器通常返回 SDP(Session Description Protocol)。SDP 里包含了视频编码格式、分辨率、帧率、音频采样率、RTP 负载类型等关键信息。

C -> S: DESCRIBE rtsp://example.com/media.mp4 RTSP/1.0
         CSeq: 2
         Accept: application/sdp

S -> C: RTSP/1.0 200 OK
         CSeq: 2
         Content-Type: application/sdp
         Content-Length: 142

         v=0
         o=- 123456 789012 IN IP4 192.168.1.1
         s=Media Presentation
         m=video 0 RTP/AVP 96
         a=rtpmap:96 H264/90000
         a=framerate:30
         m=audio 0 RTP/AVP 97
         a=rtpmap:97 MPEG4-GENERIC/16000/2

这里要注意:Accept: application/sdp 告诉服务器我要 SDP 格式。有些服务器也支持其他格式,但 SDP 是事实标准。

我的经验:DESCRIBE 返回的 SDP 里,m= 行定义了媒体轨道。一个视频文件可能有多个轨道(视频、音频、字幕),每个轨道需要单独 SETUP。我曾经遇到过一个流有 3 个音频轨道,只 SETUP 了第一个,结果播放器没声音——排查了半天才发现少 SETUP 了一个轨道。

SETUP — 搭桥铺路

SETUP 是 RTSP 里最复杂的方法。它要协商传输协议、端口号、组播还是单播、RTP 还是 RTCP。这一步搞不定,后面全白搭。

C -> S: SETUP rtsp://example.com/media.mp4/track1 RTSP/1.0
         CSeq: 3
         Transport: RTP/AVP;unicast;client_port=8000-8001

Transport 字段的格式:

  • 传输协议:RTP/AVP(标准 RTP)、RTP/AVP/TCP(RTP over TCP)、RTP/AVP/UDP(RTP over UDP)
  • 传输模式:unicast(单播)、multicast(组播)
  • 端口:client_port 是客户端 RTP/RTCP 端口,server_port 是服务器端口

服务器响应时会确认或修改传输参数:

S -> C: RTSP/1.0 200 OK
         CSeq: 3
         Session: 12345678
         Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001

注意这里多了 Session: 12345678。这个会话 ID 后续所有请求都得带上。

避坑指南:我曾经在项目中遇到一个问题——客户端发了 SETUP,服务器也返回了 200 OK,但 RTP 数据就是收不到。后来发现是 NAT 没做端口映射。如果你在内网环境测试没问题,部署到公网就出问题,先检查防火墙和 NAT 配置。

PLAY — 开闸放流

SETUP 成功后,发 PLAY 开始传输数据。可以指定播放范围:

C -> S: PLAY rtsp://example.com/media.mp4 RTSP/1.0
         CSeq: 4
         Session: 12345678
         Range: npt=0.000-

Range: npt=0.000- 表示从开头播到结束。NPT 是 Normal Play Time,单位是秒。你也可以指定时间段,比如 npt=10.0-20.0。

服务器响应:

S -> C: RTSP/1.0 200 OK
         CSeq: 4
         Session: 12345678
         RTP-Info: url=rtsp://example.com/media.mp4/track1;seq=0;rtptime=0

RTP-Info 字段告诉客户端:RTP 序列号从 0 开始,时间戳从 0 开始。客户端拿到这个信息,才能正确解码 RTP 包。

PAUSE — 暂停一下

PAUSE 很简单,就是暂停播放。服务器会停止发送 RTP 数据,但保持会话:

C -> S: PAUSE rtsp://example.com/media.mp4 RTSP/1.0
         CSeq: 5
         Session: 12345678

恢复播放时再发一次 PLAY 就行。注意:PAUSE 不是所有服务器都支持,OPTIONS 阶段可以确认。

TEARDOWN — 好聚好散

推流结束,发 TEARDOWN 释放资源。服务器会关闭 RTP 会话,回收端口和内存:

C -> S: TEARDOWN rtsp://example.com/media.mp4 RTSP/1.0
         CSeq: 6
         Session: 12345678

响应:

S -> C: RTSP/1.0 200 OK
         CSeq: 6
         Session: 12345678

之后这个会话 ID 就失效了。如果你忘了发 TEARDOWN,服务器可能会一直占用资源——我见过有人写测试脚本,每次跑完都不发 TEARDOWN,结果服务器上挂了上百个僵尸会话。

2.3 状态码详解

RTSP 的状态码和 HTTP 基本一致,但有几个是 RTSP 特有的。我列了最常用的几个:

状态码 含义 常见场景
200 OK 请求成功 一切正常
301 Moved Permanently 资源永久重定向 服务器迁移,客户端需更新 URL
302 Moved Temporarily 资源临时重定向 负载均衡,客户端重试新地址
400 Bad Request 请求语法错误 CSeq 重复、头部格式不对
401 Unauthorized 需要认证 服务器要求用户名密码
403 Forbidden 禁止访问 权限不足
404 Not Found 资源不存在 URL 写错了
405 Method Not Allowed 方法不被支持 服务器不支持 PAUSE
454 Session Not Found 会话 ID 无效 会话已过期或从未建立
455 Method Not Valid in This State 当前状态不允许此方法 还没 SETUP 就发 PLAY
500 Internal Server Error 服务器内部错误 服务器崩了
551 Option not supported 请求中的选项不支持 Transport 参数服务器不认

这里有两个 RTSP 特有的状态码,我重点说一下:

  • 454 Session Not Found:你发了 PLAY,但服务器说「我不认识这个会话」。原因通常是会话超时了,或者你忘了在请求里带 Session 字段。
  • 455 Method Not Valid in This State:这是 RTSP 状态机在保护你。比如你还没 SETUP 就发 PLAY,服务器会直接拒绝。顺序搞错了。

记住这个顺序:OPTIONS → DESCRIBE → SETUP → PLAY → PAUSE(可选)→ TEARDOWN。跳步就会收到 455。

2.4 RTSP 状态机与交互流程

RTSP 维护着一个状态机。客户端和服务器各自跟踪当前状态,只有合法的方法才能被接受。我画了一张图,把整个流程串起来:

RTSP 推流交互流程 RTSP 推流交互流程 客户端 服务器 状态 Init(初始) Ready(就绪) Playing(播放中) ① OPTIONS ② DESCRIBE ③ SETUP ④ PLAY ⑤ PAUSE ⑥ TEARDOWN 响应 SDP Session ID RTP 数据 暂停 释放 说明:PAUSE 为可选步骤,虚线表示;TEARDOWN 后状态回到 Init

从图里能看出来,状态转换是有严格顺序的:

  • Init → Ready:通过 SETUP 建立传输通道
  • Ready → Playing:通过 PLAY 开始传输
  • Playing → Ready:通过 PAUSE 暂停
  • Ready → Init:通过 TEARDOWN 终止

如果你在 Playing 状态直接发 SETUP,服务器会返回 455。嗯,这就是状态机在保护你。

我的建议:写客户端代码时,最好维护一个状态变量。每次发请求前检查当前状态是否合法。我见过太多人忽略这一步,结果线上出了各种诡异的 455 错误。

2.5 常见问题与排查思路

最后,我总结几个实际项目中经常遇到的问题:

  1. 401 Unauthorized:服务器要求认证。RTSP 支持 Basic 和 Digest 两种认证方式,和 HTTP 一样。记得在请求头里加 Authorization 字段。
  2. 454 Session Not Found:会话超时或 ID 错误。检查请求里有没有带 Session 字段,以及会话是否还在有效期内。
  3. Transport 协商失败:客户端说我要 UDP,服务器说我只支持 TCP。这时候要么客户端改用 TCP,要么服务器开启 UDP 支持。我建议优先用 TCP,穿透性好,不容易被防火墙拦截。
  4. RTP 数据收不到:SETUP 和 PLAY 都返回 200 OK,但就是没数据。先检查端口对不对,再检查防火墙。用 Wireshark 抓包是最直接的排查方式。

避坑指南:我曾经在项目中遇到一个诡异的问题——客户端发 PLAY 后,服务器返回 200 OK,但 RTP 数据延迟了 5 秒才到。后来发现是服务器端做了缓冲,Range 字段没指定起始时间,服务器从文件头开始读,而文件头有大量元数据。加上 Range: npt=0.000- 后问题解决。