第4章:RTSP协议详解

RTSP,全称是实时流协议。说白了,它就是用来控制音视频流的「遥控器」。你想想看,我们在手机上点播放、暂停、快进,这些操作背后是谁在干活?就是RTSP。

我最早接触RTSP是在做网络摄像头项目的时候。那时候刚入行,以为RTSP和HTTP差不多,结果踩了不少坑。今天我就把这些经验掰开揉碎了讲给你听。

4.1 RTSP协议结构

RTSP的报文结构和HTTP非常像。它也是文本协议,用ASCII码传输。我个人习惯把RTSP报文分成三部分:

  • 起始行:请求行或状态行
  • 头部字段:一堆key: value对
  • 消息体:通常是SDP描述

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

DESCRIBE rtsp://192.168.1.100:554/stream1 RTSP/1.0
CSeq: 2
User-Agent: MyRTSPClient/1.0
Accept: application/sdp

嗯,这里要注意:CSeq字段是必须的。它是个序列号,用来匹配请求和响应。我在项目中遇到过有人忘了递增CSeq,结果服务器一直返回454 Session Not Found。排查了半天才发现是序列号没变。

4.2 请求/响应模型

RTSP的请求/响应模型和HTTP基本一致。客户端发请求,服务器回响应。但有个关键区别:RTSP是有状态的

什么意思?HTTP每次请求都是独立的,服务器不记得你是谁。但RTSP不一样,它需要维护一个会话状态。你发了SETUP,服务器就记住了你的传输参数;你发了PLAY,服务器就开始推流。

响应码也和HTTP类似:

状态码 含义 我遇到的坑
200 OK 请求成功 最常见,但要注意消息体是否完整
454 Session Not Found 会话不存在 通常是CSeq或Session ID搞错了
455 Method Not Valid in This State 方法在当前状态无效 比如还没SETUP就发PLAY
461 Unsupported Transport 不支持的传输方式 RTP/AVP和RTP/AVP/TCP要分清
避坑指南:我曾经在调试一个海思方案的摄像头时,发现每次发PLAY都返回455。查了半天,原来是SETUP成功后服务器返回了Session ID,但我没保存。下次请求时Session ID为空,服务器以为我没建立会话。记住:Session ID必须缓存,每次请求都要带上

4.3 SDP会话描述

SDP全称是Session Description Protocol,会话描述协议。它不负责传输数据,只负责描述媒体信息。说白了,就是告诉客户端:「我这路视频是H.264编码,音频是AAC,用RTP打包,端口号是5000」。

一个典型的SDP长这样:

v=0
o=- 123456 789012 IN IP4 192.168.1.100
s=Live Stream
c=IN IP4 0.0.0.0
t=0 0
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42E01E;sprop-parameter-sets=Z0IACpY1QKABw...
m=audio 0 RTP/AVP 97
a=rtpmap:97 MPEG4-GENERIC/16000/1
a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;config=1408

我来拆解一下关键字段:

  • v=0:SDP版本号,目前只有0
  • o=:会话发起者信息,包括用户名、会话ID、版本号、网络类型、地址
  • s=:会话名称
  • c=:连接信息,IP地址和网络类型
  • t=:会话有效时间,0 0表示一直有效
  • m=:媒体描述,包括媒体类型、端口、传输协议、格式
  • a=:属性行,描述媒体的具体参数

重点来了:m=行里的端口号为什么是0?因为RTSP的SETUP阶段会重新协商端口。这个0只是个占位符,表示「待协商」。我刚开始做的时候以为端口写错了,折腾了好久才发现这是标准做法。

还有一个容易忽略的点:sprop-parameter-sets。这是H.264的SPS和PPS,Base64编码后放在SDP里。解码器需要先拿到这些参数才能正确解码。我在项目中遇到过摄像头输出的SPS格式不对,导致所有客户端都花屏。排查方法是用工具解码Base64,看看SPS里的分辨率、帧率参数是否合理。

4.4 RTSP交互流程

RTSP的交互流程,说白了就是「四步走」:OPTIONS、DESCRIBE、SETUP、PLAY。我习惯叫它「ODSP流程」。每个步骤都有它的作用,缺一不可。

4.4.1 OPTIONS

第一步,客户端问服务器:「你支持哪些方法?」

客户端 -> 服务器:
OPTIONS rtsp://192.168.1.100:554/stream1 RTSP/1.0
CSeq: 1

服务器 -> 客户端:
RTSP/1.0 200 OK
CSeq: 1
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

服务器返回的Public字段列出了所有支持的方法。我建议你不要假设服务器支持所有方法。有些低端摄像头只支持DESCRIBE、SETUP、PLAY、TEARDOWN这四个基本方法,连PAUSE都不支持。

4.4.2 DESCRIBE

第二步,客户端问服务器:「你这路流是什么格式的?」

客户端 -> 服务器:
DESCRIBE rtsp://192.168.1.100:554/stream1 RTSP/1.0
CSeq: 2
Accept: application/sdp

服务器 -> 客户端:
RTSP/1.0 200 OK
CSeq: 2
Content-Type: application/sdp
Content-Length: 456

[这里就是上面提到的SDP内容]

DESCRIBE返回的就是SDP。客户端解析SDP后,就知道视频编码、音频编码、传输参数等信息。我遇到过一个问题:有些服务器返回的SDP里Content-Length和实际长度不符,导致解析失败。解决办法是不要完全信任Content-Length,读到空行后继续读,直到遇到下一个请求或超时

4.4.3 SETUP

第三步,客户端告诉服务器:「我要用UDP还是TCP传数据,端口号是多少」。

客户端 -> 服务器:
SETUP rtsp://192.168.1.100:554/stream1/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=5000-5001

服务器 -> 客户端:
RTSP/1.0 200 OK
CSeq: 3
Session: 12345678
Transport: RTP/AVP;unicast;client_port=5000-5001;server_port=6000-6001

这里有几个关键点:

  • track1:SDP里可能有多个track(视频、音频各一个),每个track需要单独SETUP
  • Transport:传输方式,RTP/AVP是UDP,RTP/AVP/TCP是TCP
  • client_port:客户端接收RTP和RTCP的端口,一般是两个连续端口
  • Session:服务器分配的会话ID,后续所有请求都要带上

我的经验:如果摄像头在内网,用UDP传输效率更高。但如果跨公网,UDP容易被防火墙拦截,建议用TCP。我曾经在部署一个监控项目时,所有摄像头都在不同NAT后面,UDP死活连不上。后来全部改成RTP/AVP/TCP,问题就解决了。

4.4.4 PLAY

第四步,客户端说:「好了,开始推流吧」。

客户端 -> 服务器:
PLAY rtsp://192.168.1.100:554/stream1 RTSP/1.0
CSeq: 4
Session: 12345678
Range: npt=0.000-

服务器 -> 客户端:
RTSP/1.0 200 OK
CSeq: 4
Session: 12345678
RTP-Info: url=rtsp://192.168.1.100:554/stream1/track1;seq=0;rtptime=0

PLAY之后,服务器就开始往客户端推RTP包了。Range字段指定了播放起始时间,npt=0.000-表示从开头开始。如果要做seek操作,就改这个值。

RTP-Info里包含了初始序列号和RTP时间戳,客户端需要根据这些信息来同步音视频。我遇到过一个问题:有些服务器的RTP-Info里的seq和实际第一个RTP包的序列号不一致,导致客户端丢掉了前几个包。解决办法是不要完全依赖RTP-Info,以实际收到的RTP包为准

4.5 完整交互示例

把上面四步串起来,就是一个完整的RTSP交互流程:

1. OPTIONS -> 获取服务器支持的方法
2. DESCRIBE -> 获取SDP描述
3. SETUP (track1) -> 建立视频传输通道
4. SETUP (track2) -> 建立音频传输通道(如果有)
5. PLAY -> 开始推流
6. ... 接收RTP数据 ...
7. TEARDOWN -> 结束会话

重要提醒:TEARDOWN一定要发。有些嵌入式设备资源有限,如果不发TEARDOWN,会话会一直占用服务器资源。我见过一个项目,摄像头连续运行一周后死机,就是因为客户端异常断开时没发TEARDOWN,服务器端的会话越积越多,最终内存耗尽。

好了,RTSP的核心内容就这些。说白了就是「一问一答四步走」,每一步都有它的意义。你在实际开发中,只要按照这个流程来,基本不会出大问题。如果遇到奇怪的现象,先检查CSeq和Session ID,这两个是最容易出错的点。