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为例,大致流程是:

  1. 注册扩展类型:RtpHeaderExtensionUri::kExampleCustom
  2. 实现解析函数:从RTP头扩展中提取数据
  3. 在SDP协商中声明支持的扩展

小提示:自定义头扩展虽然灵活,但不要滥用。每增加一个头扩展,RTP包就会变大,网络开销也会增加。我一般建议,一个RTP会话中头扩展的数量不要超过5个。

Extmap协商的完整流程

为了让你更直观地理解,我画了一张流程图,展示Extmap在SDP协商中的完整过程:

Extmap媒体协商流程图 发送端(Offer) 接收端(Answer) 1. 声明支持的Extmap SDP Offer 2. 匹配支持的Extmap SDP Answer 3. 建立ID映射关系 协商成功 4. 发送RTP包(带Extmap) 4. 解析RTP头扩展 RTP数据流 注:ID映射必须在SDP协商阶段完成,否则RTP解析会出错

避坑指南:我曾经踩过的坑

做WebRTC这么多年,Extmap相关的坑我踩了不少。这里分享几个典型的:

  • ID冲突:两个不同的URI映射到了同一个ID。我曾经在合并代码时没注意,结果一个ID同时代表abs-send-timetoffset,接收端解析时直接崩溃。
  • URI拼写错误:自定义URI的路径写错了,比如少了一个斜杠。这种错误很难排查,因为SDP协商不会报错,只是接收端解析不到数据。
  • 大小写敏感:URI是大小写敏感的。我记得有一次,同事把urn:ietf写成了URN:IETF,结果协商失败,查了半天才发现。

警告:千万不要在生产环境中使用未注册的URI作为标准扩展。如果你需要自定义扩展,请确保通信双方都理解并支持这个URI。否则,轻则功能异常,重则整个会话崩溃。

总结

Extmap虽然只是SDP协商中的一小部分,但它直接影响RTP数据的解析质量。记住三个关键点:

  • URI注册:尽量使用IANA官方注册的URI,自定义URI要确保双方一致
  • 一对一映射:每个URI对应唯一的本地ID,不能乱
  • 自定义扩展:灵活但谨慎,不要滥用

好了,这一章就到这里。下一章我们会聊聊更具体的SDP协商细节,到时候再深入展开。


公众号:蓝海资料掘金营,微信deep3321