跳到主要内容

L2-多人会议

引入 SFU 架构,支持 16 人房间。本项目以 mediasoup 为例(方案二也给出 LiveKit 对照)。

1. 学习目标

  • 理解 SFU 的转发模型与订阅关系。
  • 配置并启用 Simulcast,让 SFU 按需转发。
  • 实现订阅策略:聚焦说话者 + 缩略图层。
  • 接入 TWCC,让 SFU 做带宽估计。
  • 上报 QoE 指标。

2. 架构

3. 目录结构

L2-多人会议/
├── client/
│ ├── src/
│ │ ├── room.js # 房间管理 / 订阅策略
│ │ ├── transport.js # mediasoup-client Transport 封装
│ │ ├── producer.js # 上行(含 simulcast 配置)
│ │ ├── consumer.js # 下行(带层切换)
│ │ └── ui/
│ └── package.json # mediasoup-client
├── server/
│ ├── index.js # mediasoup Worker + 信令
│ ├── room.js # mediasoup Router / Transport / Producer / Consumer
│ ├── speaker-detector.js # 主讲人检测
│ └── package.json # mediasoup
└── infra/
└── docker-compose.yml

4. 上行 Simulcast 三档

const producer = await sendTransport.produce({
track: videoTrack,
encodings: [
{ rid: 'q', maxBitrate: 150_000, scaleResolutionDownBy: 4 }, // 320×180
{ rid: 'h', maxBitrate: 500_000, scaleResolutionDownBy: 2 }, // 640×360
{ rid: 'f', maxBitrate: 1_500_000 }, // 1280×720
],
codecOptions: { videoGoogleStartBitrate: 1000 },
});

5. 订阅策略

房间策略:
- 主讲人(最近 1.5s 音量最大者):订阅 'f' 高分辨率
- 共享屏幕者:恒定 'f'
- 其他可见缩略图:订阅 'q' 低分辨率
- 不可见的(滚动到屏幕外):暂停 consumer
// 切层
await sender.setPreferredLayers({ spatialLayer: 2, temporalLayer: 2 });

// 暂停下行(节省下行带宽)
await consumer.pause();
await consumer.resume();

6. 主讲人检测

服务端订阅每路音频的 audioLevel RTP 扩展头,做 EMA 平滑:

// server/speaker-detector.js(节选)
import { observer } from 'mediasoup';

router.on('audiolevel', ({producerId, volume}) => {
// volume 为 -127..0 dBov
emaMap.set(producerId, ema(emaMap.get(producerId) ?? -127, volume, 0.3));
const top = [...emaMap.entries()].sort((a,b) => b[1]-a[1])[0];
if (top && top[0] !== currentSpeaker) {
currentSpeaker = top[0];
broadcast({type: 'speaker-change', producerId: top[0]});
}
});

7. 信令协议扩展

L1 的信令协议加入:

type ClientMsg =
| { type: 'create-transport'; direction: 'send'|'recv' }
| { type: 'connect-transport'; transportId: string; dtlsParameters: DtlsParameters }
| { type: 'produce'; transportId: string; kind: 'audio'|'video'; rtpParameters: RtpParameters }
| { type: 'consume'; producerId: string; rtpCapabilities: RtpCapabilities }
| { type: 'pause-consumer'; consumerId: string }
| { type: 'resume-consumer'; consumerId: string }
| { type: 'set-preferred-layers'; consumerId: string; spatialLayer: number; temporalLayer: number };

8. 方案二:LiveKit(更工程化)

如果不想自己写 mediasoup 封装,直接用 LiveKit:

import { Room } from 'livekit-client';

const room = new Room({
adaptiveStream: true, // 自动按可见性暂停 consumer
dynacast: true, // 自动按需选层
});

await room.connect(LIVEKIT_URL, accessToken);
await room.localParticipant.enableCameraAndMicrophone();

LiveKit 把 Simulcast、订阅、说话人检测、QoE 全部内置好,90% 业务首选

9. 验收 Checklist

  • 8 人房间稳定运行 30 分钟
  • 单参与者上行带宽 ≤ 2.5 Mbps
  • 主讲人切换在 1.5s 内
  • 滚动到屏幕外的视频自动暂停
  • 弱网用户(800 kbps)自动降到 'h' 或 'q'
  • SFU 单实例 CPU ≤ 70% @ 100 路转发

10. 参考资料