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定义的,我习惯叫它「你出价我还价」模式。

流程很简单:

  1. A方生成Offer:把自己支持的编解码、IP、端口等信息写进SDP,发给B。
  2. B方收到Offer:看看A支持什么,再对照自己的能力,挑一个双方都能接受的方案。
  3. B方生成Answer:把选好的方案写进SDP,回复给A。
  4. 双方确认:协商完成,开始传数据。

我在做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.1
  • sprop-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 媒体协商的完整流程

我把整个流程画了张图,方便你理解:

WebRTC Offer/Answer 媒体协商流程 推流端(摄像头) 接收端(浏览器) 1. 生成Offer SDP 发送Offer 2. 解析Offer 3. 生成Answer SDP 返回Answer 4. 解析Answer 5. 开始推流 5. 接收并播放 RTP媒体流 说明: Offer/Answer模型是双向的。推流端生成Offer,接收端返回Answer。 协商完成后,双方通过ICE进行连通性检测,然后开始传输媒体数据。

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参数中