本文解释了 Web Audio API 功能背后的一些音频理论,以帮助你在设计应用程序的音频路由时做出明智的决策。如果你还不是一名音响工程师,本文将为你提供足够的背景知识,让你理解 Web Audio API 为何会如此运作。
音频图(Audio graphs)
Web Audio API 涉及在音频上下文(AudioContext
)中处理音频操作,其设计旨在实现模块化的音频路由。每个音频节点执行一项基本的音频操作,并与一个或多个其他音频节点相连接,从而形成一个音频路由图。它支持具有不同声道布局的多个音频源,即便在单一的上下文中也是如此。这种模块化设计为创建带有动态效果的复杂音频功能提供了灵活性。
音频节点通过它们的输入和输出相连接,形成一个链路,该链路起始于一个或多个音频源,经过一个或多个处理节点,然后终止于一个目标节点(如果你只是想将某些音频数据可视化,那么并非必须要提供目标节点)。一个简单、典型的网页音频工作流程看起来大致如下:
- 创建一个音频上下文(
AudioContext
); - 在音频上下文中,创建声源(如
<audio>
标签,振荡器 (oscillator
)或音频流); - 创建效果节点(如混响,双二阶滤波,声相控制,音频振幅压缩节点等);
- 选择音频的终点(如系统的扬声器);
- 连接声源和效果节点,以及效果节点和终点。
每个输入或输出都由一个或多个音频声道组成,这些声道共同代表一种特定的音频布局。支持任何离散的声道结构,包括单声道、立体声、四声道、5.1
声道等等。
注意:
声道表示法是一个数值,比如2.0
或5.1
,它代表着信号上可用的音频声道数量。第一个数字表示该信号所包含的全频带音频声道的数量。小数点后面出现的数字则表示为低频效果(LFE
)输出预留的声道数量;这些声道通常被称作超低音声道(或低音炮声道)。
你可以通过以下几种方式获取声源:
- 由音频节点(例如振荡器)直接在
JavaScript
中生成; - 从原始的脉冲编码调制(PCM)数据(例如
.WAV
文件或由decodeAudioData()
支持的其他格式的数据)来创建声音; - 由
HTML
的媒体元素(例如<video>
或<audio>
)生成; - 从网络实时通信(
WebRTC
)媒体流(如麦克风)中获取声音。
音频数据:样本中有什么
当对音频信号进行处理时,就会涉及采样。采样是将连续信号转换为离散信号的过程。换句话说,像乐队现场演奏这样的连续声波,会被转换为一系列数字样本(离散时间信号),这样计算机就能以不同的数据块来处理音频了。
你可以在维基百科 采样 页面找到更多信息。
音频缓冲区:帧、样本和声道
音频缓冲区(AudioBuffer
)由三个参数来定义:
- 声道数量(单声道为
1
,立体声为2
,依此类推); - 缓冲区长度,也就是缓冲区内部样本帧的数量;
- 采样率,即每秒播放的样本帧数量。
一个样本是一个 32
位的浮点值,它代表了音频流在特定声道(若是立体声,则为左声道或右声道)内每个特定时刻的值。一帧(或样本帧)是指在特定时刻将要播放的所有声道的全部数值的集合:即所有声道同时播放的所有样本(对于立体声来说是两个,对于 5.1
声道来说是六个,依此类推)。
采样率是指那些样本(或帧,因为一帧中的所有样本是同时播放的)在一秒内播放的数量,以赫兹(Hz
)为单位来计量。采样率越高,音质就越好。
让我们来看一个单声道音频缓冲区和一个立体声音频缓冲区,它们时长均为一秒,采样率均为 44100
赫兹:
- 单声道缓冲区将会有
44100
个样本以及44100
个帧。其 “长度” 属性的值将会是44100
。 - 立体声音频缓冲区将会有
88200
个样本,但仍然是44100
个帧。其 “长度” 属性的值仍然会是44100
,因为该值等同于帧的数量。
当缓冲区开始播放时,你首先会听到最左边的样本帧,然后是紧挨着它右边的那一帧,接着是再右边的一帧,依此类推,直至缓冲区播放结束。如果是立体声的情况,你会同时听到两个声道的声音。样本帧很方便使用,因为它们与声道数量无关,并且能以一种理想的方式来代表时间,便于进行精确的音频操作。
注意:
要从帧数量得出以秒为单位的时间,需将帧的数量除以采样率。要从样本数量得出帧的数量,只需将样本数量除以声道数量即可。
以下是几个简单的示例:
const context = new AudioContext();
const buffer = new AudioBuffer(context, {
numberOfChannels: 2,
length: 22050,
sampleRate: 44100,
});
如果你使用上述调用,将会得到一个具有两个声道的立体声音频缓冲区,当在以 44,100 Hz
运行的音频上下文(这很常见,大多数普通声卡都以此速率运行)中播放时,其时长将为 0.5
秒:22050
帧 / 44100
赫兹 = 0.5
秒。
注意:
在数字音频中,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
的过渡频带。
const context = new AudioContext();
const buffer = new AudioBuffer(context, {
numberOfChannels: 1,
length: 22050,
sampleRate: 22050,
});
如果你使用这个调用,将会得到一个单声道缓冲区(单声道的缓冲数据),当在以 44,100 Hz
运行的音频上下文中播放时,它会被自动重新采样至 44,100 Hz
(因此会产生 44,100
个帧),并且持续时长为 1.0
秒:44100
帧 / 44100
赫兹 = 1
秒。
注意:
音频重采样与图片的缩放非常相似。假设你有一张16×16
像素的图像,但想让它填充到32×32
像素的区域,你就会对它进行调整大小(或重采样)操作。结果图像的质量会有所下降(可能会变得模糊或有锯齿,具体取决于调整大小的算法),但这样做是可行的,并且缩放后的图像占用空间比相同大小的普通图像要小。重采样后的音频也是如此:你节省了空间,但在实际操作中,你无法正确重现高频内容或高音部分的声音。
分离式与交错式缓冲区
Web Audio API 使用分离(非交错)式缓冲区格式。左声道和右声道是按如下方式存储的:
LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (对于一个有 16 帧的缓冲区)
这种结构在音频处理中应用广泛,使得独立处理每个声道变得容易。
另一种方法是使用交错式缓冲区格式:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (对于一个有 16 帧的缓冲区)
这种格式在不需要过多处理的音频存储和播放方面应用很广泛,例如:.WAV
文件或已解码的 MP3
数据流。
由于 Web Audio 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
属性设置为 speakers
(扬声器)或 discrete
(离散声道),可以应用以下规则:
输入声道 | 输出声道 | 混频方式 | 混频规则 |
---|---|---|---|
1 (单声道) | 2 (立体声) | speakers | 从单声道到立体声的向上混频。 唯一的输入声道( M )会被同时用于立体声的两个声道(L 和 R )。output.L = input.M output.R = input.M |
1 (单声道) | 4 (四声道) | speakers | 从单声道到四声道的向上混频。 唯一的输入声道( M )会被同时用于非环绕声的两个声道(L 和 R )。环绕声道( SL 和 SR )将为静音。output.L = input.M output.R = input.M output.SL = 0 output.SR = 0 |
1 (单声道) | 6 (5.1 ) | speakers | 从单声道到 5.1 的向上混频。 唯一的输入声道( M )会被同时用于中央声道(C )。其余所有声道( L ,R ,LFE ,SL ,SR )都将保持静音。output.L = 0 output.R = 0 output.C = input.M output.LFE = 0 output.SL = 0 output.SR = 0 |
2 (立体声) | 1 (单声道) | speakers | 从立体声到单声道的向下混频。 两个输入声道( L 和 R )将会被均等的合并到唯一的输出声道(M )当中。output.M = 0.5 * (input.L + input.R) |
2 (立体声) | 4 (四声道) | speakers | 从立体声到四声道的向上混频。 输入的左右声道( L 和 R )分别对应输出的非环绕左右声道(L 和 R )。环绕左右声道( SL 和 SR )将保持静音。output.L = input.L output.R = input.R output.SL = 0 output.SR = 0 |
2 (立体声) | 6 (5.1 ) | speakers | 从立体声到 5.1 的向上混频。 输入的左右声道( L 和 R )分别对应输出的非环绕左右声道(L 和 R )。其余所有输出声道( C ,SL ,SR 和 LFE )将保持静音。output.L = input.L output.R = input.R output.C = 0 output.LFE = 0 output.SL = 0 output.SR = 0 |
4 (四声道) | 1 (单声道) | speakers | 从四声道到单声道的向下混频。 四个输入声道( L ,R ,SL 和 SR )将会被均等的合并到唯一的输出声道(M )当中。output.M = 0.25 * (input.L + input.R + input.SL + input.SR) |
4 (四声道) | 2 (立体声) | speakers | 从四声道到立体声的向下混频。 两个输入左声道( L 和 SL )将会被均等的合并到输出的左声道(L )当中。相似的,两个输入右声道( R 和 SR )将会被均等的合并到输出的右声道(R )当中。output.L = 0.5 * (input.L + input.SL) output.R = 0.5 * (input.R + input.SR) |
4 (四声道) | 6 (5.1 ) | speakers | 从四声道到 5.1 的向上混频。 四个输入声道( L ,R ,SL 和 SR )将会分别进入它们对应的输出声道(L ,R ,SL 和 SR )当中。其余输出声道( 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 (单声道) | speakers | 从 5.1 到单声道的向下混频。 输入的低音炮声道( LFE )将会被抛弃。其余声道按不同权值混合到唯一的输出声道( M ):输入的中央声道(C )权值为 1 ,输入的非环绕侧声道(L 和 R )有所减弱,权值为√2/2 (即 1/√2 ,约等于 0.7071 ),输入的环绕声道(SL 和 SR )进一步衰减,权值为 0.5 。output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL + input.SR) |
6 (5.1 ) | 2 (立体声) | speakers | 从 5.1 到立体声的向下混频。 输入的低音炮声道( LFE )将会被抛弃。对于每侧的输出声道( L 或 R ):由输入的中央声道(C )先与同侧环绕声道(SL 或 SR )混合,加权(权值为√2/2 )后与输入的同侧非环绕声道(L 或 R )混合得到。output.L = input.L + 0.7071 * (input.C + input.SL) output.R = input.R + 0.7071 * (input.C + input.SR) |
6 (5.1 ) | 4 (四声道) | speakers | 从 5.1 到四声道的向下混频。 输入的低音炮声道( LFE )将会被抛弃。对于每侧的输出声道( L 或 R ):由输入的中央声道(C )加权(权值为√2/2 )后,与输入的同侧非环绕声道(L 或 R )混合后得到。对于每侧的输出环绕声道( SL 或 SR ):由同侧输入环绕声道(SL 或 SR )会不经改变直接传入。output.L = input.L + 0.7071 * input.C output.R = input.R + 0.7071 * input.C output.SL = input.SL output.SR = input.SR |
任意(x 个声道) | 任意(y 个声道),其中x<y | discrete | 向上混频离散的声道。 根据相应的频道序号,将输入声道一对一的填入到输出声道中。对于没有输入声道能够对应的,该输出声道将保持静音。 |
任意(x 个声道) | 任意(y 个声道),其中x>y | discrete | 向下混频离散的声道。 根据相应的频道序号,将输入声道一对一的填入到输出声道中。对于没有输出声道能够对应的,该输入声道将被抛弃。 |
可视化
一般来说,可视化是通过获取各个时间上的音频数据(通常是振幅或频率),然后,利用图形工具,将获取到的数据转换为可视化表示形式(如图像)。Web Audio API 提供了一个AnalyserNode
,它不会改变通过它的音频信号,但它会输出音频数据,使我们能够通过诸如<canvas>
之类的技术对其进行处理。
你可以通过如下方法获取需要的音频数据:
AnalyserNode.getFloatFrequencyData()
返回一个Float32Array
数组,其中包含传递到此音频节点声音的实时频率数据。AnalyserNode.getByteFrequencyData()
返回一个Uint8Array
无符号字节数组,其中包含传递到此音频节点声音的实时频率数据。AnalyserNode.getFloatTimeDomainData()
返回一个Float32Array
数组,其中包含传递到此音频节点声音的实时波形,时间数据。AnalyserNode.getByteTimeDomainData()
返回一个Uint8Array
无符号字节数组,其中包含传递到此音频节点声音的实时波形,时间数据。
注意:
更多信息可以参考 使用 Web Audio API 进行可视化 这篇文章。
空间化
音频空间化使我们能够对音频信号在物理空间中某个特定点的位置及行为进行建模,模拟听众听到该音频时的情况。在Web Audio API中,音频空间化是由PannerNode
和AudioListener
两个节点来处理的。
声像控制器使用右手笛卡尔坐标,将音频源的位置描述为一个矢量,并将其方向描述为一个三维定向圆锥体。例如,对于全向音频源来说,这个圆锥体可以相当大。
同样,Web Audio API 使用右手笛卡尔坐标来描述听众:将听众的位置描述为一个矢量,将其朝向描述为两个方向矢量,即 “上方” 矢量和 “前方” 矢量。这些矢量定义了听众头顶的方向以及听众鼻子所指的方向,这些矢量相互垂直。
注意:
更多信息可以参考 Web 音频空间化基础 这篇文章。
扇入与扇出
在音频领域,“扇入” 描述的是 ChannelMergerNode
接收一系列单声道输入源并输出单个多声道信号的过程:
“扇出” 描述的是与之相反的过程,即ChannelSplitterNode
接收一个多声道输入源,并输出多个单声道输出信号:
评论0
暂时没有评论