上文介绍了 Web Audio API 的相关知识,以及如何在你的 web 程序中引入 音频流,内容都是介绍性的,所以没有写太多 DEMO。本文重点讲解如何利用 Web Audio API 中的中间节点拿到音频信号的信息,并将信息转化成信号图绘制到 canvas 中。

从上文中我们了解到,AudioContext 是音频播放和处理的一个环境,大概的流程是这个样子:

  +---------------+------------------------------------+
| AudioContext | |
+---------------+ |
| +-------+ +-------+ +-------+ |
| | | | | | | |
==========>| Source|===>|Lots of|===>| Dest | Output |
Multi Input| | | | | |===========>
==========>| Node |===>| Nodes |===>| Node | |
| | | | | | | |
| +-------+ +-------+ +-------+ |
| | |
| | Created By Barret Lee |
+-------------------------|--------------------------+
| +-------------+
+========>| Other Tools |
signal +-------------+

在 AudioContext 中,通过一个结点(AudioNode)来接受输入源,中间的一些结点可以过滤、放大、去杂等处理 Source Node 的信号,处理之后可以送到 AudioContext 的输出结点,然后启用 source.start() 播放音频信息;也可以将处理的信息送到外部交给其他对象来处理,比如本文要谈到的,将信号交给 Canvas 来处理,这样就能看到音频信号的波形图了。

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

注:较新版 Google Chrome 和 Firefox 才能运行本文中的 DEMO。

一、节点的连接

先说一说,各个节点在 AudioContext 中的连接是如何用代码实现的:

// 创建一个 AudioContext 环境
var context = new AudioContext(); function playSound() {
// 创建一个 Source 节点
var source = context.createBufferSource();
// 拿到输入源(狗吠)
source.buffer = dogBarkingBuffer;
// 将 source 节点链接到 destination 节点(输出节点)
source.connect(context.destination);
// currentTime 设定为 0,开始播放
source.start(0);
}

上面的连接十分简单,直接将 source 节点连接到 destination 节点,相当于没有经过人任何处理,直接输出了,而下面的方式是创建一个中间节点,对信号做一些处理,不过在拿到 Source 的方式上跟上面有些不一样:

var audio = document.getElementsByTagName("audio")[0];
// context 为一个 AudioContext 环境
// 从 audio 元素中拿到输入源,也就是上图所看到的 Mutil Input
var source = context.createMediaElementSource(audio);
// 建立一个处理延时节点
var delayNode = context.createDelay();
// sourceNode -> delayNode -> destinationNode
source.connect(delayNode);
delayNode.connect(context.destination);

这里需要注意的是,destination 是 AudioContext 实例的固有属性,他就是信号的最终汇聚的位置,也是信号的输出位置。下面是一个简单的 DEMO 代码:

<audio src="http://qianduannotes.duapp.com/file/tankWar.mp3" id="origin"></audio>
<audio src="http://qianduannotes.duapp.com/file/tankWar.mp3" id="audio"></audio>
<input type="button" onclick="origin.play()" value="原始音质 播放" />
<input type="button" onclick="origin.pause()" value="原始音源 暂停" /><br/>
<input type="button" onclick="audio.play()" value="滤波音质 播放" />
<input type="button" onclick="audio.pause()" value="滤波音源 暂停" />
<script type="text/javascript">
var AudioContext = AudioContext || webkitAudioContext;
var context = new AudioContext();
var source = context.createMediaElementSource(audio);
// 低通滤波节点(高频信号被过滤,听到的声音会很沉闷)
var FilterNode = context.createBiquadFilter("lowpass");
// sourceNode -> FilterNode -> destinationNode
source.connect(FilterNode);
FilterNode.connect(context.destination);
</script>

上面的代码,AudioContext 获取 audio 源的原理是这样的:

  1. audio有一个内置的输出通道
  2. AudioContext 通过 createMediaElementSource 将 audio 的输出直接拉去到新的环境中,之前 audio 环境被破坏
  3. 拉去的 source 没有 start 函数,他会一直监听 audio 的操控,当 play 函数被触发的时候,开始播放音频。也可以认为,play 函数触发了 start (老版浏览器中是 noteOn)

下面是一个演示图:

 +----------+------------------------------+
| audio | |
+----------+ |
| +--------+ // +-------------+ |
| | Source |=====//==>| Destination | |
| +--------+ | // +-------------+ |
| | |
+-----------------|-----------------------+
| Created By Barret Lee
+--------|-----+--------------------------+
| ↓ |
| +--------+ +-------+ +------+ |
| | Source |===>| Nodes |===>| Dest | |
| +--------+ +-------+ +------+ |
| +--------------+
| | AudioContext |
+--------------------------+--------------+

二、两个中间节点的介绍

1. ScriptProcessorNode

我们可以直接使用 JavaScript 操控这个节点,他的作用是产生、传递、分析一段音频。他有一个 bufferSize 属性和一个 onaudioprocess 事件。初始化一个 ScriptProcessorNode:

var processor=context.createScriptProcessor(4096, 1, 1);

他接收三个参数,第一个是 bufferSize 的大小,取值范围是 Math.pow(2, N) ( 8≤N≤14 ),第二个参数是送入的 channel 数,第三个参数是输出的 channel 数。信息不会自动通过这个节点需要我们自己将输入的信号复制到输出位置去:

processor.onaudioprocess = function(e){
//获取输入和输出的数据缓冲区
var input = e.inputBuffer.getChannelData(0);
var output = e.outputBuffer.getChannelData(0); //将输入数缓冲复制到输出缓冲上
for(var i = 0, len = input.length; i < len; i++){
output[i] = input[i];
}
}

这样处理的原因是因为多个输入要对应对个输出,也有可能是多对一或者一对多,所以这些信息的设定必须要人为去控制。

关于 ScriptProcessorNode 的介绍,具体请移步http://www.w3.org/TR/webaudio/#ScriptProcessorNode-section

2. AnalyserNode

通过这个节点我们可以对信号进行频域和时域上的分析,学过 通信原理 的同学对这些属于应该是十分熟悉的。

interface AnalyserNode : AudioNode {

    // Real-time frequency-domain data
void getFloatFrequencyData(Float32Array array);
void getByteFrequencyData(Uint8Array array); // Real-time waveform data
void getByteTimeDomainData(Uint8Array array); attribute unsigned long fftSize;
readonly attribute unsigned long frequencyBinCount; attribute double minDecibels;
attribute double maxDecibels; attribute double smoothingTimeConstant; };

上面是这个节点的接口信息,不要感到奇怪,对接口的描述,都是使用这种方式,从上面我们可以看到,他有三个方法,四个属性。fftSize 是指频率分析下的快速傅里叶变换大小,他的值被限定在 32-2048 的 2 的整数次方。

关于 AnalyserNode 的介绍,具体请移步http://www.w3.org/TR/webaudio/#AnalyserNode-section

三、音频信息的提取

利用上面介绍的两个节点可以十分轻松的提取到音频信息,如使用 ScriptProcessorNode,在他的 onaudioprocess 触发的时候,可以拿到 input 信息,此时也就是音频信息流。

processor.onaudioprocess = function(e){
//获取输入和输出的数据缓冲区
var input = e.inputBuffer.getChannelData(0); doSomething(input);
};

上面这种方式拿到数据的效率是比较低的,一般可以直接使用 AnalyserNode 节点。这个节点中一个获取缓冲数据区的方法叫做 getByteTimeDomainData,这个方法的设计是十分偏底层的,或者对 JSer 来说,这个借口的设计并不合理,可以看看:

//以fftSize为长度创建一个字节数组作为数据缓冲区
var output = new Uint8Array(analyser.fftSize);
// 将获取得到的数据赋值给 output
analyser.getByteTimeDomainData(output);

这里是把 output 作为引用传进 getByteTimeDomainData 函数中,相信大家应该没有在 JS 中遇到过这样的写法吧~ (我觉得在该 web 标准定稿的时候,这里一定会做修改!)

四、信号图的绘制

上面我们已经拿到了信号数据了,绘制工作其实就是 canvas 的事情啦~

var width = canvas.width,
height = canvas.height,
g = canvas.getContext("2d"); // 将坐标原点移动到(0.5, height / 2 + 0.5)的位置
g.translate(0.5, height / 2 + 0.5);

然后绘制图形:

processor.onaudioprocess=function(e){
//获取输入和输出的数据缓冲区
var input = e.inputBuffer.getChannelData(0);
var output = e.outputBuffer.getChannelData(0);
//将输入数缓冲复制到输出缓冲上
for(var i=0; i<input.length; i++)
output[i]=input[i];
//将缓冲区的数据绘制到Canvas上
g.clearRect(-0.5, -height/2 - 0.5, width, height); g.beginPath();
for(i = 0; i < width; i++)
g.lineTo(i, height / 2 * output[output.length * i / width|0]);
g.stroke();
};

下面是整个 DEMO 的代码,效果预览:

代码:

<canvas id="canvas" width="400" height="100"></canvas>
<audio id="audio" autoplay src="http://qianduannotes.duapp.com/file/tankWar.mp3"></audio>
<br/>
<input type="button" onclick="audio.play()" value="播放" />
<input type="button" onclick="audio.pause()" value="暂停" />
<script>
var AudioContext=AudioContext||webkitAudioContext;
var context=new AudioContext;
//从元素创建媒体节点
var media=context.createMediaElementSource(audio);
//创建脚本处理节点
var processor=context.createScriptProcessor(4096,1,1);
//Canvas初始化
var width=canvas.width,height=canvas.height;
var g=canvas.getContext("2d");
g.translate(0.5,height/2+0.5);
//连接:媒体节点→控制节点→输出源
media.connect(processor);
processor.connect(context.destination);
//控制节点的过程处理
processor.onaudioprocess=function(e){
//获取输入和输出的数据缓冲区
var input=e.inputBuffer.getChannelData(0);
var output=e.outputBuffer.getChannelData(0);
//将输入数缓冲复制到输出缓冲上
for(var i=0;i<input.length;i++)output[i]=input[i];
//将缓冲区的数据绘制到Canvas上
g.clearRect(-0.5,-height/2-0.5,width,height);
g.beginPath();
for(var i=0;i<width;i++)
g.lineTo(i,height/2*output[output.length*i/width|0]);
g.stroke();
};
</script>

DEMO

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

五、小结

本文着重讲述了 AudioContext 内部节点之间的交互原理,以及如何使用节点获取数据,关于图形的绘制是 canvas 的操作,不是本系列文章的重点,所以一笔带过。

如果文中有叙述不正确的地方,还请斧正!

六、参考资料

[Voice communications] 看得到的音频流的更多相关文章

  1. [Voice communications] 音量的控制

    改变音频的音量是音频处理中最基础的部分,我们可以利用 GainNode 来构建 Mixers 的结构块.GainNode 的接口是很简单的: interface GainNode : AudioNod ...

  2. [Voice communications] 声道的转换

    本系列文章主要是介绍 Web Audio API 的相关知识,以及 web语音通信 中会遇到的一些问题,阐述可能存在错误,还请多多斧正! 很多粤语剧都提供了两个声道,一个左声道为粤语,一个右声道有国语 ...

  3. [Voice communications] 声音的滤波

    本系列文章主要是介绍 Web Audio API 的相关知识,以及 web语音通信 中会遇到的一些问题,阐述可能存在错误,还请多多斧正! 通过设备获取音频流会不可避免的渗入一些杂音,这些杂音可能来自你 ...

  4. [Voice communications] 让音乐响起来

    本系列文章主要是介绍 Web Audio API 的相关知识,由于该技术还处在 web 草案阶段(很多标准被提出来,至于取舍需要等待稳定版文档来确定,草案阶段的文档很多都会被再次编辑甚至重写.全部删除 ...

  5. (Step by Step)How to setup IP Phone Server(VoIP Server) for free.

    You must have heard about IP Phone and SIP (Software IP Phone).Nowadays standard PSTN phone are bein ...

  6. week9:个人博客作业

    团队作业(5) 以下内容多数是网上的内容,只是做了整合的过程. 要求 在PM 带领下, 每个团队深入分析下面行业的App, 找到行业的Top 5 (从下面的三个备选中,任选一个行业即可) 英语学习/词 ...

  7. 每日英语:5 Things to Know About Missing Malaysia Airlines Flight and Air Safety

    Malaysia Airlines Flight MH370, with 239 people aboard, lost contact early Saturday with the airline ...

  8. 转载:P2P技术原理及应用(1)

    转帖allen303allen的空间 作 者:金海 廖小飞 摘要:对等网络(P2P)有3种主要的组织结构:分布式哈希表(DHT)结构.树形结构.网状结构.P2P技术已 经延伸到几乎所有的网络应用领域, ...

  9. Method for address space layout randomization in execute-in-place code

    The present application relates generally to laying out address space for execute-in-place code and, ...

随机推荐

  1. shell循环语句

    所有的笔记只记录一些例子,根据例子解释一些出现的语法,不介绍具体的语法 2015-07-01 21:58:33 星期三 for循环 用例一用for循环在家目录下创建aaa1-aaa10,然后在aaa1 ...

  2. Cocos2d-x 生成真正的随机数

    关于随机数 cocos2d-x 定义了一个宏 CCRANDOM_0_1 生成的是 [0, 1] 之间的值 因此,要生成  [0-100] 之间的数    CCRANDOM_0_1 * 100 生成 [ ...

  3. YUV格式介绍

    原文链接:http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html YUV格式有两大类:planar和packed.对于plana ...

  4. Codeforces Round #379 (Div. 2)

    A ~~ B ~~ C 对于第二种方法,我们可以任取一个换c[i]个potions,花费d[i]:或者是不取,我的做法就是枚举这些情况,得到剩余的s,再尽量优的获取小的a[i]: 枚举+二分 #inc ...

  5. sql字段属性

  6. input=text数字问题

    其实老干部也会犯错,今天朋友在银行卡账号时候要求输入数字,它的正则竟然排除中文.其实正则是很重要的.废话少说.如果禁止中文呢 ime-mode 但是这个方法兼容性并不是很好,首先我们得知道. css ...

  7. trigger() --工作中问题nav样式

    自动执行某元素的某个事件 $("#div").trigger("click");  //让系统自动执行单击事件 适用于nav样式中,下面横线绝对定位于nav.o ...

  8. 老生长谈的$.extend()方法

    jq的extend()是jq插件扩展很重要的部分,到这里证明是可以自己在jq的基础上,分为两种方法去扩展或开发,为jq本身添加一个方法,可以理解成扩展静态方法和自定义方法. 今天有看到一篇帖子,对这部 ...

  9. Jsonp跨域

    Jsonp.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  10. QT5之三大重要窗体

    当创建项目时,会发现编辑器提供三个基类,分别为:QMainWindow.QWidget.QDialog,三个基类的区别说明如下.1.QMainWindowQMainWindow类提供一个有菜单条.锚接 ...