2、RTP会话建立:SDP协议简介、Offer/Answer模型、媒体协商流程
聊完RTP的包头和基本传输模型,咱们得面对一个实际问题:两个端点怎么知道对方支持什么编码、用什么端口、发什么格式的数据?
说白了,RTP只管把媒体数据打包扔出去。但在这之前,双方得先“握手”——告诉对方自己能干什么、想干什么。这个握手的过程,就是SDP协议配合Offer/Answer模型完成的。
我刚开始接触RTP开发时,总觉得SDP就是个文本描述文件,没什么技术含量。直到有一次在项目中排查兼容性问题,发现对方把a=fmtp参数写错了,导致视频花屏。嗯,从那以后我再也不敢小看这几行文本了。
2.1 SDP协议:不只是个文本描述
SDP的全称是Session Description Protocol,会话描述协议。它不负责传输数据,只负责描述媒体会话的“长相”。
你想想看,两个设备要通话,至少得知道:
- 用什么编码?H.264还是VP8?
- 码率多少?分辨率多大?
- RTP往哪个IP和端口发?
- 音频和视频是分开还是混在一起?
SDP就是干这个的。它用一组键值对来描述这些信息,每一行以单个字母开头,后面跟着等号和具体值。
SDP的核心行类型
| 行标识 | 含义 | 示例 |
|---|---|---|
v= |
协议版本 | v=0 |
o= |
会话发起者信息 | o=- 123456 2 IN IP4 192.168.1.1 |
s= |
会话名称 | s=RTP Session |
c= |
连接信息 | c=IN IP4 0.0.0.0 |
t= |
会话有效时间 | t=0 0 |
m= |
媒体描述 | m=video 49170 RTP/AVP 96 |
a= |
属性行(扩展描述) | a=rtpmap:96 H264/90000 |
我个人习惯把SDP分成两部分看:会话级和媒体级。会话级描述全局信息(版本、发起者、连接地址),媒体级描述每个媒体流的具体参数。一个SDP里可以有多个m=行,分别对应音频、视频、字幕等。
小技巧:调试时如果看到c=IN IP4 0.0.0.0,别慌。这通常表示地址还没确定,等ICE协商完成后会更新。我在项目中遇到过好几次,一开始以为是Bug,后来发现是标准做法。
2.2 Offer/Answer模型:你一句我一句
SDP只是描述格式,那谁来发起、谁来响应?这就是Offer/Answer模型要解决的问题。
这个模型很简单:
- Offer方(通常是呼叫方)生成一个SDP,描述自己支持的媒体能力。
- Answer方(被叫方)收到后,根据自己的能力生成一个SDP回复。
- 双方各自解析对方的SDP,确定最终的媒体参数。
说白了,就是“我告诉你我能做什么,你告诉我你能做什么,然后我们取交集”。
举个例子:
// Offer(呼叫方)
v=0
o=- 123456 2 IN IP4 192.168.1.10
s=-
c=IN IP4 192.168.1.10
t=0 0
m=audio 5004 RTP/AVP 0 8 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
m=video 5006 RTP/AVP 96 97
a=rtpmap:96 H264/90000
a=rtpmap:97 VP8/90000
// Answer(被叫方)
v=0
o=- 654321 1 IN IP4 192.168.1.20
s=-
c=IN IP4 192.168.1.20
t=0 0
m=audio 6004 RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
m=video 6006 RTP/AVP 96
a=rtpmap:96 H264/90000
你看,Answer方只选择了自己支持的编码:音频选了PCMU(payload type 0),视频选了H.264(payload type 96)。VP8被跳过了,因为Answer方不支持。
注意:Offer/Answer模型要求Answer方不能随意添加Offer方没提到的编码。我曾经见过一个实现,Answer方自作主张加了个m=application行,结果Offer方直接报错。记住,Answer只能“选”,不能“加”。
2.3 媒体协商流程:从SDP到RTP流
光有Offer/Answer还不够,实际项目中媒体协商是一个多步骤的流程。我画了一张图,帮你理清整个过程:
这个流程看起来简单,但实际项目中坑不少。我挑几个常见的说说:
2.4 避坑指南:媒体协商中的常见问题
我曾经在做一个跨平台项目时,发现Android端和iOS端视频通话一直黑屏。排查了半天,问题出在a=fmtp参数上。Android端在SDP里写了a=fmtp:96 packetization-mode=1;profile-level-id=42e01f,但iOS端解析时把profile-level-id当成了可选参数,直接忽略了。结果H.264的profile不匹配,解码失败。
从那以后,我要求团队在SDP解析时,对a=fmtp做严格校验,不匹配就降级或报错。
另一个常见问题是Payload Type冲突。RTP标准里,0-95是静态分配的类型(比如0=PCMU,8=PCMA),96-127是动态类型。但不同厂商对动态类型的理解不一样。我见过一个设备把H.264映射到96,另一个设备把VP8映射到96,结果两边都以为对方说的是自己的编码。
建议:在实现Offer/Answer时,尽量使用a=rtpmap明确声明编码名称和时钟频率。不要依赖静态Payload Type的默认映射。这样即使对方用了不同的动态类型,也能通过编码名称匹配上。
2.5 媒体协商与编解码协同
回到课程主题——流控与编解码协同。媒体协商阶段其实已经埋下了协同的种子。
举个例子,Offer方在SDP里声明了a=fmtp:96 max-br=2000,意思是“我最高支持2Mbps的码率”。Answer方如果网络条件差,可以在Answer里回复a=fmtp:96 max-br=500,把码率限制到500Kbps。这样在RTP流启动前,双方就已经对码率上限达成了共识。
我个人习惯在SDP里额外携带以下编解码参数:
| 参数 | 含义 | 示例 |
|---|---|---|
max-br |
最大码率(kbps) | a=fmtp:96 max-br=2000 |
max-fs |
最大帧尺寸(宏块数) | a=fmtp:96 max-fs=8160 |
max-fps |
最大帧率 | a=fmtp:96 max-fps=30 |
packetization-mode |
H.264分包模式 | a=fmtp:96 packetization-mode=1 |
这些参数在协商阶段定下来后,后续的流控算法就有了边界。比如码率不能超过max-br,分辨率不能超过max-fs对应的尺寸。说白了,SDP就是给流控画了个“天花板”。
总结一下:SDP + Offer/Answer模型是RTP会话建立的基石。它不复杂,但细节多。你只要记住三点:
- SDP描述“我能做什么”
- Offer/Answer模型完成“我们做什么”
- 媒体协商的结果直接影响后续的流控和编解码行为
下一章我们会深入RTP的传输控制,看看在流启动后,如何动态调整码率和编码参数。嗯,到时候你会回来感谢SDP里那些参数的。