3. 媒体协商与SDP:SDP协议详解、Offer/Answer模型、媒体描述与编解码参数
媒体协商,说白了就是两个端点坐下来谈:「你支持什么格式?我支持什么格式?咱俩找个都能接受的方案。」在WebRTC的世界里,这个谈判的「语言」就是SDP。
我刚开始接触WebRTC时,觉得SDP就是一堆看着头疼的文本。后来做RTSP推流项目,被一个SDP字段坑了整整两天,才老老实实把协议啃了一遍。嗯,今天咱们就把这块彻底讲透。
3.1 SDP协议到底是什么?
SDP全称Session Description Protocol,会话描述协议。注意它的名字——它只负责「描述」,不负责「传输」。你想想看,两个人要打电话,总得先知道对方用什么编解码器、IP地址是多少、端口开在哪儿。SDP就是干这个的。
一个典型的SDP长这样:
v=0
o=- 1234567890 1234567890 IN IP4 192.168.1.100
s=WebRTC Session
t=0 0
m=video 10000 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
c=IN IP4 192.168.1.100
每一行都是一个字段,格式固定:<类型>=<值>。类型是单个字符,比如v表示版本,o表示会话发起者,m表示媒体描述。
核心要点:SDP不是传输协议,它只是把媒体参数写成一份「简历」,让双方互相认识。真正的媒体数据走的是RTP/SRTP,跟SDP没关系。
3.2 Offer/Answer模型:谈判的艺术
WebRTC的媒体协商用的是Offer/Answer模型。这个模型是RFC 3264定义的,我习惯叫它「你出价我还价」模式。
流程很简单:
- A方生成Offer:把自己支持的编解码、IP、端口等信息写进SDP,发给B。
- B方收到Offer:看看A支持什么,再对照自己的能力,挑一个双方都能接受的方案。
- B方生成Answer:把选好的方案写进SDP,回复给A。
- 双方确认:协商完成,开始传数据。
我在做RTSP转WebRTC网关时,遇到过一个问题:摄像头只支持H.264 Baseline Profile,但浏览器默认Offer里写的是H.264 High Profile。结果Answer里直接拒绝了视频流。后来我手动改了Offer里的profile-level-id参数,才搞定。
避坑指南:我曾经在Offer里写了三个视频编解码器,以为对方会选第一个。结果对方选了第二个,而第二个的payload type我根本没实现。记住:Offer里写的每一个选项,你都得能处理。
3.3 SDP媒体描述详解
SDP里最重要的就是媒体描述行,以m=开头。格式如下:
m=<媒体类型> <端口> <传输协议> <格式列表>
举个例子:
m=video 10000 RTP/AVP 96 97 98
这行表示:视频媒体,端口10000,使用RTP/AVP协议,支持payload type 96、97、98。
常用的媒体类型:
| 类型 | 说明 |
|---|---|
audio |
音频流 |
video |
视频流 |
application |
数据通道(Data Channel) |
传输协议这块,WebRTC里最常见的是:
RTP/AVP:普通RTP,用于非加密场景RTP/SAVP:加密RTP(SRTP),WebRTC默认用这个UDP/TLS/RTP/SAVP:基于TLS的SRTP,更安全
3.4 编解码参数:细节决定成败
光说「我支持H.264」是不够的。H.264有几十种参数组合,你不说清楚,对方可能用错配置。这时候就需要a=rtpmap和a=fmtp属性行。
3.4.1 rtpmap:编解码映射
a=rtpmap:96 H264/90000
这行把payload type 96映射到H.264编码,时钟频率90000Hz。视频的时钟频率通常是90000,音频则根据编码不同而不同,比如PCMU是8000。
3.4.2 fmtp:编解码具体参数
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
这里才是真正的坑。拿H.264举例:
packetization-mode:0表示单NALU模式,1表示非交错模式(WebRTC常用),2表示交错模式profile-level-id:编码档次和级别,42e01f表示Baseline Profile 3.1sprop-parameter-sets:序列参数集和图像参数集,解码器需要这个才能解码
我记得有一次做RTSP推流,摄像头输出的H.264流里sprop-parameter-sets是空的。浏览器收到后直接报错,说无法解码。后来我在转SDP时手动把SPS/PPS塞进了这个字段,问题才解决。
注意:不同浏览器对H.264参数的支持不一样。Chrome要求profile-level-id必须匹配实际码流,否则会拒绝解码。Safari则宽松一些。我建议你在Offer里只写实际能支持的档次,别贪多。
3.5 SDP中的其他关键属性
除了媒体描述和编解码参数,还有几个属性在RTSP推流场景里特别重要:
| 属性 | 示例 | 作用 |
|---|---|---|
c= |
c=IN IP4 192.168.1.100 |
连接信息,指定媒体数据的IP地址 |
a=sendrecv |
a=sendrecv |
媒体方向:可收可发 |
a=sendonly |
a=sendonly |
只发不收(推流场景常用) |
a=recvonly |
a=recvonly |
只收不发(拉流场景常用) |
a=ice-ufrag |
a=ice-ufrag:abc123 |
ICE用户名片段 |
a=ice-pwd |
a=ice-pwd:xyz789 |
ICE密码 |
a=fingerprint |
a=fingerprint:sha-256 ... |
DTLS指纹,用于加密认证 |
在RTSP推流场景里,我们通常把媒体方向设为sendonly,因为摄像头只负责推,不负责收。但如果你做的是双向对讲,那就得用sendrecv。
3.6 媒体协商的完整流程
我把整个流程画了张图,方便你理解:
3.7 RTSP推流场景下的SDP特殊处理
RTSP本身也有自己的SDP协商机制,但跟WebRTC不完全一样。做RTSP转WebRTC网关时,你需要做一次「SDP翻译」。
我总结了几点关键差异:
- 传输协议不同:RTSP通常用RTP/AVP,WebRTC用RTP/SAVP。你需要把
RTP/AVP改成RTP/SAVP,或者用UDP/TLS/RTP/SAVP。 - ICE信息缺失:RTSP的SDP里没有ICE字段,你需要自己生成
ice-ufrag和ice-pwd。 - DTLS指纹:WebRTC要求DTLS加密,RTSP通常不加密。你需要在SDP里加上
fingerprint属性。 - 媒体方向:RTSP推流通常是
sendonly,但WebRTC的Offer默认是sendrecv。记得改成sendonly,否则浏览器会傻等着接收数据。
个人经验:我写过一个SDP转换器,核心逻辑就三步:1)解析RTSP的SDP;2)补充ICE和DTLS信息;3)修改传输协议和媒体方向。代码量不大,但每一步都有坑。特别是profile-level-id,RTSP摄像头经常写错,你得自己校正。
3.8 常见问题与排查思路
做媒体协商时,我遇到过最多的问题就是「协商失败」。这里列几个常见原因:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Answer里没有视频 | Offer里的编解码参数不匹配 | 检查profile-level-id和packetization-mode |
| ICE连接失败 | SDP里缺少ICE字段 | 确认ice-ufrag和ice-pwd是否存在 |
| DTLS握手失败 | 指纹不匹配或证书问题 | 检查fingerprint是否与证书一致 |
| 解码失败 | sprop-parameter-sets缺失 |
手动添加SPS/PPS到fmtp参数中 |