Web 音频 API 背后的基本概念

本文解释了 Web 音频 API 功能背后的部分音频理论,以帮助你在设计应用程序音频路由时做出明智的决策。如果你还不是一名音频工程师,它将为你提供足够的背景知识,让你了解 Web 音频 API 的工作原理。

音频图

Web 音频 API 涉及在 音频上下文 中处理音频操作,并且旨在允许模块化路由。每个 音频节点 执行基本的音频操作,并与一个或多个其他音频节点链接以形成 音频路由图。支持多种具有不同通道布局的源,即使在单个上下文中也是如此。这种模块化设计提供了创建具有动态效果的复杂音频功能的灵活性。

音频节点通过其输入和输出链接,形成一条链,从一个或多个源开始,经过一个或多个节点,然后最终到达目的地(尽管如果你只想可视化一些音频数据,则不必提供目的地)。Web 音频的简单典型工作流程如下所示

  1. 创建音频上下文。
  2. 在上下文中创建音频源(例如 <audio>、振荡器或流)。
  3. 创建音频效果(例如混响、双二次滤波器、声场定位器或压缩器节点)。
  4. 选择音频的最终目的地(例如用户的电脑扬声器)。
  5. 将源节点连接到零个或多个效果节点,然后连接到所选目的地。

注意:通道表示法 是一个数值,例如2.05.1,表示信号上可用的音频通道数。第一个数字是信号包含的全频段音频通道数。句点后出现的数字表示为低频效果 (LFE) 输出保留的通道数;这些通常称为低音炮

A simple box diagram with an outer box labeled Audio context and three inner boxes labeled Sources, Effects, and Destination. The three inner boxes have arrows between them pointing from left to right, indicating the flow of audio information.

每个输入或输出都由一个或多个音频通道组成,这些通道共同表示特定的音频布局。支持任何离散的通道结构,包括单声道立体声四声道5.1等等。

Show the ability of audio nodes to connect via their inputs and outputs and the channels inside these inputs/outputs.

您可以通过多种方式获取音频

  • 声音可以通过音频节点(例如振荡器)在 JavaScript 中直接生成。
  • 它可以从原始 PCM 数据(例如 .WAV 文件或 decodeAudioData() 支持的其他格式)创建。
  • 它可以从 HTML 媒体元素(例如 <video><audio>)生成。
  • 它可以从 WebRTC MediaStream(例如网络摄像头或麦克风)获取。

音频数据:样本中的内容

处理音频信号时,会发生采样。采样是将 连续信号 转换为 离散信号。换句话说,连续的声波(例如现场演奏的乐队)被转换为一系列数字样本(离散时间信号),允许计算机以不同的块处理音频。

您可以在维基百科页面 采样(信号处理) 上找到更多信息。

音频缓冲区:帧、样本和通道

AudioBuffer 使用三个参数定义

  • 通道数(单声道为 1,立体声为 2 等),
  • 其长度,即缓冲区中样本帧数,
  • 以及采样率,每秒播放的样本帧数。

样本是一个 32 位浮点数,表示特定通道(如果为立体声,则为左右)中音频流在每个特定时间点的值。样本帧是在特定时间点播放的所有通道的所有值的集合:同时播放的所有通道的所有样本(立体声为两个,5.1 为六个等)。

采样率是这些样本(或帧,因为所有样本都同时播放)在一秒钟内播放的数量,以赫兹 (Hz) 为单位。采样率越高,音质越好。

让我们看一下一个单声道和一个立体声音频缓冲区,每个缓冲区时长 1 秒,速率为 44100Hz

  • 单声道缓冲区将有 44,100 个样本和 44,100 个帧。length 属性将为 44,100。
  • 立体声缓冲区将有 88,200 个样本,但仍然有 44,100 个帧。length 属性仍然为 44100,因为它等于帧数。

A diagram showing several frames in an audio buffer in a long line, each one containing two samples, as the buffer has two channels, it is stereo.

当缓冲区播放时,你将首先听到最左边的样本帧,然后是紧挨着它的一个,然后是下一个,依此类推,直到缓冲区的末尾。在立体声的情况下,你将同时听到两个通道。样本帧非常方便,因为它们独立于通道数,并以理想的方式表示时间,以便进行精确的音频操作。

注意:要从帧数获取以秒为单位的时间,请将帧数除以采样率。要从样本数获取帧数,只需将后一个值除以通道数。

以下是一些简单的示例

js
const context = new AudioContext();
const buffer = new AudioBuffer(context, {
  numberOfChannels: 2,
  length: 22050,
  sampleRate: 44100,
});

注意:数字音频 中,44,100 Hz(或者表示为44.1 kHz)是一个常见的 采样频率。为什么是 44.1 kHz?

首先,因为人类耳朵的 听力范围 大致在 20 Hz 到 20,000 Hz 之间。通过 奈奎斯特-香农采样定理,采样频率必须大于希望再现的最大频率的两倍。因此,采样率必须大于40,000 Hz。

其次,信号必须在采样前进行 低通滤波,否则会发生 混叠。虽然理想的低通滤波器会在完美地通过低于 20 kHz 的频率(不会衰减它们)并且完美地截止高于 20 kHz 的频率,但在实践中,需要一个 过渡带,其中频率部分被衰减。此过渡带越宽,制造 抗混叠滤波器 就越容易且越经济。44.1 kHz 的采样频率允许 2.05 kHz 的过渡带。

如果你使用上面的调用,你将得到一个具有两个通道的立体声缓冲区,当在以 44100 Hz 运行的 AudioContext 上回放时(非常常见,大多数正常的声卡都以这种速率运行),将持续 0.5 秒:22,050 帧 / 44,100 Hz = 0.5 秒。

js
const context = new AudioContext();
const buffer = new AudioBuffer(context, {
  numberOfChannels: 1,
  length: 22050,
  sampleRate: 22050,
});

如果你使用此调用,你将获得一个单声道缓冲区(单通道缓冲区),当在以 44,100 Hz 运行的 AudioContext 上回放时,将自动重新采样到 44,100 Hz(因此产生 44,100 帧),并持续 1.0 秒:44,100 帧 / 44,100 Hz = 1 秒。

注意:音频重新采样非常类似于图像大小调整。假设你有一个 16 x 16 的图像,但希望它填充 32 x 32 的区域。你调整(或重新采样)它。结果质量较低(它可能变得模糊或边缘化,具体取决于大小调整算法),但它可以工作,调整大小后的图像占用更少的空间。重新采样的音频也是一样:你节省了空间,但在实践中,你无法正确再现高频内容或高音。

平面缓冲区与交错缓冲区

Web 音频 API 使用平面缓冲区格式。左右声道存储如下

LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (for a buffer of 16 frames)

这种结构在音频处理中很常见,使得可以轻松地独立处理每个声道。

另一种方法是使用交错缓冲区格式。

LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (for a buffer of 16 frames)

这种格式普遍用于存储和回放音频,而无需进行大量处理,例如:.WAV 文件或解码后的 MP3 流。

由于 Web Audio API 旨在用于处理音频,因此它公开平面缓冲区。它使用平面格式,但在将音频发送到声卡进行回放时将其转换为交错格式。反之,当 API 解码 MP3 时,它从交错格式开始,并将其转换为平面格式以进行处理。

音频通道

每个音频缓冲区可能包含不同数量的声道。大多数现代音频设备使用基本的单声道(仅一个声道)和立体声(左右声道)设置。一些更复杂的设备支持环绕声设置(如四声道5.1 声道),由于其较高的声道数,可以带来更丰富的聆听体验。我们通常使用下表中详细说明的标准缩写来表示声道

名称 声道
单声道 0: M: 单声道
立体声 0: L: 左声道 1: R: 右声道
四声道 0: L: 左声道 1: R: 右声道 2: SL: 左环绕声道 3: SR: 右环绕声道
5.1 0: L: 左声道 1: R: 右声道 2: C: 中置声道 3: LFE: 低音炮 4: SL: 左环绕声道 5: SR: 右环绕声道

上混和下混

当输入和输出的声道数不匹配时,必须进行上混或下混。以下规则由将AudioNode.channelInterpretation属性设置为speakersdiscrete来控制。

解释 输入声道 输出声道 混音规则
speakers 1 (单声道) 2 (立体声) 将单声道上混到立体声.
M输入声道用于两个输出声道(LR)。
output.L = input.M
output.R = input.M
1 (单声道) 4 (四声道) 将单声道上混到四声道。
M输入声道用于非环绕输出声道(LR)。环绕输出声道(SLSR)静音。
output.L = input.M
output.R = input.M
output.SL = 0
output.SR = 0
1 (单声道) 6 (5.1) 将单声道上混到5.1声道。
M输入声道用于中置输出声道(C)。所有其他声道(LRLFESLSR)静音。
output.L = 0
output.R = 0

output.C = input.M
output.LFE = 0
output.SL = 0
output.SR = 0
2 (立体声) 1 (单声道) 将立体声下混到单声道.
两个输入声道(LR)等权重组合以生成唯一的输出声道(M)。
output.M = 0.5 * (input.L + input.R)
2 (立体声) 4 (四声道) 将立体声上混到四声道。
LR输入声道分别用于其对应的非环绕输出声道(LR)。环绕输出声道(SLSR)静音。
output.L = input.L
output.R = input.R
output.SL = 0
output.SR = 0
2 (立体声) 6 (5.1) 将立体声上混到5.1声道。
LR输入声道分别用于其对应的非环绕输出声道(LR)。环绕输出声道(SLSR)以及中置声道(C)和低音炮声道(LFE)静音。
output.L = input.L
output.R = input.R
output.C = 0
output.LFE = 0
output.SL = 0
output.SR = 0
4 (四声道) 1 (单声道) 将四声道下混到单声道.
所有四个输入声道(LRSLSR)等权重组合以生成唯一的输出声道(M)。
output.M = 0.25 * (input.L + input.R + input.SL + input.SR)
4 (四声道) 2 (立体声) 将四声道下混到立体声.
两个左输入声道(LSL)等权重组合以生成唯一的左输出声道(L)。类似地,两个右输入声道(RSR)等权重组合以生成唯一的右输出声道(R)。
output.L = 0.5 * (input.L + input.SL)
output.R = 0.5 * (input.R + input.SR)
4 (四声道) 6 (5.1) 将四声道上混到5.1声道。
LRSLSR输入声道分别用于其对应的输出声道(LR)。中置声道(C)和低音炮声道(LFE)静音。
output.L = input.L
output.R = input.R
output.C = 0
output.LFE = 0
output.SL = input.SL
output.SR = input.SR
6 (5.1) 1 (单声道) 将5.1声道下混到单声道。
左声道(LSL)、右声道(RSR)和中置声道都混合在一起。环绕声道的音量略微衰减,常规的左右声道的功率进行补偿,使其作为单个声道进行计算,方法是乘以√2/2。低音炮声道(LFE)丢失。
output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL + input.SR)
6 (5.1) 2 (立体声) 将5.1声道下混到立体声。
中置声道(C)与每个侧环绕声道(SLSR)求和并混合到每个侧声道。由于它被下混到两个声道,因此以较低的功率混合:在每种情况下,都乘以√2/2。低音炮声道(LFE)丢失。
output.L = input.L + 0.7071 * (input.C + input.SL)
output.R = input.R + 0.7071 * (input.C + input.SR)
6 (5.1) 4 (四声道) 将5.1声道下混到四声道。
中置声道(C)与左右非环绕声道(LR)混合。由于它被下混到两个声道,因此以较低的功率混合:在每种情况下,都乘以√2/2。环绕声道保持不变。低音炮声道(LFE)丢失。
output.L = input.L + 0.7071 * input.C
output.R = input.R + 0.7071 * input.C
output.SL = input.SL
output.SR = input.SR
其他非标准布局 非标准声道布局的行为就像channelInterpretation设置为discrete一样。
规范明确允许将来定义新的扬声器布局。因此,这种回退方式不是面向未来的,因为浏览器针对特定声道数的行为将来可能会发生变化。
discrete 任意 (x) 任意 (y),其中x<y 上混离散声道。
用其对应的输入声道填充每个输出声道,即具有相同索引的输入声道。没有对应输入声道的声道保持静音。
任意 (x) 任意 (y),其中x>y 下混离散声道。
用其对应的输入声道填充每个输出声道,即具有相同索引的输入声道。没有对应输出声道的输入声道将被丢弃。

可视化

通常,我们会获取一段时间内的输出以生成音频可视化,通常读取其增益或频率数据。然后,使用图形工具,我们将获得的数据转换为可视化表示形式,例如图表。Web Audio API 提供了一个AnalyserNode,它不会改变通过它的音频信号。此外,它输出音频数据,允许我们通过诸如<canvas>之类的技术来处理它。

Without modifying the audio stream, the node allows to get the frequency and time-domain data associated with it, using an FFT.

您可以使用以下方法获取数据

AnalyserNode.getFloatFrequencyData()

将当前频率数据复制到传入的Float32Array数组中。

AnalyserNode.getByteFrequencyData()

将当前频率数据复制到传入的Uint8Array(无符号字节数组)中。

AnalyserNode.getFloatTimeDomainData()

将当前波形或时域数据复制到传入的Float32Array数组中。

AnalyserNode.getByteTimeDomainData()

将当前波形或时域数据复制到传入的Uint8Array(无符号字节数组)中。

注意:有关更多信息,请参阅我们的使用 Web Audio API 进行可视化文章。

空间化

音频空间化允许我们模拟音频信号在物理空间中某个点的定位和行为,模拟听众听到该音频的情况。在 Web Audio API 中,空间化由PannerNodeAudioListener处理。

声场处理器使用右手笛卡尔坐标来描述音频源的位置(作为向量)及其方向(作为 3D 方向锥)。例如,对于全向声源,锥体可以非常大。

The PannerNode defines a spatial position and orientation for a given signal.

类似地,Web Audio API 使用右手笛卡尔坐标来描述监听器:其位置(作为向量)及其方向(作为两个方向向量,向上向前)。这些向量定义了监听器头部顶部的方向和监听器鼻子指向的方向。这些向量相互垂直。

We see the position, up, and front vectors of an AudioListener, with the up and front vectors at 90° from the other.

注意:有关更多信息,请参阅我们的Web 音频空间化基础知识文章。

扇入和扇出

在音频术语中,扇入描述了ChannelMergerNode如何获取一系列单声道输入源并输出单个多声道信号的过程。

Fan-in process diagram. Multiple point-less arrows representing mono-input sources combine to output a single pointed arrow representing a single multi-channel signal

扇出描述了相反的过程,即ChannelSplitterNode如何获取多声道输入源并输出多个单声道输出信号的过程。

Fan-out process diagram. A single point-less arrow representing a multi-channel input source splits to output multiple pointed arrows representing multiple mono output signals