SDP 是国标里唯一「人能一眼读懂」的东西:一行一个字段,全部是 类型=值 的纯文本。
它只有两百来字节,却决定了「用什么编码、往哪个地址发」——写错一个字段,整路黑屏。
上一节讲 PS 封装,是「媒体长什么样」。这一节讲「媒体往哪走」,也就是 SDP。 它是被 SIP 报文捎带的,不单独走线。
一、一行一个字段的纯文本
SDP 的语法简单到不像协议:每行开头一个字母加等号,后面跟值;行与行之间用换行分开。
必填的有五个:v= 写版本(固定 0)、o= 写会话归属者、s= 写会话名、
t= 写起止时间(固定 0 0)、m= 写媒体类型和端口。
可选的有三个最常出现:c= 写连接地址、a= 写属性、y= 写 SSRC。
m= 那一行信息量最大:m=video 6000 RTP/AVP 96 拆开就是「媒体类型是视频、端口 6000、
传输协议 RTP/AVP、负载类型 96」。
96 这个数字要特别注意:
它是「动态负载类型」,意思是「具体是什么编码,去看 a=rtpmap 那行」。
0 到 95 是固定含义,96 以上由会话自己定义。
国标在这里规定死了一件事:a=rtpmap:96 PS/90000,
也就是「96 号负载是 PS 封装、时钟频率 90000」。
二、国标扩展的三个字段
标准 SDP 里没有 y=,这是国标加的:
它装的是 SSRC,一个 32 位整数,写成十位十进制、不足补零。
为什么需要它:
同一台设备可能同时给多个平台发流,接收方要靠 SSRC 把「这一包属于哪一路」分开。
没有 y=,多平台并发时就会串流。
第二个扩展是 f=:它留给厂商塞私有信息,标准里没有定义内容。
多数设备直接写成空值,看到 f= 后面没东西是正常的。
第三个是 s= 的取值:实时点播写 Play,历史回放写 Playback,文件下载写 Download。
这个字段看着无关紧要,其实是「这次请求要干什么」的开关。
审核 SDP 时按这个顺序看:
先看 s= 对不对,再看 m= 的端口,然后看 c= 的地址,最后看 y= 和 rtpmap。
前两个错了整路不通,后两个错了能通但会乱。
三、两份 SDP 怎么对上
点播时 SDP 出现两次,方向相反:
平台在 INVITE 里写「往我这发」,设备在 200 OK 里写「我从这发」。
所以平台那份的 c= 是平台地址、a=recvonly;设备那份的 c= 是设备地址、a=sendonly。
这一对关系是理解国标点播的钥匙:收流端口是平台挑的,发流端口是设备挑的,
两边各说各的,拼起来才是一条完整的路径。
最容易搞错的是 o= 那行:它是「会话归属者」,记录谁发起的会话,不是媒体地址。
改 o= 不影响流往哪走,改 c= 才影响。
还有一种情况会让人懵:有些平台在做级联时会把 c= 改成自己的地址,让流先到平台再转出去。
这时抓包会看到「设备发的流打到了平台」,这是设计如此,不是配错。
四、动手 Demo:一份国标 SDP 逐行拆开看
造两份 SDP:一份是平台在 INVITE 里写的,一份是设备在 200 OK 里回的。 量字节、拆字段、算 SSRC,看两边到底差在哪。
inv = ["v=0", "o=34020000002000000001 0 0 IN IP4 10.1.1.10",
"s=Play", "c=IN IP4 10.1.1.10", "t=0 0",
"m=video 30000 RTP/AVP 96", "a=recvonly",
"a=rtpmap:96 PS/90000", "y=0200000001"]
ok = [ ... 同样的九行,但 c= 是 192.168.1.64、
m= 是 6000、a= 是 sendonly、y= 是 0100000001 ]
for name, body in (("INVITE", inv), ("200 OK", ok)):
print(name, len(body), sum(len(x) + 2 for x in body))
print(int("0100000001") < 2 ** 32)
print(90000 * 40 // 1000) # 一帧 40 毫秒的时钟计数
实跑输出:
--------------------------------------------------------------------
一份国标 SDP 逐行拆开看
--------------------------------------------------------------------
**点播时两边都要交底**:**平台先说「往我这发」,设备再答「我从这发」。**
**两份 SDP 长得几乎一样,只有地址和端口是反的。**
**这正是不容易看懂的地方。**
位置 行数 字节
---------------------------------------------------------
**INVITE 里(平台写)** **9 行** **159 字节**
**200 OK 里(设备写)** **9 行** **164 字节**
**两份都不到两百字节,而且差别只在三个字段**:**归属者编号、`c=` 的地址、`m=` 的端口。**
**`c=` 和 `m=` 是最容易搞反的一对**:
字段 INVITE 里 200 OK 里 含义
-----------------------------------------------------------------
`c=` 10.1.1.10 192.168.1.64 **媒体走哪个 IP**
`m=` 30000 6000 **媒体走哪个端口**
`a=` recvonly sendonly **这一端是收还是发**
`y=` 0200000001 0100000001 **这一端的 SSRC**
**看第三行就知道谁收谁发**:**平台写 `recvonly`,设备写 `sendonly`,两边一拼就对上了。**
**唯独 `o=` 那行容易误读**——**它是「会话归属者」,不是「媒体地址」**,
**改 `o=` 不影响流往哪走,改 `c=` 才影响。**
**`y=` 那 10 位数字装的是 32 位 SSRC**:
`0200000001` → 200000001,占 32 位空间的 4.6566%,补零 1 位
`0100000001` → 100000001,占 32 位空间的 2.3283%,补零 1 位
**注意左边那个补上的 0**:
**它不是符号位也不是标志位,只是补位**——
**SSRC 是 32 位整数,写成十进制最多十位,不足前面补零凑够十位。**
**所以解析时先 `int()` 再比,别按字符一位位读。**
**`a=rtpmap:96 PS/90000` 里的 90000 是时钟频率**:
**一秒钟走九万个计数,一帧 40 毫秒就是 3600 个。**
**这个数决定 RTP 时间戳怎么涨**,
**写错了不会立刻报错,只会让播放器算错帧间隔**——**表现是画面「快放」或「慢放」,而不是花屏。**
**最后算一下它有多不起眼**:
一份 SDP 约 210 字节,一分钟的 4 Mbps 流约 3.0 MB
占比 0.00700000%
**小到可以忽略,但少一个字段就一路黑屏**——**这就是信令的脾气:字节上不重要,语义上要命。**
两份都是九行、一百六十字节上下,差别只有归属者编号、c=、m=、a=、y= 五处。
y= 那两行值得看第二眼:0200000001 换成整数是两亿,只占 32 位空间的 4.7%。
最前面那个 0 不是标志位,纯粹是为了凑够十位补上去的。
所以解析 y= 时直接 int(),别按字符一位位读——
按位读会把补位的 0 当成 SSRC 的一部分,算出来小十倍。
90000 * 40 // 1000 = 3600 这个数说明一件事:90 千赫兹的时钟走 40 毫秒,时间戳涨 3600。
时钟频率写错,播放器就会按错误的间隔播,表现是快放或慢放。
最后那个占比是 0.007%:两百多字节对三兆的媒体,小到可以忽略。
但它是「有没有」的问题,不是「多不多」的问题。
30 秒实验
- 算一下:
y=0100000001转成整数是多少?(100000001) - 想一下:为什么收流端口由平台定、发流端口由设备定?
- 查一下:
m=video 6000 RTP/AVP 96里 96 代表什么编码?(看a=rtpmap)
常见误区
- 以为「SDP 是一份独立的协议报文」。 它是被 SIP 的正文捎带的,本身不单独发。
- 以为「
o=里那个 IP 就是媒体地址」。 媒体地址看c=,o=只记会话归属者。 - 以为「
y=前面的 0 是某种标志」。 它就是补位,凑够十位而已。
自测三问(点开对答案)
1. 国标为什么要加 `y=` 这个字段? —— **为了让接收方能区分 SSRC**。**一台设备同时给多个平台发流时,靠它分开不同的路。** 2. `s=` 有哪三种取值? —— **`Play` 实时点播、`Playback` 历史回放、`Download` 文件下载**。 3. 时钟频率写错会怎样? —— **不会报错,只会让播放器算错帧间隔**,**表现是画面快放或慢放。**下一节看「转换」:非国标的设备要接进来,中间那个网关到底在干什么。
- ☐ (本节完,下一节 10.05.05 网络传输协议的转换)