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

实体就是报文里可选的那块消息体,RTSP 里几乎只有 SDP 会用到它。 Content-Length 数字节不数字符,还得把每行结尾的 CRLF 算进去。

📝 本节和前面几节的关系

上一节讲清楚了「控制谁」,这一节讲报文里「带什么货」。 它看着是个小概念,但 Content-Length 算错是最常见的对接故障——表现是「服务端一直等实体收全, 客户端以为发完了」。 下一节 01.04.03 讲的容器文件,就是实体里装的那个 SDP。

一、报文的三段结构

RTSP 报文就三段:起始行、头、实体。

| 段 | 例子 | 必须有吗 | |---|---|---|---| | 起始行 | DESCRIBE rtsp://… RTSP/1.0 | 必须有 | | 头 | CSeq: 2 / Accept: application/sdp | 几乎没有例外 | | 空行 | CRLF | 分隔头与实体 | | 实体 | v=0 / o=- … / m=video … | 只有 DESCRIBE 等少数情况 |

那个空行不能省:没有它,解析方不知道头在哪结束,会把实体第一行当成头继续读。 这是手写报文时第一个要检查的地方。

二、七个实体头

实体头只描述实体本身,不描述会话。

| 头 | 作用 | RTSP 里常见吗 | |---|---|---|---| | Content-Type | 实体是什么格式 | 常见,值多为 application/sdp | | Content-Length | 实体多少字节 | 常见,必填 | | Content-Base | 相对 URL 的基准 | 常见 | | Content-Location | 实体的真实地址 | 少见 | | Content-Encoding | 压过没有 | 极少 | | Content-Language | 什么语言 | 极少 | | Expires | 什么时候过期 | 极少 |

日常只用得上前三个。后面四个是从 HTTP 抄过来的,RTSP 场景里基本用不到。

三、Content-Length 的三个坑

坑一:算的是字节不是字符。ASCII 里两者一样,一旦有中文就差三倍。 坑二:忘了每行结尾的 CRLF。五行的 SDP,光换行就十个字节。 坑三:忘了最后一个空行。SDP 以空行结束,那两个字节也要算。

算一遍就清楚了:八行 SDP、全 ASCII,每行末尾 CRLF 两字节、末尾再一个 CRLF, 所以总长 = 所有行字符数之和 + (8+1)×2。

漏算那两个字节的后果:服务端一直等实体收全,客户端觉得发完了,两边僵在那里直到超时。 表现是「请求发出去了,没有任何响应,等三十秒才断」——看到这个现象,先去数 Content-Length。

四、实体和 SDP 的关系

RTSP 的实体里装的基本只有一种东西:SDP。

方法 实体里装什么 谁发
DESCRIBE 的响应 SDP 服务端
ANNOUNCE 的请求 SDP 客户端(推流)
SETUP/PLAY/TEARDOWN 通常没有 —
GET_PARAMETER 的响应 可能有参数文本 服务端

所以看抓包时有个快判法:有实体又有 Content-Type: application/sdp, 那就是 DESCRIBE 或 ANNOUNCE;没有实体的基本都是控制信令。

推流方向(ANNOUNCE)容易忘:它是客户端告诉服务端「我要推什么」,所以实体是客户端写的 SDP, Content-Length 也要客户端算准。

五、动手 Demo:实体就是消息体,长度得自己数准

这段造一份八行的 SDP,把字符数、CRLF 数、总字节数逐项量出来。 最后看「如果漏算末尾那个 CRLF,差多少」。

sdp = [u"v=0", u"o=- 0 0 IN IP4 192.168.1.64", u"s=IP Camera",
       u"t=0 0", u"a=control:*", u"m=video 0 RTP/AVP 96",
       u"a=rtpmap:96 H264/90000", u"a=control:trackID=0"]
body, nb = _crlf(sdp)      # 每行末尾加 CRLF,最后再加一个空行
print(len(sdp), len(body), nb)
# Content-Length 要填 nb,不是 len(body)

实跑输出:

--------------------------------------------------------------------
实体就是消息体,长度得自己数准
--------------------------------------------------------------------
**RTSP 报文分三块:起始行、头、实体。**实体是可选的消息体,
只有 DESCRIBE 的响应一定带——带的是 SDP。

  实体头               作用                常见值
  -----------------------------------------------------------------
  Content-Type         实体是什么格式      application/sdp
  Content-Length       实体多少字节        整数,按字节不按字符
  Content-Language     用什么语言          zh-CN,很少用
  Content-Encoding     压过没有            gzip,很少用
  Content-Base         相对 URL 的基准     rtsp://…/101/
  Content-Location     实体的真实地址      同 Content-Base
  Expires              什么时候过期        日期时间

**造一份最小的 SDP,实测它的字节数:**

  行数 8,字符数 136,**字节数 136**。
  差在哪儿:每行结尾的 CRLF 是 2 个字节,最后还有一个空行,所以 8 行要 18 个换行字节。

  Content-Length 就该填 136。
  **填成 134(漏了最后一个 CRLF)是常见的手写错误**,服务端会因为实体不完整一直等。

**中文的坑:**
  Content-Length 数的是**字节**。这条 SDP 全是 ASCII,字符数等于字节数;
  要是 `s=` 里写了中文,一个汉字 3 个字节,字符数就对不上了。

三个数分别是 8、实体字符数、字节数,中间那个已经含了 CRLF,所以和最后一个数在纯 ASCII 下相等。 差别只会在有中文时出现——那时字符数小于字节数,而 Content-Length 要填字节数。

「差两个字节」这件事值得单独记: SDP 规范要求以空行结束,那个空行就是最后两个字节; 漏了它,长度少 2,接收方会一直等; 多算了也不行——多 2 个字节,接收方会读越界,把后面的东西当实体。

最稳的写法是让代码算,别手填:把 SDP 拼成一个字符串,然后 len(s.encode("utf-8"))。 手填的 Content-Length 是这类对接故障的第一大来源,而且因为「有时对有时错」,特别难查。

30 秒实验

  1. 把你手上设备的 DESCRIBE 响应抓下来,数一下 SDP 有几行、Content-Length 是多少。
  2. 按「字符数 + (行数+1)×2」自己算一遍,看和抓到的数对不对得上。
  3. 把 SDP 里 s= 改成中文,重新算 Content-Length,看字符数和字节数差多少。

常见误区

  1. 以为 Content-Length 是字符数。 它是字节数。全 ASCII 时两者一样,一有中文就差三倍。
  2. 以为 SDP 最后那个空行不算长度。 算。它也是两个字节的 CRLF,漏了就会一直等。
  3. 以为 RTSP 报文都可以带实体。 只有 DESCRIBE/ANNOUNCE 这类才带,PLAY、 SETUP 带实体反而会被当成异常。
自测三问(点开对答案) 1. RTSP 报文的实体里装的最常见的是什么? —— SDP。DESCRIBE 的响应和 ANNOUNCE 的请求都带它,其余方法基本不带实体。 2. 八行全 ASCII 的 SDP,Content-Length 怎么算? —— 所有行字符数之和加上 (8+1)×2。多的那 1 是末尾必须有的空行。 3. Content-Length 少填两个字节,会看到什么现象? —— 服务端一直等实体收全,客户端以为发完了,两边僵住到超时。 表现为「请求发出去了但没有任何响应」。

下一节看实体里装的那个 SDP:一行 m= 就是一路媒体,这就是「容器文件」。

相关笔记