2. 音频基础概念

音频源节点,说白了就是声音的「起点」。在 Web Audio API 里,所有声音都得从一个源节点出发,经过各种效果器,最后到达扬声器。我刚开始接触时,总觉得它们差不多——不都是出声嘛?后来踩过几次坑才明白,每种源节点的性格完全不同,选错了,后面全白搭。

今天咱们就把四种核心源节点掰开揉碎,看看它们各自擅长什么,又有什么坑等着你。

OscillatorNode:最纯粹的波形发生器

OscillatorNode 能生成最基础的声音波形。正弦波、方波、锯齿波、三角波,就这四种。它不依赖任何外部音频文件,纯靠数学计算出声。

适用场景:合成器音色、报警提示音、音频测试信号、电子音乐中的基础音色层。

// 创建一个振荡器,播放 440Hz 的正弦波
const audioCtx = new AudioContext();
const osc = audioCtx.createOscillator();

osc.type = 'sine';       // 波形类型:sine | square | sawtooth | triangle
osc.frequency.value = 440; // A4 音高
osc.connect(audioCtx.destination);
osc.start();             // 开始发声
// osc.stop(audioCtx.currentTime + 2); // 2秒后停止

我个人习惯用 OscillatorNode 做「声音原型验证」。比如要测试一个滤波器效果,直接挂个正弦波上去,比找音频文件快得多。但要注意,振荡器一旦 start() 就不能再 start() 第二次——想重新播放?得新建一个实例。

注意:OscillatorNode 是一次性节点。start() 和 stop() 只能各调用一次。需要循环播放时,每次都得重新 createOscillator()。

AudioBufferSourceNode:内存里的音频播放器

这个节点负责播放已经加载到内存中的音频数据。你可以从服务器下载一个音频文件,解码成 AudioBuffer,然后塞给这个节点播放。

它支持 loop 循环、播放偏移、变速变调。我在项目中经常用它来做「音效池」——把短音效预加载到内存里,需要时瞬间触发,几乎没有延迟。

// 假设已经通过 fetch 拿到了音频数据
async function playBuffer(arrayBuffer) {
  const audioCtx = new AudioContext();
  const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

  const source = audioCtx.createBufferSource();
  source.buffer = audioBuffer;
  source.loop = false;       // 是否循环
  source.playbackRate.value = 1.0; // 播放速度,1.0 为正常
  source.connect(audioCtx.destination);
  source.start();
}

经验之谈:我曾经做过一个游戏音效系统,把所有短音效(< 3秒)预解码成 AudioBuffer 存起来。触发时直接 createBufferSource + start(),延迟在 1ms 以内。但千万别拿它播长音频——内存会炸。

AudioBufferSourceNode 和 OscillatorNode 一样,也是一次性节点。start() 之后就不能再用了。想重播?再 new 一个。

MediaElementAudioSourceNode:把 <audio> 拉进音频图

这个节点的作用很简单:把 HTML 里的 <audio> 元素接入 Web Audio 的音频处理管线。它不负责加载音频,只负责「桥接」。

你想想看,<audio> 本身就能播放,为什么还要绕一道?因为 <audio> 的音频流是「直通」扬声器的,你没法在中间插效果器。而 MediaElementAudioSourceNode 让你能对 <audio> 的声音做实时处理——加混响、做均衡、变声,都行。

const audioCtx = new AudioContext();
const audioEl = document.getElementById('myAudio'); // <audio> 元素

const source = audioCtx.createMediaElementSource(audioEl);
// 现在可以接效果器了
const gainNode = audioCtx.createGain();
source.connect(gainNode);
gainNode.connect(audioCtx.destination);

// 播放控制仍然用 audioEl.play()
audioEl.play();

坑:一个 <audio> 元素只能被 createMediaElementSource() 调用一次。如果你已经把它接入了音频图,再调一次会报错。我刚开始不知道,调试了半天……

这个节点适合播放大文件(比如播客、音乐),因为音频数据由浏览器流式加载,不占内存。但它有个限制:你没法精确控制「从第几毫秒开始播」——那是 AudioBufferSourceNode 的活。

MediaStreamAudioSourceNode:麦克风进来的原始声音

这个节点从 MediaStream 获取音频——最常见的就是麦克风输入。它把实时音频流送进音频图,你可以做实时分析、录音、降噪、变声。

async function startMic() {
  const audioCtx = new AudioContext();
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

  const source = audioCtx.createMediaStreamSource(stream);
  source.connect(audioCtx.destination); // 直接听到麦克风声音(小心啸叫)
}

嗯,这里要注意:直接 connect(destination) 会把麦克风声音实时回放,容易产生啸叫。实际项目中通常接一个分析器做可视化,或者接录音节点做录制,很少直接放出来。

避坑指南:我曾经做过一个语音聊天室,直接用 MediaStreamAudioSourceNode 处理麦克风输入。结果发现不同浏览器对采样率的处理不一样——Chrome 默认 48kHz,Firefox 可能是 44.1kHz。跨浏览器测试时差点翻车。

四种源节点对比:选对工具,事半功倍

源节点 数据来源 是否一次性 适合场景 典型延迟
OscillatorNode 数学计算 是 合成音色、测试信号、报警音 < 1ms
AudioBufferSourceNode 内存中的 AudioBuffer 是 短音效、采样播放、精确控制 < 1ms
MediaElementAudioSourceNode <audio> 元素 否(可复用) 长音频播放、流式加载、实时效果 取决于网络
MediaStreamAudioSourceNode 麦克风/系统音频流 否(持续流) 实时语音、录音、音频分析 10-50ms

说白了,选哪个源节点,取决于你的声音从哪来、要播多久、需不需要精确控制。短促的音效用 AudioBufferSourceNode,长音频用 MediaElementAudioSourceNode,合成音色用 OscillatorNode,实时语音用 MediaStreamAudioSourceNode——各司其职。

一张图理清四种源节点

下面这张 SVG 图展示了四种源节点在 Web Audio 音频图中的位置和流向。你可以看到,它们都汇聚到音频处理管线,但入口完全不同。

音频源节点结构图 四种音频源节点在音频图中的位置 OscillatorNode 数学计算生成波形 AudioBufferSourceNode 内存中的音频数据 MediaElementAudioSourceNode <audio> 元素桥接 MediaStreamAudioSourceNode 麦克风/系统音频流 效果器链 Gain / Filter / Reverb ... AudioDestination 扬声器输出 💡 关键理解 四种源节点都输出 AudioNode 信号, 后续可接相同的效果器链。 区别在于:声音从哪来?能不能复用? 源节点层 → 处理层 → 输出层

从图上可以清楚看到:四种源节点虽然入口不同,但最终都汇入同一个效果器处理管线。你可以在效果器链里统一加 GainNode 控制音量、加 BiquadFilterNode 做均衡——不管声音从哪来,处理方式是一样的。

选型决策:一句话总结

  • 要合成声音? 用 OscillatorNode。别拿它播音频文件。
  • 要播短音效? 用 AudioBufferSourceNode。预加载,瞬间触发。
  • 要播长音频? 用 MediaElementAudioSourceNode。省内存,流式加载。
  • 要接麦克风? 用 MediaStreamAudioSourceNode。实时流,别忘处理啸叫。

嗯,四种源节点就聊到这儿。每种都有它的脾气,摸透了,后面做音频处理就顺手多了。