音频节点:模块化连接
网页音频接口(Web Audio API)主要是 音频环境(audio context) 中进行音频处理,它允许模块间的连接。基本的音频处理在 音频节点(audio node) 当中执行,这些系欸但连接在一起形成了 音频导向图(audio routing graph). 多个声源,甚至包含不同种类的声道,都可以在一个音频环境中进行处理。这种模块化的设计使得人们在创建复杂多变的声音特效时可以更加灵活。
音频节点可以通过各自的输入与输出相连,形成一个 从一个或多个声源开始,经过处理节点,终止于末节点 的链式结构(有时你不需要末节点,比如你只是想数字化处理某些音频数据的时候)。一个简单、典型的网页音频接口的操作流程可以是这样的:
- 创建一个音频环境
- 在音频环境中,创建声源——例如
<audio>
标签,振动发声器(oscillator),音频流 - 创建特效节点——例如混响,双二阶滤波,声相控制,音频振幅压缩
- 选择音频的终点——例如系统的扬声器
- 连接声源和特效,以及特效和终点。
每个输入和输出都可以包括几个声道,声道代表了一个特定的音效通道。各种声道分离结构都可以使用,包括单声道,立体声,四声道,5.1等等。
声源可以来自不同的地方:
直接通过 Javascript 生成声音节点产生(例如一个振动发声器)
由脉冲编码调制产生的原始数据(音频环境中可以调用一些方法来解码部分支持的格式)
从 HTML 元素中(例如 <video>
或者 <audio>
标签)
直接通过一个 WebRTC MediaStream 获取流媒体(例如一个摄像头或麦克风)
音频数据:什么是样本
当一个音频信号被处理时,取样意味着从一个连续的信号转化为离散的信号;更具体地说,一个连续的声波(例如一个正在演奏的乐队发出的声音)会被转化成一系列的样本点(一个时间上离散的信号),计算机只可以处理这些离散的样本块。
更多的细节可以查看维基百科的采样页面。
音频片段:帧,样本和声道
一个音频片段(AudioBuffer
) 会包含几个组成参数:一个或几个声道(1代表单声道,2代表立体声等等),一个长度(代表片段中采样帧的数目)和一个采样率(是每秒钟采样帧的个数)。
每个样本点都是一个代表着该音频流在特定时间特定声道上的数值的单精度浮点数。一个帧,或者一个采样帧是由一组在特定时间上的所有声道的样本点组成的——即所有声道在同一时间的样本点(立体声有2个,5.1有6个等等,每个帧包含的样本点个数和声道数相同)。
采样率就是一秒钟内获取帧的个数,单位是赫兹(Hz)。采样率越高,音频效果越好。
现在让我们来看一下通道,一个单声道和一个立体声的音频片段,每个都是1秒钟,播放频率(采样率)为44100赫兹:
- 单声道片段会有 44100 个样本点和 44100 个帧。长度属性为 44100 。
- 立体声片段会有 88200 个样本点和 44100 个帧。长度属性依旧为 44100 ,因为长度总和帧的个数相同。
当一个音频片段开始播放时,你将会听到最左侧的样本帧,之后是他右侧相邻的一帧,以此类推。在立体声中,你将会同时听到两个声道。样本帧的概念在此时非常有用,因为每个样本帧代表特定的播放时间,而和声道个数无关,这种方式很有利于精确的多声道同步处理。
注意:只需用帧的数目除以采样率即可得到播放时间(单位为秒)。用样本点数目除以声道个数即可得到帧的数目。
下面我们将展示几个浅显易懂的示例:
1 | var context = new AudioContext(); |
如果你使用上面的方法调用,你将会得到一个立体声(两个声道)的音频片段(Buffer),当它在一个频率为44100赫兹(这是目前大部分声卡处理声音的频率)的音频环境中播放的时候,会持续0.5秒:22050帧 / 44100赫兹 = 0.5 秒
在数字音频中,44,100 赫兹 (有时也写作 44.1 kHz)是一个常见的取样频率。 为什么选取44.1kHz呢?首先,因为人耳的接收频率大约在 20 Hz 到 20,000 Hz之间,根据采样定理,采样频率一定要大于最终生成数据最大频率的二倍,因此就一定要大于40,000 Hz(即 40kHz)。不仅如此,在采样之前信号还必须通过低通滤波器 ,否则会发生混叠现象,一个理想低通滤波器会完全留下低于20kHz的信号(且没有使它衰减)并完美阻拦一切高于20kHz的信号,而事实上过度频带(英文)总是存在,在这个区域内信号会被部分衰减。这个频带越宽,建立一个抗混叠滤波器才越容易。因此我们选取44.1kHz允许我们有2.05kHz的空间预留给过度频带。
1 | var context = new AudioContext(); |
如果你这样调用,你将会得到一个单声道的音频片段(Buffer),当它在一个频率为44100赫兹的音频环境中播放的时候,将会被自动按照44100赫兹重采样(因此也会转化为44100赫兹的片段),并持续1秒:44100帧 / 44100赫兹 = 1秒。
注意:音频重采样与图片的缩放非常类似:比如你有一个16 x 16的图像,但是你想把它填充到一个32 x 32大小的区域,你就要对它进行缩放(重采样)。得到的结果会是一个叫低品质的(图像会模糊或者有锯齿形的边缘,这取决于缩放采用的算法),但它却是能将原图形缩放,并且缩放后的图像占用空间比相同大小的普通图像要小。重新采样的音频道理相同——你会介于一些空间,但事实上你无法产出高频率的声音(高音区)。
分离式与交错式音频片段
网页音频接口使用了分离式的片段储存方式:左(L)右(R)声道像这样存储:
LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (对于一个有16帧的音频片段)
这种储存方式在音频处理中非常常见:这种方式允许对每个声道单独处理。另一种储存方式是使用交错式的片段储存方式:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (对于一个有16帧的音频片段)
这种方式在存储以及播放音频等不需要音频加工的操作中非常常见,例如一个解码后的MP3流媒体。
在开发者接触到的网页音频接口中只有分离式音频片段,因为它主要用于音频加工。在处理过程中它使用分离式,但是当它传送到声卡中用于播放时,音频片段会被转化为交错式。反过来,当MP3被解码时,初始状态它是交错式的,但是他会被转化成分离式以用于音频加工。
声道
不同的音频片段可能包含不同数量的声道个数,从基本的单声道(只有一个声道),立体声(左右两个声道),到更加复杂的四声道,5.1。由于每个声道中包含的音频数据可以不同,因此声道越多听觉效果越好。在不同声道模式中,表示特定声道的缩写如下表所示:
向上和向下混频
当输入与输出的声道数不同时,我们就需要按照如下方法进行混频。这些封装好的方法可以通过设置声音节点的 AudioNode.channelInterpretation
属性为 "speakers"
(扬声器) 或 "discrete"
(离散声道) 进行混频。
可视化
一般来说,可视化是通过获取各个时间上的音频数据(通常是振幅或频率),之后运用图像技术将其处理为视觉输出(例如一个图像)来实现的。网页音频接口提供了一个不会改变输入信号的音频节点 AnalyserNode
,通过它可以获取声音数据并传递到像 <canvas>
等等一样的可视化工具。
你可以通过如下方法获取需要的音频数据:
AnalyserNode.getFloatFrequencyData()
返回一个Float32Array
数组,其中包含传递到此音频节点声音的实时频率数据。AnalyserNode.getByteFrequencyData()
返回一个Uint8Array
无符号字节数组(unsigned byte array)
,其中包含传递到此音频节点声音的实时频率数据。AnalyserNode.getFloatTimeDomainData()
返回一个Float32Array
数组,其中包含传递到此音频节点声音的实时波形,时间数据。AnalyserNode.getByteTimeDomainData()
返回一个Uint8Array
无符号字节数组(unsigned byte array)
,其中包含传递到此音频节点声音的实时波形,时间数据。
注意: 更多信息可以参考我们的这篇文章:基于Web Audio API实现音频可视化效果 。
空间位置化
音频的空间化(由网页音频接口的 PannerNode
和 AudioListener
节点处理)允许我们对空间中某一点的音频信号,以及这一信号的接听者建立位置和行为模型。
声相控制器的位置可以通过笛卡尔坐标系进行描述,控制器的运动可以由速度向量来表示,这会引起多普勒效应,它的传播方向可以用一个方向圆锥来表示,当它是一个全方向声源时,圆锥会变得非常大。
接听者的位置可以用笛卡尔坐标系来表示;他的运动可以用方向向量表示;头部姿态可以用两个向量表示:一个向上向量表示头顶正对的方向,一个向前向量表示鼻子所指向的方向(面向的方向),这两个向量应该互相垂直。
扇入与扇出
对于音频来说,扇入是指 ChannelMergerNode
节点接收一系列单声道输入声源,并将它们整合输出为一个多声道音频信号的过程:
扇出恰恰相反,是指一个 ChannelSplitterNode
节点接收一个多声道输入声源并将它分离成多个单声道音频信号的过程: