第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要分清 |
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,这两个是最容易出错的点。