第一章:基础
这一章将介绍如何着手开始学习web音频api、哪些浏览器支持音频api、如何检测音频api是否可用、什么是音频图、什么是音频节点、如何连接各音频节点、一些基础的节点类型,最后将介绍如何加载声音文件和播放声音。

网页音频历史简介
第一种在网页上播放声音的方法是通过<bgsound>标签实现的,当用户访问网页的时候,该标签能让网站作者自动播放背景音乐。这个特性只能在IE浏览器中使用,并且该特性从未被标准化或被其他浏览器采用。网景公司用<embed>标签实现了一个相似的特性,并提供了基本相同的功能。


Flash是第一个跨浏览器的在网页上播放声音的方法,但是有一个巨大的缺点是需要有插件运行。近年来,浏览器厂商已经聚焦HTML5的<audio>元素,该元素在现代浏览器中提供了对音频的原生支持。


尽管<audio>标签使得音频在网页中不再需要插件,但它在实现复杂的游戏和交互应用时依然有很大的局限性。以下是部分<audio>元素的局限性:
·没有精确的时间控制
·一次只能播放非常少的声音数量
·不能可靠的预加载声音
·不能使用实时效果
·不能解析声音


历史上有过几个尝试创建一个功能强大的网页音频api来针对以上局限,有一个著名的例子就是音频数据api。它是Mozilla Firefox设计并制作的原型。Mozilla的方法是先创建一个<audio>元素然后把更多的功能拓展到它的JavaScript API上。这些API只有有限的音频图(后文“音频上下文”包含更多关于音频图的信息),并且在第一次实现之后就没有被采用了。为了支持网页音频api,Firefox现在已经废弃这个api。


与音频数据api不同,网页音频api是一个全新的模型,与<audio>完全分离,尽管与其他网页api有一些集成的地方(查看第七章)。它是一个高级JavaScript api,用来在网页应用中处理和合成音频。这个api的目标是包含更多发现在现代游戏引擎和一些现代桌面音频产品应用中的音频混合、处理和过滤任务中的音频处理的能力。最终的结果是一个多功能的api,这个api可以被用在多种音频相关的任务上,从游戏到交互应用到非常高级的音乐合成应用和可视化应用。

游戏和交互
音频是让交互体验非常吸引人的一个巨大的部分。如果你不相信我,可以试着把声音关掉去看一部电影。


游戏也不例外!我最钟情的视频游戏记忆全是音乐和声音特效。现在,距离我最喜欢的游戏发行将近20年,我依然无法忘记Koji Kondo的Zelda和Matt Uelmen的Diablo配音。(后面bla bla说他如何不能忘记那些游戏的声音——译者注)


声音特效在游戏之外也有着巨大影响。它们从有命令行的时候就存在于各种UI当中,比如某些命令错误会让电脑发出“哔”的声音。同样的思路延续到了现代UI中,处理得当的声音在通知、钟鸣中是至关重要的,当然也包括一些音视频交流应用,比如说Skype,Google Now和Siri提供了非常丰富的基于音频的反馈。当我们深入研究这个计算无所不在的世界的时候,会发现使得人们进入无屏交互的基于语音和手势的接口更加依赖于音频的反馈。最后,对于具有视力损害的电脑用户,声音提示、语音合成和语音识别对于创建可用的体验是至关重要的。

交互式的音频展现了一些有意思的挑战。为了创造有识别度的游戏中的音乐,设计者需要调整以适应一个游戏者可能处于的所有的潜在不可预测的游戏状态。实践中,游戏部分可以运行不确定的时间,同时声音与环境相互影响并以复杂的方式混合。它们有着环境所特有的效果和相关的声音位置。最终,可能有很大数量的声音同时播放,这些声音都需要听起来不错并且在渲染时不会引起质量和性能的降低。

音频上下文
网页音频API建立在音频上下文的概念之上。音频上下文是音频节点的直接图像,它定义了一个音频流如何从它的源(通常是音频文件)流到它的端(通常是你的扬声器)。因为音频通过每一个节点,所以他的属性可以被修改和审查。最简单的音频上下文是直接从从源节点到端节点的连接。(图示1-1)

图示1-1


一个音频上下文可以变得非常复杂,包含许多源到端之间的节点(图示1-2),这样做可以实现任意的高级合成和解析。

图示1-2


图示1-1和1-2把音频节点表示成了块。箭头表示节点之间的连接。节点通常可以有多个输入和多个输出连接。默认情况下,如果有多个输入连接到一个节点,网页音频API只是简单的将所有输入音频信号混合。


音频节点图不是新的概念。它可以追溯到一些流行的音频框架比如苹果的CoreAudio,这款软件有着相似的音频处理图API。这个思想更老了,它起源于1960年代的一些音频环境,比如说Moog模块合成器系统。

初始化音频上下文
网页音频API现在被Chrome和Safari浏览器(包括iOS6移动版Safari)实现,开发者可以通过JavaScript来使用。(现在Firefox、Opera、IE Edge和最新版的Android系统原生浏览器都支持该接口——译者注)在这些浏览器中,音频上下文构造器需要webkit的前缀,这意味着你该创建一个webkitAudioContext而不是AudioContext。然而,这些在未来都会改变。这个API将走向足够稳定来以至于可以移除前缀,其他浏览器也将陆续实现该API。Mozilla已经公开声明他们将在Firefox中实现网页音频API,Opera也已经加入了这个工作组当中。


有了以上理解,就有了一个兼容的方式来初始化音频上下文,这包含了其它的几种实现(一旦它们存在):
var contextClass = (window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
window.oAudioContext ||
window.msAudioContext);
if (contextClass) {
// Web Audio API is available.
var context = new contextClass();
} else {
// Web Audio API is not available. Ask the user to use a supported browser.
}


一个单一的音频上下文可以支持多个声音输入和复杂的音频图,所以一般来说,我们创造的每一个音频应用只需要一个音频上下文。音频上下文的实例包含许多创建音频节点和操作全局音频偏好的方法。幸运的是,这些方法都没有webkit的前缀并且相对稳定。API现在依然在改变,所以要小心突然的改变。(详勘附录A)

网页音频节点的类型
音频上下文的一个主要用处是创建一个新的音频节点。大体上说,有以下几种音频节点:
源节点
  声音源比如说音频缓冲、实时音频的输入、<audio>标记、振荡器和JS处理器
改良节点
  过滤器、卷积器、声像、JS处理器等等
分析节点
  分析器和JS处理器
端节点
  音频输出和脱机处理缓冲


源不需要以声音文件为基础,事实上它可以是一个实时的现场播放的乐器或者话筒的输入、一个音频节点的音频直接输出(详勘“从<audio>标记开始建立背景音乐”)或者整个合成的声音(详勘“用JavaScript处理音频”)。尽管最终的端节点通常是扬声器,你依然可以不播放处理的音频(例如单纯的做一个可视化效果)或者做脱机处理。后者造成的结果是音频流被写入一个端的缓冲中以备后用。

连接音频图
任何音频节点的输出都可以用connect()函数连接到任何其他节点的输入。在以下例子中,我们连接了一个源节点的输出到一个增长节点中,并且连接这个增长节点的输出到这个上下文的端。
// Create the source.
var source = context.createBufferSource();
// Create the gain node.
var gain = context.createGain();
// Connect source to filter, filter to destination.
source.connect(gain);
gain.connect(context.destination);
注意context.destination是一个特殊的与你的系统默认的音频输出有关的节点。以上代码的音频图像与看起来像图示1-3。

图示1-3


一旦我们连接了一个像这样的图像我们可以动态的修改它。我们可以用node.disconnect(outputNumber)从图中断开一个节点。例如,为了改造线路使得源和端可以直接连接,绕过中间的节点,我们可以如下做:
source.disconnect(0);
gain.disconnect(0);
source.connect(context.destination);

模块化流程的力量
在许多游戏中,最终的混合音频由许多声音源合并而来。源包括背景音乐、游戏声音特效、UI反馈声音、在多玩家的情况下的其它玩家的说话声音。网页音频API的一个重要的特点是它可以让你分开所有不同的声道并且给你对每一个和全部音频的完全的控制权。这种设置的音频图看起来可能像图示1-4。

图示1-4


我们已经把一些增长节点联系在每一条声道上并且创建了一个主增长节点来控制他们。有了这种设置,你的玩家分别、精确的控制每一个声道将变得容易起来。例如,许多人更喜欢在玩游戏时把背景音乐关掉。

什么是声音?
用物理学术语,声音是一种可以通过声音和其他介质传播的纵波。声音源使得空气中的分子互相震动和碰撞。这就会产生高压区和低压区,他们同时产生且散布在各个频段中。如果你可以凝固时间和看到声波图案,你会看到像图示1-5一样的东西。

图示1-5
数学上,声音可以被表示为一个函数,它表示时间域下的压力值。图示1-6展示了一个这样的函数的图。你可以看出它和图示1-5相似,高值的地方对应于粒子密集的地区(高压),低值的地方对应于粒子疏松的地区(低压)。

图示1-6

回溯至20世纪早期的电子学使我们第一次捕捉和再现了声音。话筒捕捉压强波并且把它转化为电子信号。+5伏对应于最高压,-5伏对应于最低压。相反的,音频扬声器把电压转化成压强波,我们就可以听到了。


我们是在综合声音还是在分析它,这个对于音频编程者的有趣点在图示1-7的黑盒子里,它是操作音频信号的地方。在音频的早期,这个地方被模拟信号滤波器和其他用我们的现在的标准说古老的硬件所占据。今天,有许多与这些古老的滤波器装置相同的现代数字产品。但在我们可以用软件来处理有趣的东西之前,我们需要把声音表示成一个方式以使得电脑可以操作之。

图示1-7

什么是数字声音?
我们可以把模拟信号通过一定频率来进行时间采样,然后把每个样例编码为一个数字。我们对模拟信号的采样的速率称为采样速率。在声音应用中常用的采样速率是44.1kHz。这意味着声音的每一秒都有44100个记录。这些速率必须在一定范围内降低。通常每个值都会分配有一定的位的数量,这个位的数量叫做位深。对大多数数字录音(包括CD)来说,位深为16,这对大部分听者来说足够了。Audiophiles使用的是24位深,这使得声音足够精确到用户听不出它与更高的位深之间的差别。


把模拟信号转化为数字信号的过程叫做数字化(或抽样)。图解为图示1-8所示。

图示1-8
在图示1-8中,数字化之后的数字信号和模拟信号不太相同,这是由于条柱和平滑的线之间的差异造成的。这个差异(蓝色部分所示)在更高的采样率和位深的情况下可以降低。然后,增加这些值的同时也增加了内存、磁盘或网页中存储这些声音的存储单元数量。为了节省空间,电话系统通常使用低至8kHz的采样率,因为使得人声可听明白的频率范围比我们完全的发声频率范围要低得多。
有了声音数字化,电脑可以把声音当做一个很长的数字数组。这种编码方式叫做脉冲编码(PCM)。因为电脑非常善于处理数组,对于大部分音频应用来说,PCM成为一个非常强大的基础。在网页音频API的世界中,表示声音的长数字数组被抽象为AudioBuffer。AudioBuffer可以存储多音频声道(通常是用立体声的方式,也就是说左右声道),它被表示为一个由标准化为-1和1之间的浮点数组。同样的信号也可以被表示成一个位长为16位,范围从(-2)的15次方到(2)的15次方-1之间的整数组成的数组。

音频编码格式
原始的PCM编码的音频非常大,它使用了额外的内存、浪费了硬盘驱动的空间并且在下载时消耗了额外的带宽。正因如此,音频一般以压缩格式来存储。有两种压缩方式:有损和无损。无损压缩(比如FLAC)保证压缩和解压声音之后,位数是相同的。有损音频压缩(比如MP3)利用了人听觉的特点,丢弃掉我们听不到的位来节省空间。有损压缩通常对大多数人来说足够好了,发烧友除外。


压缩量的通用量度叫做比特率,这表示音频播放时每一秒所播放的位数。比特率越高,单位时间内分配的数据量越多,因此,这需要更少的压缩。通常,有损压缩(比如MP3)使用比特率来进行描述(常用的比特率为128Kb/s和192Kb/s)。把有损音频编码为任意比特率是可能的。例如,电话质量的人声通常与8Kb/s的MP3差不多。一些格式(比如OGG)支持多比特率,还可以根据时间进行改变比特率。小心不要把比特率和采样率或者位深混淆。(详勘什么是声音)。


浏览器对不同的音频格式的支持也大不相同。一般的,如果网页音频API在浏览器中被实现了,它会使用<audio>标签使用的格式。因此浏览器对<audio>和网页音频接口的支持模型是一样的。一般来说WAV(一个简单的无损的,典型未压缩的格式)被所有浏览器支持。MP3依然受到专利限制,以至于它不能在纯开源浏览器(比如Firefox和Chromium)中使用。不幸的是,不太流行但没有专利限制的OGG格式在写作时依然不被Safari支持。


查看更加实时的音频格式支持表格,详勘 http://mzl.la/13kGelS

下载并播放声音
网页音频API严格区分缓冲和源节点。这个架构的想法是为了解耦声音资源和播放状态。以唱片机作为例子,缓冲相当于唱片,源节点相当于播放头。在网页音频API的世界中却不一样,你可以用任意多的唱片头同时播放同一个唱片。因为许多应用需要同时播放一段缓冲的不同地方,这个模型是非常重要的。例如,如果你想连续快速播放弹跳球的声音,你只需要加载一次弹跳的缓冲,然后在播放时安排不同的源节点。
为了下载一段音频样例到网页音频API中,我们可以使用XMLHttpRequest并且用context.decodeAudioData来处理结果。这些都是异步的,所以不会阻塞主UI线程。
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';

// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(theBuffer) {
buffer = theBuffer;
}, onError);
}
request.send();
音频缓冲都只有一个可以播放的源节点。其他源节点包括从话筒中直接输入或者内置设备或者一个<audio>标签。(详勘第七章)


一旦你下载了你的缓冲,你可以给它创造一个源节点(AudioBufferSourceNode),把这个源节点连接到你的音频图中,然后在源节点上调用start(0)。如果想要停止一段声音,可以在源节点上调用stop(0)。注意这两个函数调用都需要一个当前音频上下文的坐标系统上的时间参数。(详勘第二章)
function playSound(buffer) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(0);
}


游戏通常有个循环的背景音乐。然而,要小心你的选段的过度重复:如果一个玩家卡在一个地区或者一个等级。并且与其连续播放一段相同的背景音乐,更值得做的做法是渐渐消失来防止出现不好的情况。另外一个策略是使渐变强度的音频混合并且基于游戏场景相互消失到另外一段音频中。(详勘渐变的音频参数)

把它们放在一起
如果你看了之前的代码,就知道在使用网页音频API播放声音的时候需要建立一些东西。在一个现实的游戏中,要考虑实现一个与网页音频API有关的JavaScript的抽象类。一个例子就是下例中的BufferLoader类。它把所有的东西放在一个简单的下载器中,这个下载器给出一个路径集合,返回音频缓冲集合。下面就是如何使用这个类:
window.onload = init;
var context;
var bufferLoader;

function init() {
context = new webkitAudioContext();

bufferLoader = new BufferLoader(
context,
[
'../sounds/hyper-reality/br-jam-loop.wav',
'../sounds/hyper-reality/laughter.wav',
],
finishedLoading
);

bufferLoader.load();
}

function finishedLoading(bufferList) {
// Create two sources and play them both together.
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();
source1.buffer = bufferList[0];
source2.buffer = bufferList[1];

source1.connect(context.destination);
source2.connect(context.destination);
source1.start(0);
source2.start(0);
}
查看一个BufferLoader的简单实现,查看http://webaudioapi.com/samples/shared.js。

 

posted on 2016-02-18 18:24  iStartan  阅读(490)  评论(0编辑  收藏  举报