第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.oncanplayvideo.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视频帧合成 音频混音原理 MediaStream合成 Web Audio API drawImage() 绘制帧 toBlob() / toDataURL() 导出 requestAnimationFrame 循环 采样点加法运算 归一化 vs 自适应混音 防溢出处理 captureStream() 转流 MediaStream() 组合轨道 音视频轨道合并 音频上下文图 节点连接:源→处理→输出 分析器与可视化 最终目标:合成高质量的音视频流,用于录制或实时传输

这张图把本章的四个核心模块串起来了。你可以看到,Canvas负责视频帧处理,Web Audio API负责音频处理,而MediaStream合成则是把两者合并的桥梁。音频混音原理则是理解Web Audio API的基础。

好了,第一章的内容就到这里。记住,这些基础概念会在后面的实战章节中反复用到。如果你现在觉得有些地方不太明白,没关系,先有个印象,后面用到的时候我会再详细展开。

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