19、多人通话架构:Mesh、MCU、SFU对比、Mediasoup/SFU实战

多人通话,说白了就是让三个以上的人同时聊天。你想想看,两个人通话很简单,点对点直连就行。但人一多,问题就来了——每个人的音视频流要发给谁?服务器要不要参与?带宽扛不扛得住?

我最早做多人通话时,天真地以为把每个客户端的流都发给其他人就行了。结果呢?一个会议室8个人,每个人的上行带宽要乘以7,下行也要收7路流。嗯,效果可想而知。今天我们就来聊聊三种主流架构:Mesh、MCU、SFU,以及我实战中用得最多的Mediasoup。

一、三种架构的核心区别

先看一张图,帮你快速建立整体认知:

多人通话架构对比 Mesh 架构 每个客户端直连其他所有人 A B C D 连接数: N*(N-1)/2 MCU 架构 服务器混流,客户端只收一路 MCU A B C 服务器开销大,客户端最轻 SFU 架构 服务器转发,客户端选择性接收 SFU A B C 当前主流方案,平衡之道

二、Mesh 架构:简单但有限

Mesh 是最直观的方案。每个客户端都和其他所有人建立P2P连接。4个人开会,每个人要维护3个连接,总共6条连接。

优点很明显:

  • 不需要服务器转发,部署成本低
  • 延迟最低,因为数据不经过中转
  • 适合小规模会议(2-4人)

缺点也很致命:

  • 上行带宽 = 码率 × (人数-1),下行也一样
  • 客户端CPU/内存开销随人数线性增长
  • 超过6人基本就卡死了
我的经验: 曾经有个客户要求支持8人视频会议,我一开始用了Mesh。结果测试时,一个普通笔记本上行要推7路720p流,直接卡成PPT。后来果断换了SFU。

三、MCU 架构:服务器做混流

MCU(Multipoint Control Unit)的思路是:所有客户端把流发给服务器,服务器把多路视频合成一路,再下发给每个客户端。

这样做的好处是——客户端只需要收一路流,带宽压力极小。但代价呢?服务器要解码、合成、再编码,CPU开销巨大。我记得有个项目用MCU支持20人会议,一台高配服务器只能跑两个会议室。

对比项 Mesh MCU SFU
客户端上行带宽 高 (N-1倍码率) 低 (1倍码率) 低 (1倍码率)
客户端下行带宽 高 (N-1倍码率) 低 (1倍码率) 中 (选择性接收)
服务器开销 极高 (解码+编码) 中 (仅转发)
延迟 最低 高 (编解码耗时) 低 (仅转发)
灵活性 差 (布局固定) 好 (可选择性订阅)
典型场景 2-4人小会 大型直播/录制 6人以上互动会议

四、SFU 架构:当前的最佳实践

SFU(Selective Forwarding Unit)是目前最流行的方案。服务器只做转发,不解码也不编码。客户端把一路流推给SFU,SFU再分发给其他订阅者。

为什么说它是最佳实践?

  • 服务器压力小:只转发RTP包,不碰编解码
  • 客户端灵活:可以只订阅大屏的几路流,小屏的流不订阅
  • 支持Simulcast:客户端推多路不同分辨率的流,SFU根据订阅者需求转发合适的版本
  • 延迟低:只做包级别转发,微秒级延迟
核心要点: SFU 本质上是一个智能路由器。它知道每个订阅者需要什么质量的流,然后只转发对应的RTP包。这比MCU省了90%的服务器算力。

五、Mediasoup 实战:搭建一个SFU

Mediasoup 是我个人最推荐的SFU实现。它是C++写的底层,Node.js做控制层,性能极好。我在生产环境跑过,单台服务器支持200人同时在线,CPU占用不到30%。

下面是一个最简单的Mediasoup服务端示例:

// 1. 创建Worker(每个Worker一个CPU核心)
const worker = await mediasoup.createWorker({
  logLevel: 'warn',
  rtcMinPort: 40000,
  rtcMaxPort: 49999
});

// 2. 创建Router(一个Router对应一个房间)
const router = await worker.createRouter({
  mediaCodecs: [
    {
      kind: 'video',
      mimeType: 'video/VP8',
      clockRate: 90000,
      parameters: {}
    },
    {
      kind: 'audio',
      mimeType: 'audio/opus',
      clockRate: 48000,
      channels: 2
    }
  ]
});

// 3. 创建WebRTC传输(用于接收/发送媒体)
const transport = await router.createWebRtcTransport({
  listenIps: [{ ip: '0.0.0.0', announcedIp: '你的公网IP' }],
  enableUdp: true,
  enableTcp: true,
  preferUdp: true
});

// 4. 客户端推流时,创建Producer
const producer = await transport.produce({
  kind: 'video',
  rtpParameters: { /* 客户端协商的参数 */ }
});

// 5. 客户端拉流时,创建Consumer
const consumer = await transport.consume({
  producerId: producer.id,
  rtpCapabilities: router.rtpCapabilities
});
避坑指南: 我曾经在生产环境遇到一个问题——客户端频繁断连。排查了两天,发现是 listenIps 里的 announcedIp 写成了内网IP。客户端拿到的ICE候选地址是内网的,当然连不上。记住:一定要填公网IP或者域名。

六、Simulcast 与 SVC:SFU的进阶玩法

SFU 真正厉害的地方在于支持 Simulcast 和 SVC。这两个技术解决了多人场景下的带宽适配问题。

Simulcast( simulcast 编码):客户端同时编码多路不同分辨率的流(比如720p、360p、180p),全部推给SFU。SFU根据订阅者的网络状况,选择转发合适的流。

SVC(可伸缩视频编码):只编码一路流,但流本身包含多个质量层。SFU可以丢弃增强层,只转发基础层,实现动态降级。

我个人更倾向于 Simulcast,因为实现简单,兼容性好。SVC 虽然理论上更优雅,但编码器支持有限,而且码率节省并不明显。

// Mediasoup 中启用 Simulcast
const producer = await transport.produce({
  kind: 'video',
  rtpParameters: {
    encodings: [
      { maxBitrate: 2000000, scalabilityMode: 'L1T3' },  // 高清流
      { maxBitrate: 500000,  scalabilityMode: 'L1T3' },  // 中清流
      { maxBitrate: 100000,  scalabilityMode: 'L1T3' }   // 低清流
    ],
    codec: { mimeType: 'video/VP8', clockRate: 90000 }
  }
});

七、选型建议

说了这么多,到底怎么选?我根据实战经验给个建议:

  • 2-4人小会:Mesh 就够了,省服务器钱
  • 5-10人互动会议:SFU 是唯一选择,推荐 Mediasoup 或 Janus
  • 10人以上大型会议:SFU + Simulcast,配合带宽预估算法
  • 需要录制/直播:可以考虑 MCU 做混流输出,但互动部分还是用 SFU
一个小技巧: 如果你用 Mediasoup,建议把 Worker 数量设置为 CPU 核心数减1。留一个核心给 Node.js 事件循环用。我一开始设成等于核心数,结果 Node.js 主线程被阻塞,导致信令延迟飙升。

好了,关于多人通话架构就聊到这里。Mesh 简单但有限,MCU 强大但昂贵,SFU 才是当前最务实的方案。Mediasoup 作为 SFU 的代表实现,值得你花时间深入研究。下次遇到多人通话需求,你应该知道怎么选了。


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