多人录制方案:同时录制多个视频流
说实话,多人录制这个需求,我最早是在做在线教育平台时遇到的。一个老师加三个学生,四路视频要同时录下来。当时我想,这不就是每个 PeerConnection 各开一个 MediaRecorder 嘛?结果一跑起来,时间轴全对不上,画面和声音错位得离谱。嗯,今天就把我踩过的坑和最终方案,一次性说清楚。
核心挑战:不是录制,是同步
同时录多路视频,技术上并不难。每个 RTCPeerConnection 收到远程流后,各自 new 一个 MediaRecorder 就行。但问题出在——每个录制器的起始时间不一样。
为什么会这样?因为每个 MediaRecorder 的 start() 调用有微小延迟,浏览器内部的时间戳基准也不同。你想想看,四个录制器各自为政,最后生成的四个 WebM 文件,播放时根本对不上嘴型。
核心原则:多人录制不是「各自录完再拼」,而是「用一个时钟指挥所有录制器」。
我的方案:统一时间轴 + 主控录制器
我在项目中最终采用的做法是:
- 选一个「主控」——通常是发起端(老师端)的 MediaRecorder。
- 所有录制器共享同一个起始时间——用
performance.now()或Date.now()打一个全局时间戳。 - 每个录制器的
start()都传入这个时间戳,而不是各自 new 一个时间。
代码大概长这样:
// 主控端发起同步信号
const syncTime = performance.now();
// 每个远程流到达时,都用同一个 syncTime 启动录制
remoteStreams.forEach((stream, index) => {
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9'
});
// 关键:传入统一时间戳
recorder.start(1000); // 1秒切片
recorder.syncTimestamp = syncTime; // 自定义属性,后续使用
recorders.push(recorder);
});
小技巧:我习惯在 ondataavailable 回调里,把 syncTimestamp 写入每个 chunk 的 metadata 中。这样后期合并时,可以根据这个时间戳对齐各段视频。
同步多个录制器:时间同步问题处理
你以为传同一个时间戳就完事了?太天真了。浏览器实际执行 start() 时,会有几毫秒到几十毫秒的偏差。我遇到过最夸张的一次,Chrome 和 Firefox 之间差了 200ms。
怎么解决?我用了两招:
- 第一招:NTP 时间同步——如果各端在不同机器上,通过信令服务器交换 NTP 时间戳。WebRTC 的 RTCP 报告里其实就带 NTP 时间,可以直接拿来用。
- 第二招:录制前先录一段「同步信号」——比如让所有参与者同时拍一下手,或者显示一个倒计时动画。后期合并时,以这个事件为对齐点。
注意:千万不要依赖 new Date().getTime() 做跨设备同步。各设备的系统时间可能差好几秒。我吃过这个亏,录出来的视频完全没法用。
合并多人视频:用 FFmpeg 还是浏览器端?
合并方案我试过两种,各有优劣:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 服务端 FFmpeg 合并 | 功能强大,支持各种布局 | 需要上传原始文件,带宽成本高 |
| 浏览器端 Canvas 合并 | 实时性高,无需上传 | 性能瓶颈,最多支持 4-6 路 |
我个人更推荐混合方案:浏览器端先做初步合并(比如把 4 路小画面拼成一个大画面),然后只上传合并后的单路视频。这样既节省带宽,又保留了同步精度。
浏览器端合并的核心代码:
// 创建一个离屏 Canvas
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
// 每帧绘制所有视频流
function drawFrame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 假设我们有 4 个视频元素,排成 2x2 网格
const streams = [video1, video2, video3, video4];
streams.forEach((video, index) => {
const x = (index % 2) * 640;
const y = Math.floor(index / 2) * 360;
ctx.drawImage(video, x, y, 640, 360);
});
requestAnimationFrame(drawFrame);
}
// 用 MediaRecorder 录制 Canvas 流
const combinedStream = canvas.captureStream(30);
const finalRecorder = new MediaRecorder(combinedStream, {
mimeType: 'video/webm;codecs=vp9'
});
避坑指南:我曾经用 canvas.captureStream(60) 试图录 60fps,结果移动端直接卡死。后来发现 30fps 足够清晰,而且兼容性好得多。嗯,别贪心。
时间同步问题处理的终极方案
如果你问我,最稳妥的时间同步方法是什么?我会说:在录制过程中持续校准。
具体做法是:
- 每个录制器每 5 秒向主控发送一次「当前时间 + 录制进度」。
- 主控收到后,计算偏差,然后通过 DataChannel 广播一个「时间校正值」。
- 各录制器根据校正值调整自己的时间戳。
这样即使一开始有偏差,也能在录制过程中逐渐拉齐。我在一个 8 人会议的项目中实测过,最终偏差控制在 16ms 以内(一帧的误差)。
总结一下:多人录制不是技术难题,而是工程细节。统一时间轴、持续校准、合理合并,这三步走下来,基本不会出大问题。
知识体系总览
下面这张图,是我自己梳理的多人录制核心逻辑。你看一眼,心里就有谱了:
这张图把整个流程串起来了:多路流进来,先统一时间轴,然后各自录制,最后合并输出。中间别忘了持续校准——这是最容易忽略但最关键的一步。
公众号:蓝海资料掘金营,微信deep3321