本系列文章主要是介绍 Web Audio API 的相关知识,由于该技术还处在 web 草案阶段(很多标准被提出来,至于取舍需要等待稳定版文档来确定,草案阶段的文档很多都会被再次编辑甚至重写、全部删除等,不适合作为正式参考),很多 API 都是未确定的,目前支持 Web Audio API 的浏览器是较新版的 Google Chrome 和 FireFox,其他浏览器暂时还没有兼容。

现在的网络硬件环境还没有达到普遍语音通信的条件,但是 web语音通信 一定会成为后期 web 研究的一个重要话题,凭着一点个人兴趣,拿出来研究研究~

本文主要介绍 Web Audio API 的相关特性,以及音频源的获取方式。

本文地址:http://www.cnblogs.com/hustskyking/p/webAudio-listen.html,转载请注明源地址。

一、Web Audio

<audio> 标签的到来之前,Flash 以及其他的插件相继打破 web 的宁静,而如今 audio 被送到了 web 开发之中,我们再也不需要引用各种插件来播放声音了。

1. Web Audio API 的特性

Web Audio API 是 JavaScript 中主要用于在网页应用中处理音频请求的一个高级应用接口,这个 API 目的是用于让最新技术与传统的游戏音频引擎的综合处理相兼容,也即尽力提供一些桌面音频处理程序的要求。

  • 查看音频播放期间调度事件发生的确切时间;
  • 支持各种类型的音频过滤波器以实现各种效果,包括回声、消除噪音等;
  • 支持利用合成声音(Sound synthesis)创建电子音乐;
  • 支持3D位置音频模拟效果,比如某种声音随着游戏场景而移动;
  • 支持外部输入的声音与 WebRTC 进行集成(调用 WebRTC ,在你的设备中增添吉他声),或者在 WebRTC 中调用其他地方传输过来的声音;
  • 利用音频数据分析创造良好的可视化声音等。

2. AudioContent 简介

Web Audio API 可以用来操作或者播放网页以及应用中的 audio 资源,在一个 Audio上下文环境(AudioContext)中,有各种 Audio节点(AudioNode),如:

Interface description
AudioContext 包含表示连接中间件 AudioNodes 的音频信号曲线图
AudioNode 表示 audio源,audio输出以及 中间处理模块,他处在 AudioContext 的上下文中
AudioDestinationNode 他是 AudioNode 的一个子类,表示所有被渲染音频流到达的最终地点
AudioBuffer 表示 audio资源 的一个临时缓存,可表示一个音频剪辑
AudioBufferSourceNode 从 AudioBuffer 中生成 audio 的 AudioNode 节点
ScriptProcessorNode 一个可以直接被 JS 操作的 AudioNode 节点
GainNode 音频增益节点
OscillatorNode 一个可产生固定频率的 audio 源

还有其他的 API 可以查看http://www.w3.org/TR/webaudio/#APIOverview.

使用 AudioContext 实例,我们可以将生成的一个或者多个音频流连接到声音的输出位置,这个连接并不一定是直接送到输出端,期间可以使用 AudioNodes 作为中继器和处理器,让这些声音信号有一个更好的效果。

单个 AudioContext 实例可以支持多音频的输入,所以对于一个 audio 应用我们只需要创建一个实例就行了。很多诸如创建一个 AudioNode 节点、解码音频文件等方法都是 AudioContext 的内置方法。创建一个 AudioContext 上下文也十分简单:

群里童鞋问到关于事件传播的一个问题:“事件捕获的时候,阻止冒泡,事件到达目标之后,还会冒泡吗?”。

初学 JS 的童鞋经常会有诸多疑问,我在很多 QQ 群也混了好几年了,耳濡目染也也收获了不少,以后会总结下问题的结论,顺便说说相关知识的扩展~

如果贸然回答还会冒泡,这不太好的,稍微严谨点考虑 0级 DOM 事件模型的话,这个答案是否定的。但是在 2级 DOM 事件模型中,答案是肯定的,这个问题值得探讨记录下。

本文地址:http://www.cnblogs.com/hustskyking/p/problem-javascript-event.html 

一、问题结论
netscape 和 微软 曾经的战争还是比较火热的,当时, netscape 主张捕获方式,微软主张冒泡方式。后来 w3c 采用折中的方式,平息了战火,制定了统一的标准——先捕获再冒泡。

 事件的触发有三个阶段
document 往事件触发地点,捕获前进,遇到相同注册事件立即触发执行
到达事件位置,触发事件(多谢 @糖果果 指出 问题 ,@update 14/2/19 如果该处既注册了冒泡事件,也注册了捕获事件,按照注册顺序执行)
事件触发地点往 document 方向,冒泡前进,遇到相同注册事件立即触发
这么说很多人比较迷糊,我们在注册事件的时候,通常使用的是 捕获 或者 冒泡 的 一种:

obj.addEventListener("click", func, true); // 捕获方式
obj.addEventListener("click", func, false); // 冒泡方式
事件只会因为捕获或者冒泡触发一次。举个栗子:



点击 s2,结果是:



因为这里采用的是捕获模式,从 document 往 s2 走,依次发现 s1 和 s2 都有注册捕获事件,于是便触发了,然后冒泡,没找到冒泡事件,不执行任何操作。如果将 true 改成 false,可以看到结果相反。为了更好的让你理解整个事件机制,我给他们的捕获和冒泡模式下都注册事件:



结果真是太清晰了:





二、误区
指出两个误区。

1. 在同一个对象上注册事件,并不一定按照注册顺序执行

这一点,从上面的例子可以看出,你随便打乱四个事件绑定的顺序,结果一般不变!出现这样结果的原因是存在捕获模式和冒泡模式。但是值得注意的是,下面 #5楼 @糖果果 提出的问题,之所以如此是因为事件目的地节点既绑定了冒泡事件也绑定了捕获事件,此时的执行顺序按照绑定的先后顺序执行(情况比较少见)。

2.event.stopPropagation();就是阻止事件的冒泡

这个表述不能说他错误,但是是不完整的,他除了阻止事件的冒泡,还阻止事件的继续捕获,简而言之就是阻止事件的进一步传播。下面的例子可以看到:



结果是输出了 s1.



三、拓展
1. stopImmediatePropagation 的使用

这玩意儿是 w3c 的东西,使用的也不是特别多,我们知道 stopPropagation 可以阻止事件的进一步传播,但是他阻止不了该元素上绑定的其他函数的执行,比如我们在 obj 上绑定了 func1 和 func2,如果我们在 func1 中使用了 stopPropagation ,那 func2 依然还是会执行出来。倘若这里使用 stopImmediatePropagation,结果就不一样了,他不仅阻止事件的传播,还阻止 func2 的执行。如:



结果是:



而改成evt.stopImmediatePropagation();之后,阻止了第二个监听事件的触发:



结果是:



2. setCapture 和 releaseCapture

这两个是 IE 下的事件绑定函数,只要我们在某个元素上 setCapture 了,那么你在任何地方的鼠标操作(mouseXXX之类的动作)都会在这个元素上触发(前提是你在这个元素上绑定了事件),releaseCapture 或者本窗口失去聚焦才会释放这个绑定~



四、小结
对于此类知识的学习,应该查阅官方点的文档,或者看看《JavaScript权威指南》的解说,后期会经常整理诸如此类的问题。若有疑问,可以在下方评论中提出。var context;
try{
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    context = new AudioContext();
}
catch(e) {
    alert('请更新至最新版的 Chrome 或者 Firefox');
}

二、音频流的获取

1. 标签引入

标签引入是最直接的方式,

<audio autoplay src="http://qianduannotes.duapp.com/file/tankWar.mp3">
    浏览器不支持 audio 标签。
</audio>

运行代码

如果浏览器不支持 audio 标签,便会将其当做一个普通元素来解析,中间一行字也就会被显示出来。而支持 audio 标签的浏览器会忽略标签内任何文本。我们还可以为他加上 autoplay 、loop 等属性,使音频在进入页面之后立即循环播放。

<audio autoplay="autoplay" controls="controls" src="http://qianduannotes.duapp.com/file/tankWar.mp3">
    浏览器不支持 audio 标签。
</audio>

运行代码

controls 属性是用来控制显示音频文件的控制部分的。默认未设置 controls 属性。

2. webRTC mediaStream Media Capture (多谢@Hehe123提醒,RTC属于通信了,此处只是获取 media 流)

利用 getUserMedia 拿到本地的音频流。

// 前缀处理
window.AudioContext = window.AudioContext ||
                  window.webkitAudioContext;
navigator.getUserMedia =  navigator.getUserMedia ||
                      navigator.webkitGetUserMedia ||
                      navigator.mozGetUserMedia ||
                      navigator.msGetUserMedia;

var context = new AudioContext();

navigator.getUserMedia({audio: true}, function(stream) {
  // 获取本地流送入 AudioContext
  var microphone = context.createMediaStreamSource(stream);

  // filter为一个中间处理器,用来过滤音频
  var filter = context.createBiquadFilter();

  // microphone -> filter -> destination.
  microphone.connect(filter);
  filter.connect(context.destination);
}, function(){
    console.log("出了点问题~ 是否在服务器环境下运行?");
});

这里需要注意的是,使用 webRTC 需要在服务器环境下,你可以搭建一个本地服务器,也可以把代码上传到远程服务器上测试。

3. FileSystem

选择本地文件,读取音频流,拿到 Blob 流地址,送入 audio 中

<input type="file" onchange="return run(this.files);" /><br /><br />
<audio controls id="audio"></audio>
<script type="text/javascript">
    function run(files){
        var blob = window.webkitURL.createObjectURL(files[0]);
        audio.src = blob;
        audio.autoplay = "autoplay";
    }
</script>

运行代码

4. 移动设备,还可以使用如下方式

1)HTML Media Capture

<input type="file" accept="audio/*;capture=microphone">

2)device 元素

<device type="media" onchange="update(this.data)"></device>
<audio autoplay></video>
<script>
  function update(stream) {
    document.querySelector('audio').src = stream.url;
  }
</script>

5. 从键盘获取

本质并不是从键盘获取,而是通过键盘获取到我们设定的频率值,然后通过程序创建一段音频。如下面的程序:

下面例子中可以按键盘上中间的一排按键(A到K)来发出不同的声音。

var AudioContext=AudioContext||webkitAudioContext;
var context=new AudioContext;
//为每个键盘位对应一个频率
var s={65:256,83:288,68:320,70:341,71:384,72:426,74:480,75:512};
//为每个频率创建一个Oscillator
for(var i in s)
  value=s[i],
  s[i]=context.createOscillator(),
  s[i].frequency.value=value,
  s[i].start();
//键盘按下时将相应频率的Oscillator连接到输出源上
addEventListener("keydown",function(e){
  if(e=s[e.keyCode])e.connect(context.destination);
});
//键盘松开时将相应频率的Oscillator的连接取消
addEventListener("keyup",function(e){
  if(e=s[e.keyCode])e.disconnect();
});

运行代码

这段代码引自次碳酸钴的博客.

三、小结

本文是个介绍性的文章,提到了 Web Audio API 的相关知识,以及如何在你的 web 程序中引入 音频流。没有写相关 demo,感兴趣的童鞋可以复制代码自己去测试,在后续文章中会给出测试 DEMO。

四、参考文章