19、多人通话架构:Mesh、MCU、SFU对比、Mediasoup/SFU实战
多人通话,说白了就是让三个以上的人同时聊天。你想想看,两个人通话很简单,点对点直连就行。但人一多,问题就来了——每个人的音视频流要发给谁?服务器要不要参与?带宽扛不扛得住?
我最早做多人通话时,天真地以为把每个客户端的流都发给其他人就行了。结果呢?一个会议室8个人,每个人的上行带宽要乘以7,下行也要收7路流。嗯,效果可想而知。今天我们就来聊聊三种主流架构:Mesh、MCU、SFU,以及我实战中用得最多的Mediasoup。
一、三种架构的核心区别
先看一张图,帮你快速建立整体认知:
二、Mesh 架构:简单但有限
Mesh 是最直观的方案。每个客户端都和其他所有人建立P2P连接。4个人开会,每个人要维护3个连接,总共6条连接。
优点很明显:
- 不需要服务器转发,部署成本低
- 延迟最低,因为数据不经过中转
- 适合小规模会议(2-4人)
缺点也很致命:
- 上行带宽 = 码率 × (人数-1),下行也一样
- 客户端CPU/内存开销随人数线性增长
- 超过6人基本就卡死了
三、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根据订阅者需求转发合适的版本
- 延迟低:只做包级别转发,微秒级延迟
五、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
好了,关于多人通话架构就聊到这里。Mesh 简单但有限,MCU 强大但昂贵,SFU 才是当前最务实的方案。Mediasoup 作为 SFU 的代表实现,值得你花时间深入研究。下次遇到多人通话需求,你应该知道怎么选了。
公众号:蓝海资料掘金营,微信deep3321