上文介绍了 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. 【leetcode】Valid Parentheses

    题目简述: Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if th ...

  2. C# DM5 32位加密

    using System.Security.Cryptography;using System.Text; public static string StringToMD5Hash(string in ...

  3. 为你的Android App实现自签名的 SSL 证书(转)

    介绍 网络安全已成为大家最关心的问题. 如果你利用服务器存储客户资料, 那你应该考虑使用 SSL 加密客户跟服务器之间的通讯. 随着这几年手机应用迅速崛起. 黑客也开始向手机应用转移, 原因有下列3点 ...

  4. WebStorm 2016.2 破解方法

    http://www.jetbrains.com/ 以前的 http://idea.lanyus.com/ 不能激活了 有帖子说的 http://15.idea.lanyus.com/ http:// ...

  5. Mac下golang开发环境配置

    go语言在开发效率和运行效率中的优势让很多人青睐,所以有倾向打算转向go语言的开发. 下面介绍在Mac OS X中golang的开发环境配置. 1.安装brew brew是一个mac下的由ruby开发 ...

  6. maven打包时,依赖包打不进jar包中

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  7. TF-IDF算法确定阅读主题词解答英语阅读Title题目

    #include <math.h> #include <time.h> #include <stdlib.h> #include <iostream> ...

  8. 移动web资源整理

    [原]移动web资源整理 2013年初接触移动端,简单做下总结,首先了解下移动web带来的问题 设备更新换代快--低端机遗留下问题.高端机带来新挑战 浏览器厂商不统一--兼容问题多 网络更复杂--弱网 ...

  9. 缺少.lib文件导致的Link2019 解决方案汇总

    环境Vs2015,  Win10 添加lib的方法在末尾 下面的错误都是我在写Direct3D程序中遇到的, 记下来方便查找 4.ws2_32.lib 3.   version.lib _GetFil ...

  10. my97DatePicker日期控件——日期输入框联动,使用focus使第二个输入框没展示出日期控件

    描述问题场景: 1.jquery使用的版本是jquery-1.7.2.min.js 2.代码不是写在页面上的,是通过事件后追加的 <!DOCTYPE html> <html> ...