音频源节点详解:四种源头,各有脾气
音频源节点,说白了就是声音的「起点」。在 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 音频图中的位置和流向。你可以看到,它们都汇聚到音频处理管线,但入口完全不同。
从图上可以清楚看到:四种源节点虽然入口不同,但最终都汇入同一个效果器处理管线。你可以在效果器链里统一加 GainNode 控制音量、加 BiquadFilterNode 做均衡——不管声音从哪来,处理方式是一样的。
选型决策:一句话总结
- 要合成声音? 用 OscillatorNode。别拿它播音频文件。
- 要播短音效? 用 AudioBufferSourceNode。预加载,瞬间触发。
- 要播长音频? 用 MediaElementAudioSourceNode。省内存,流式加载。
- 要接麦克风? 用 MediaStreamAudioSourceNode。实时流,别忘处理啸叫。
嗯,四种源节点就聊到这儿。每种都有它的脾气,摸透了,后面做音频处理就顺手多了。