一、Group属性到底是什么?

Group属性,说白了就是告诉浏览器:「这几个媒体流,你们得绑在一起处理」。我刚开始接触WebRTC时,看到SDP里一堆a=group行,说实话有点懵。后来做项目多了,才真正理解它的重要性。

在WebRTC的SDP协商中,group属性主要干两件事:

  • BUNDLE分组:把多个媒体流合并到同一个传输通道
  • FID/FEC分组:把原始流和冗余流关联起来

嗯,这里要注意,group属性的格式其实很简单:

a=group:BUNDLE audio video
a=group:FID 1 2
a=group:FEC 1 3

第一行表示audio和video要捆绑传输,第二行表示媒体流1和它的重传流2是一对,第三行表示媒体流1和它的FEC流3是一对。

二、BUNDLE分组:省带宽的利器

我个人习惯把BUNDLE叫做「多流合一」。你想想看,如果没有BUNDLE,每个媒体流都要单独开一个UDP端口,那ICE连接数就爆炸了。

BUNDLE的核心思想:

  • 所有媒体流共享同一个ICE候选对
  • 所有媒体流共享同一个DTLS连接
  • 所有媒体流共享同一个SRTP上下文

我在项目中遇到过一个问题:有个客户反馈视频通话经常卡顿,排查了半天发现是BUNDLE没协商成功,导致每个流都走了独立的UDP端口,NAT穿透失败率飙升。

BUNDLE协商的关键点:

  • 发起方在SDP中声明a=group:BUNDLE,列出所有要捆绑的媒体
  • 应答方必须按相同顺序回应,否则协商失败
  • 每个媒体行都要有a=mid标识,group里引用的就是mid

三、FID分组:重传流的绑定

FID,全称是Flow Identification。它解决的是「丢包重传」的问题。当网络丢包时,接收方会请求重传,但重传的数据包和原始数据包怎么区分?

答案就是FID分组:

m=video 9 UDP/TLS/RTP/SAVPF 100 101
a=mid:1
a=rtpmap:100 VP8/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100

m=video 0 UDP/TLS/RTP/SAVPF 101
a=mid:2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100

a=group:FID 1 2

这里mid:1是原始VP8流,mid:2是重传流。FID分组告诉接收方:「这两个流是一家的,重传数据请认准这个组合」。

避坑指南:我曾经在调试时发现重传一直不生效,查了半天原来是FID分组里的mid顺序写反了。记住,第一个是原始流,第二个是重传流,顺序不能乱。

四、FEC分组:前向纠错的SDP协商

FEC(前向纠错)和重传不同,它不需要等待丢包反馈,直接发送冗余数据。接收方即使丢了一些包,也能通过冗余数据恢复出来。

FEC分组的SDP长这样:

m=video 9 UDP/TLS/RTP/SAVPF 100 102
a=mid:1
a=rtpmap:100 VP8/90000
a=rtpmap:102 ulpfec/90000

m=video 0 UDP/TLS/RTP/SAVPF 102
a=mid:3
a=rtpmap:102 ulpfec/90000

a=group:FEC 1 3

这里mid:1是原始VP8流,mid:3是FEC流。FEC分组告诉接收方:「这两个流是配套的,FEC数据用来修复原始流」。

FEC的两种模式

模式 说明 适用场景
ULP FEC 基于异或运算,冗余率可配置 丢包率<10%的场景
Flex FEC 更灵活的FEC方案,支持多种编码 高丢包率场景

我个人建议,如果丢包率超过15%,单纯靠FEC就不太够了,带宽浪费太严重。这时候应该FEC和NACK(重传)配合使用。

五、Group属性的完整协商流程

让我用一张图来展示整个协商流程:

Group属性协商流程 发起方 应答方 1. 生成Offer SDP 包含a=group:BUNDLE audio video 2. 发送Offer 通过信令通道传输 3. 解析Offer 检查group分组是否支持 4. 生成Answer SDP 保持相同的group分组顺序 5. 协商完成,开始媒体传输

这个流程看起来简单,但实际项目中坑不少。我记得有一次调试,发起方SDP里写了a=group:BUNDLE audio video,但应答方返回的group顺序是video audio,结果Chrome直接报错,协商失败。

重要提醒:BUNDLE分组中的媒体顺序必须严格一致。发起方怎么写,应答方就怎么回。这是很多新手容易踩的坑。

六、FID和FEC的共存策略

在实际项目中,FID和FEC经常同时使用。这时候group属性会变成这样:

a=group:BUNDLE audio video
a=group:FID 1 2
a=group:FEC 1 3
a=group:FID 4 5
a=group:FEC 4 6

这里mid:1是视频原始流,mid:2是视频重传流,mid:3是视频FEC流。mid:4是音频原始流,mid:5是音频重传流,mid:6是音频FEC流。

我个人习惯的做法是:

  • 视频流:FID + FEC 同时开启,FEC冗余率设10%
  • 音频流:只开FID,不开FEC(音频包小,重传成本低)

为什么这么配?因为视频数据量大,FEC的冗余开销相对可以接受。音频包本来就小,重传一次也就几十字节,没必要浪费FEC带宽。

七、总结

Group属性是WebRTC媒体协商中容易被忽视但极其重要的部分。它决定了媒体流如何组织、如何关联、如何容错。

三个核心点:

  1. BUNDLE:多流合一,节省连接资源
  2. FID:原始流和重传流的绑定
  3. FEC:原始流和前向纠错流的绑定

嗯,这些内容看起来简单,但实际调试时每个细节都可能让你头疼。建议大家在写SDP解析代码时,一定要严格校验group的格式和顺序,否则线上出问题排查起来很痛苦。

一句话总结:Group属性就是WebRTC的「组织架构图」,它告诉系统谁和谁是一组的,谁负责干活,谁负责兜底。

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