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 的核心架构:

Web Audio API 核心架构图 Web Audio API 核心架构 音频源节点 OscillatorNode BufferSourceNode MediaStreamSourceNode 处理节点 GainNode BiquadFilterNode ConvolverNode 目标节点 AudioDestinationNode 数据流方向:源节点 → 处理节点 → 目标节点 所有节点必须通过 connect() 方法串联,形成有向无环图

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 的生命周期分为三个阶段:

  1. 创建阶段:new AudioContext(),分配系统资源。
  2. 使用阶段:创建节点、连接、播放、处理。
  3. 销毁阶段:调用 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)。