16、媒体协商中的Extmap:Extmap URI注册、一对一映射关系、自定义头扩展
各位同学,今天我们来聊一个在WebRTC媒体协商中容易被忽略,但实际项目中却经常踩坑的话题——Extmap。
说实话,我刚开始做WebRTC的时候,对Extmap的理解也就停留在“哦,这是RTP头扩展”的层面。直到有一次,我在做视频会议项目时,发现两个端协商出来的视频流总是花屏,查了整整两天,最后发现是Extmap的映射关系搞错了。嗯,从那以后,我再也不敢小看这个小小的头扩展了。
什么是Extmap?
Extmap,全称是RTP Header Extension,也就是RTP头扩展。你想想看,标准的RTP头只有12个字节,能携带的信息非常有限。但在实际应用中,我们往往需要传递一些额外的信息,比如时间戳、帧标记、传输序列号等等。这些信息放哪儿呢?RTP头扩展就是干这个用的。
在SDP媒体协商中,Extmap通过a=extmap属性来描述。它的基本格式是这样的:
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
这里的1是本地ID,urn:ietf:params:rtp-hdrext:ssrc-audio-level是URI,用来唯一标识这个头扩展的类型。
Extmap URI注册:谁说了算?
你可能要问:这些URI是随便写的吗?当然不是。IANA(互联网号码分配局)维护了一个官方的RTP头扩展注册表。常见的URI包括:
| URI | 含义 | 常见用途 |
|---|---|---|
| urn:ietf:params:rtp-hdrext:ssrc-audio-level | 音频音量级别 | 音频流音量指示 |
| urn:ietf:params:rtp-hdrext:toffset | 时间偏移 | 音视频同步 |
| urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id | RTP流ID | Simulcast流标识 |
| urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id | 修复流ID | 修复后的流标识 |
| http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time | 绝对发送时间 | 带宽估计 |
我个人习惯在项目中尽量使用IANA注册的URI,因为兼容性最好。但有时候,业务需求比较特殊,比如我们需要传递自定义的元数据,那就得用自定义URI了。
一对一映射关系:为什么必须是1:1?
这是Extmap最核心的规则——在同一个RTP会话中,每个URI必须映射到唯一的本地ID。也就是说,你不能把同一个URI映射到两个不同的ID上,也不能把两个不同的URI映射到同一个ID上。
为什么会这样?因为接收端收到RTP包后,是根据头扩展的ID来解析数据的。如果映射关系混乱,接收端就不知道这个ID到底对应什么数据,解析出来的结果自然就是错的。
我在项目中遇到过这样一个坑:两个端协商时,发送端把abs-send-time映射到了ID 3,接收端却以为ID 3是toffset。结果就是带宽估计完全乱套,视频质量忽高忽低。后来我加了一段日志,才发现是映射关系没对齐。
核心要点:Extmap的映射关系必须在SDP协商中达成一致。发送端和接收端对同一个ID的理解必须完全相同。
自定义头扩展:什么时候用?怎么用?
官方注册的URI虽然够用,但总有一些场景是标准覆盖不到的。比如,我在做直播推流时,需要在RTP头里携带一个自定义的帧序号,用来做丢包检测。这时候就得自己定义URI了。
自定义URI的格式一般有两种:
- URN格式:
urn:example:my-custom-extension - HTTP格式:
http://example.com/my-custom-extension
我个人更推荐用HTTP格式,因为可以附带文档链接,方便团队其他人理解。当然,URN格式更简洁,看个人喜好。
下面是一个自定义头扩展的SDP示例:
a=extmap:6 http://example.com/frame-seq-number
在代码层面,你需要实现自定义的RTP头扩展解析逻辑。以libwebrtc为例,大致流程是:
- 注册扩展类型:
RtpHeaderExtensionUri::kExampleCustom - 实现解析函数:从RTP头扩展中提取数据
- 在SDP协商中声明支持的扩展
小提示:自定义头扩展虽然灵活,但不要滥用。每增加一个头扩展,RTP包就会变大,网络开销也会增加。我一般建议,一个RTP会话中头扩展的数量不要超过5个。
Extmap协商的完整流程
为了让你更直观地理解,我画了一张流程图,展示Extmap在SDP协商中的完整过程:
避坑指南:我曾经踩过的坑
做WebRTC这么多年,Extmap相关的坑我踩了不少。这里分享几个典型的:
- ID冲突:两个不同的URI映射到了同一个ID。我曾经在合并代码时没注意,结果一个ID同时代表
abs-send-time和toffset,接收端解析时直接崩溃。 - URI拼写错误:自定义URI的路径写错了,比如少了一个斜杠。这种错误很难排查,因为SDP协商不会报错,只是接收端解析不到数据。
- 大小写敏感:URI是大小写敏感的。我记得有一次,同事把
urn:ietf写成了URN:IETF,结果协商失败,查了半天才发现。
警告:千万不要在生产环境中使用未注册的URI作为标准扩展。如果你需要自定义扩展,请确保通信双方都理解并支持这个URI。否则,轻则功能异常,重则整个会话崩溃。
总结
Extmap虽然只是SDP协商中的一小部分,但它直接影响RTP数据的解析质量。记住三个关键点:
- URI注册:尽量使用IANA官方注册的URI,自定义URI要确保双方一致
- 一对一映射:每个URI对应唯一的本地ID,不能乱
- 自定义扩展:灵活但谨慎,不要滥用
好了,这一章就到这里。下一章我们会聊聊更具体的SDP协商细节,到时候再深入展开。