第1章:音视频合成基础
各位同学,欢迎来到《WebRTC录制与截图实战》的第一章。今天我们要聊的是音视频合成的基础知识。说实话,这部分内容看起来有点理论,但它是后面所有实战操作的根基。我自己当年刚接触WebRTC时,就是在这块吃了不少亏,所以今天我会把踩过的坑都给你们指出来。
1.1 使用Canvas合成视频帧
Canvas合成视频帧,说白了就是把视频画面一帧一帧地画到Canvas上。为什么要这么做?因为浏览器原生视频元素能做的事情有限,而Canvas给了你完全的控制权。
我习惯这样理解:<video>标签就像一台电视机,你只能看,不能改。而Canvas就像一块画板,你可以把电视画面截下来,然后在上面任意涂鸦、叠加文字、添加滤镜。
核心思路:通过 canvas.drawImage() 方法,将视频帧绘制到Canvas上,再通过 canvas.toBlob() 或 canvas.toDataURL() 导出为图片或视频帧。
来看一个最基础的例子:
// 获取视频元素和Canvas
const video = document.getElementById('myVideo');
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制视频帧到Canvas
function drawFrame() {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
requestAnimationFrame(drawFrame);
}
// 开始绘制
video.addEventListener('play', () => {
drawFrame();
});
嗯,这里要注意:drawImage 的调用时机很关键。如果视频还没加载完成就调用,画出来的是黑屏。我曾经在项目里犯过这个错,排查了半天才发现是视频 readyState 不够。
小技巧:建议在 video.oncanplay 或 video.onloadeddata 事件触发后再开始绘制,确保视频帧已经可用。
1.2 音频混音原理
音频混音,就是把多个音频信号叠加成一个。你想想看,直播的时候主播的声音和背景音乐要同时播放,这就是混音。
混音的核心原理其实很简单:加法运算。每个音频采样点就是一个数值,把多个音频的对应采样点相加,就得到了混合后的结果。
但问题来了——直接相加会导致溢出。比如两个音频的采样值都是0.8,相加就是1.6,超出了音频范围[-1, 1]。这时候声音就会「爆音」。
我常用的解决方案有两种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 归一化混音 | 相加后除以音轨数 | 音轨数量固定且较少 |
| 自适应混音 | 检测峰值,动态调整增益 | 音轨数量动态变化 |
归一化混音的代码实现:
function mixAudio(samples1, samples2) {
const mixed = new Float32Array(samples1.length);
for (let i = 0; i < samples1.length; i++) {
// 直接平均,防止溢出
mixed[i] = (samples1[i] + samples2[i]) / 2;
}
return mixed;
}
避坑指南:我曾经在做一个多人会议项目时,直接用了加法混音,结果6个人同时说话时声音完全炸了。后来改用自适应混音,根据当前音量峰值动态调整每个音轨的增益,才解决问题。
1.3 MediaStream合成技术
MediaStream合成,就是把多个音视频流合并成一个流。这在WebRTC中非常常见,比如屏幕共享时要把摄像头画面和屏幕画面合成一路流。
我个人觉得,理解MediaStream合成的关键是记住一句话:流就是管道,你可以把多个管道接到一个出口。
具体实现有两种方式:
- Canvas + MediaStream:通过
canvas.captureStream()将Canvas动画转为视频流 - MediaStream API:通过
MediaStream()构造函数手动组合音视频轨道
来看一个Canvas合成流的例子:
// 创建Canvas并绘制内容
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 将Canvas转为视频流
const canvasStream = canvas.captureStream(30); // 30fps
// 获取麦克风音频流
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 合成最终的MediaStream
const finalStream = new MediaStream([
...canvasStream.getVideoTracks(),
...audioStream.getAudioTracks()
]);
注意:captureStream() 的帧率参数会影响性能。30fps是平衡点,60fps会明显增加CPU负载。我在做移动端项目时,通常降到15fps。
1.4 Web Audio API音频处理
Web Audio API是浏览器提供的音频处理引擎。它比 <audio> 标签强大得多,可以实现混音、滤波、音量控制、音频分析等高级功能。
它的工作方式像搭积木:音频源 → 处理节点 → 输出目标。这个链条叫做「音频上下文图」。
// 创建音频上下文
const audioCtx = new AudioContext();
// 创建音频源(比如麦克风)
const source = audioCtx.createMediaStreamSource(stream);
// 创建增益节点(控制音量)
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0.5;
// 创建分析器节点(可视化用)
const analyser = audioCtx.createAnalyser();
// 连接节点:源 → 增益 → 分析器 → 输出
source.connect(gainNode);
gainNode.connect(analyser);
analyser.connect(audioCtx.destination);
为什么需要分析器节点?因为你可以用它获取音频的频域数据,做可视化效果。我在做音乐可视化项目时,就是靠 analyser.getByteFrequencyData() 拿到频率数据,然后绘制到Canvas上。
核心知识点:Web Audio API的节点是可以任意组合的。你可以串联、并联,甚至创建复杂的音频处理网络。这给了你无限的创作空间。
最后,我用一张图来总结本章的知识体系:
这张图把本章的四个核心模块串起来了。你可以看到,Canvas负责视频帧处理,Web Audio API负责音频处理,而MediaStream合成则是把两者合并的桥梁。音频混音原理则是理解Web Audio API的基础。
好了,第一章的内容就到这里。记住,这些基础概念会在后面的实战章节中反复用到。如果你现在觉得有些地方不太明白,没关系,先有个印象,后面用到的时候我会再详细展开。