1. 音频基础与 Web Audio API 概览
各位同学,欢迎来到《前端音频流式处理实战》的第一章。在开始动手写代码之前,我想先聊聊音频的底层逻辑。你可能会问:我一个前端工程师,为什么要懂物理和数字音频?
说实话,我早年踩过不少坑。有一次做实时语音特效,采样率没对齐,结果声音像开了慢速磁带一样。排查了半天,才发现是基础概念没吃透。所以这一章,咱们把地基打牢。
1.1 声音的物理特性
声音的本质是什么?说白了,就是空气的振动。这种振动通过介质(空气、水、固体)传播到我们耳朵里,就形成了听觉。
描述一个声音,有三个核心物理量:
- 频率(Frequency):每秒振动的次数,单位是赫兹(Hz)。频率越高,音调越高。人耳能听到的范围大约是 20Hz - 20kHz。
- 振幅(Amplitude):振动的幅度,决定了声音的响度。振幅越大,声音越响。
- 波形(Waveform):振动的形状。不同的波形决定了音色。比如正弦波是纯净的单音,方波、锯齿波则带有丰富的泛音。
关键点:频率决定音高,振幅决定音量,波形决定音色。这三个参数,是你在 Web Audio API 中操作声音的起点。
我记得刚学音频时,总把「频率」和「采样率」搞混。频率是声音本身的属性,采样率是数字化的参数,别弄反了。
1.2 数字音频基础
计算机听不懂连续的声波,它只认 0 和 1。所以我们需要把模拟信号转成数字信号。这个过程叫「模数转换」,涉及三个关键参数。
| 参数 | 定义 | 常见值 | 影响 |
|---|---|---|---|
| 采样率 | 每秒采集声音样本的次数 | 44100 Hz(CD 音质)、48000 Hz(视频) | 决定最高可还原频率(奈奎斯特定律:采样率的一半) |
| 位深度 | 每个样本用多少比特存储 | 16 bit、24 bit、32 bit float | 决定动态范围和信噪比 |
| 声道 | 声音的通道数 | 1(单声道)、2(立体声)、5.1(环绕声) | 决定空间感和沉浸感 |
我的习惯:做 WebRTC 实时通信时,我一般用 48000 Hz、16 bit、单声道。既能保证语音清晰,又节省带宽。如果是音乐处理,我会用 44100 Hz、32 bit float,保留更多细节。
你想想看,采样率 44100 Hz 意味着每秒要采集 44100 个点。如果位深度是 16 bit,那每秒的数据量就是 44100 × 16 = 705600 bit,约 88 KB/s。这就是为什么音频文件需要压缩。
1.3 Web Audio API 整体架构
Web Audio API 是浏览器提供的一套音频处理引擎。它不是简单的播放器,而是一个模块化的音频路由系统。
我个人喜欢把它想象成「音频乐高」:
- AudioContext:整个音频世界的容器。所有节点都挂在这个上下文里。
- 音频源节点:声音从哪里来?可以是麦克风(MediaStreamSource)、音频文件(BufferSource)、振荡器(OscillatorNode)。
- 处理节点:声音怎么变?增益(GainNode)、滤波器(BiquadFilterNode)、卷积(ConvolverNode)等。
- 目标节点:声音去哪里?通常是扬声器(AudioDestinationNode)。
这些节点通过 connect() 方法串联起来,形成一个音频处理图。数据从源节点流入,经过一系列处理节点,最终到达目标节点。
注意:Web Audio API 的节点图是有向无环图。你不能把节点连成环,否则会引发音频反馈啸叫。我曾经在调试时不小心连成了环,耳机里直接爆音,吓得我赶紧拔了耳机。
下面这张图展示了 Web Audio API 的核心架构:
1.4 AudioContext 的创建与生命周期
AudioContext 是 Web Audio API 的入口。没有它,一切音频操作都无法进行。
创建 AudioContext 非常简单:
const audioCtx = new AudioContext();
但这里有几个坑,我一个个说。
1.4.1 创建时机
浏览器为了用户体验,不允许页面一加载就自动创建 AudioContext。必须由用户交互触发(比如点击按钮)。
最佳实践:在用户第一次点击时创建 AudioContext,然后复用。不要每次播放都 new 一个新的。
1.4.2 状态管理
AudioContext 有三种状态:
- suspended:挂起状态。刚创建时默认是这个状态(部分浏览器)。
- running:运行状态。音频可以正常播放和处理。
- closed:关闭状态。上下文被销毁,无法再使用。
你可以通过 audioCtx.state 查看当前状态,用 audioCtx.resume() 恢复,用 audioCtx.suspend() 暂停。
// 恢复音频上下文
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
1.4.3 生命周期管理
AudioContext 的生命周期分为三个阶段:
- 创建阶段:new AudioContext(),分配系统资源。
- 使用阶段:创建节点、连接、播放、处理。
- 销毁阶段:调用 audioCtx.close(),释放资源。
我曾经踩过的坑:在单页应用中,切换页面时忘记关闭旧的 AudioContext,导致内存泄漏。浏览器标签页的音频资源是有限的,不用的上下文一定要及时 close()。
// 正确销毁
function cleanupAudio() {
if (audioCtx && audioCtx.state !== 'closed') {
audioCtx.close();
}
}
1.4.4 采样率与延迟
创建 AudioContext 时,可以指定采样率:
const audioCtx = new AudioContext({ sampleRate: 48000 });
但要注意,不是所有采样率都支持。浏览器会使用最接近的可用采样率。你可以通过 audioCtx.sampleRate 查看实际值。
另外,AudioContext 还有一个 baseLatency 属性,表示音频处理的最小延迟。这个值通常在几毫秒到几十毫秒之间,取决于浏览器和硬件。
总结:AudioContext 是整个音频应用的「心脏」。创建要晚(用户交互后),销毁要早(不用就关),状态要查(suspended/running/closed)。