注:本文使用的Web Audio API遵循W3C在18年9月发布的候选推荐版本,本文代码在Chrome76中测试通过,请注意代码兼容性。如有错误,请不吝指正。
引言
每段缘起始都很简单,或是擦肩而过的那阵清风,或是四目相对的那缕温情,亦是共经患难的那份情谊……初春的一天,在我被工作中出现的问题折磨的不堪之际,经朋友引荐,她出现在我的视野中。我忘不了她解决问题时的飒爽英姿,那干练的身影在我脑海中迟迟不能散去。我有意要了解她,却总感觉只在冰山一角。终于经历了几个月的徘徊后,我下定决心不再等待,开一个系列来介绍我的理解。Why now? Why not?
本文是《认识 Web Audio》系列文章的第一篇,主要涉及音频上下文的一些简单概念的介绍和使用。整个系列分为以下文章:
- 遇见你,很幸运——初识 Web Audio
- 知其然,知其所以然——Web Audio原理探究
- 我和音符有个约会——音频可视化篇
- 为你弹奏肖邦的夜曲——音频创作篇
- 是谁带来远古的呼唤——音频空间化篇
Web Audio背景
长期以来在网页上播放音视频都是一个痛点,虽然HTML5引入了audio、video标签来实现基本的音视频播放,但却不足以应付更复杂情况如游戏引擎、实时交互等场景下的音频处理。于是,Web Audio应运而生。但要说明的是,它之于audio标签并不是替代关系,而是类似img和canvas的一种补充的关系。Web Audio播放音频能实现安卓、ios的一致性,消除诸如播放有延迟、不能循环播放等问题,如howler.js(当然不仅于此);其次可以利用振荡器、滤波器等创造自己的乐器,进行交互式音乐的构建、实现歌曲的演奏,如Tone.js;还可以对音频播放进行可视化,如Pts.js……
Web Audio是应在web浏览器中处理音频的需求而产生的,它早已不是一个新鲜的概念,但并没有大规模使用却是一个不争的事实。除去不值一提的兼容性问题,我想原因大概有几个,首先是API居多,因为它要实现在浏览器产生、处理音频必须要有相应的接口,但和专业音频制作程序仍有一定差距,不是特别能吸引专业人士;然后就是要实现实时WebRTC、游戏级引擎的音频特效等所需要的专业知识(数字信号处理、通信原理等)所带来的学习成本也是让很多人望而却步。不过大势所趋,它还在处于不断发展中,相信未来前景还是不错的。
Web Audio提供的合成声音、添加特效、音频可视化功能,是在音频上下文完成的,它将不同的操作细化为对应的节点实现了模块化(Modular routing),各个节点又可以通过connect方法相连接,进行必要的处理后,最终输出到目标节点(音频上下文的destination属性,通常是声音输出设备如扬声器)。整个流程连接在一起形成一个音频路由图(audio routing graph)。
Web Audio Api简单工作流程为:
- 创建音频上下文(AudioContext或OfflineAudioContext)
- 在上下文中创建声音来源(如audio标签、xmlhttprequest请求得到的arraybuffer、oscillator振荡器产生的各种波形)
- 创建特效节点(如混响、各种滤波、平移、压缩)
- 选择音频的最终产出地(如系统扬声器)
- 将来源连到特效节点,将特效节点连到目的节点
Web Audio Api使得我们可以更精确(时间上可以做到无延迟、空间上可以产生衰减效果模拟真实环境)的操作音频,这是audio标签所不具备的。举个例子来说,如果要实现音频的渐入渐出效果,原始方法需要使用定时器来不断更改volume来实现,而使用web audio只需将源节点连到增益节点(GainNode),设置gainNode.gain.linearRampToValueAtTime(value, endTime)就能实现在endTime-currentTime时间段内,声音值从原始到value值的变化,可以说操纵很轻松了。
Web Audio基础
在正式使用之前,首先了解一下Web Audio Api的部分接口。
音频上下文接口
-
BaseAudioContext是实际使用的音频上下文AudioContext(用于实时渲染)和OfflineAudioContext(用于离线渲染)的基类,不能被直接实例化。音频节点都在其内创建并相互连接在一起,允许信号最终连接到AudioDestinationNode节点进行播放,形成一个音频路由图。其包含的部分属性如下:
state属性是一个枚举属性,取值为 {‘suspended’, ‘running’, ‘closed’},表示音频上下文的当前状态。前两个可通过实例的resume()
和suspend()
方法切换,调用close()之后音频上下文将会释放系统资源,不能再次使用。
currentTime属性表示上下文的运行时间,当上下文处于running状态时该值会以均匀的速度单调递增,由渲染线程控制,不一定和处理的音频时间同步。
destination属性是一个AudioDestinationNode节点,通常是实际的音频输出设备。
sampleRate只读属性,表示采样率,在处理过程中保持不变,因此实时处理中不支持采样率转换。在不设置的情况下默认为音频输出设备的采样率。进行处理时使用的采样率如果和音频输出时的采样率不一致会进行重采样。 -
AudioContext实时音频上下文,来直接为用户产生信号。AudioContext初始化时state默认为suspended,因为自动播放策略限制,必须经过用户操作后才允许处于运行状态,这可通过resume()恢复音频上下文,或者调用AudioBufferSourceNode的start()时来恢复上下文。主动暂停上下文使用suspend(),关闭使用close()。但要注意的是虽然二者都可以释放包括线程,进程和音频流等系统资源。但暂停后可通过resume()恢复运行,关闭AudioContext则意味着释放所有资源,此后将无法使用或再次恢复它。构造函数中可传入contextOptions的对象,如果选项中包含sampleRate属性,则采样率设置为该属性,否则使用默认输出设备的采样率。
-
OfflineAudioContext离线音频上下文,虽然有destination属性,但实际并不会渲染到音频输出硬件中,但会尽可能快的渲染(一般来说要比实时渲染更快),通过
startRendering()
返回AudioBuffer,适用于那些可以在后台进行音频处理的场景。使用OfflineAudioContext(numberOfChannels, length, sampleRate)
构造函数进行初始化,参数必填,不过也可以将一个包含这三个属性的对象(sampleRate/length必须属性)作为参数传递。因为构造时就已经确定了长度,所以在渲染length/smapleRate时间之后其状态就会变为closed,不能继续使用了。和AudioContext不同的是,离线音频上下文只有suspend(suspendTime)
方法,没有close()
方法,在音频数据渲染完之后主动关闭。终止时必须传入终止时间,以在指定时刻终止,该方法一般来说只有在同步操作音频数据时才有用。
音频节点、数据和音频控制接口
-
AudioNode:音频节点,是所有展示在音频路由图中节点模块(如音频源节点、目的节点、过滤器节点、增益节点等)的基类,不能直接实例化,提供connect方法实现节点间的连接。
-
AudioBuffer:音频缓冲区,由
createBuffer(numberOfChannels, length, sampleRate)
或构造器创建而来,参数都是必填,生成指定长度的音频buffer,该buffer默认初始化为0。该buffer包含duration(=length/sampleRate)、length、numberOfChannels、sampleRate属性。可通过getChannelData(channel)
方法获取某一通道下的音频数据,返回一个Float32Array类型的数据。它只是保存数据源,不是真正的音频节点,只有赋值给相应的AudioNode才有作用。像AudioBufferSourceNode的buffer就是AudioBuffer类型。 -
AudioParam:音频参数接口,控制AudioNode的某个方面,比如音量。该接口除了value属性,其他属性都是只读的。可以直接为参数的value赋值,也可以使用AudioParam的方法实现预先设定。
setValueAtTime(value, startTime)
可以做到的当AudioContext的currentTime走到给定的startTime时间后进行赋值为value。linearRampToValueAtTime(value, endTime)
可以做到从当前时间到endTime从当前值线性变化到value值。常用场景是实现声音播放的渐隐渐现,来避免使用定时器实现音量大小的设置。setTargetAtTime(target, startTime, timeConstant)
可用于实现声音信号的衰变。
音频源节点
-
AudioBufferSourceNode:音频数据源节点,由createBufferSourceNode()方法或构造器创建得到。它接受一个AudioBuffer作为buffer属性来提供数据源,数据源必须来自内存。
-
MediaElement