HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图:

项目演示:别说话,点我!  源码已经挂到github上了,有兴趣的同学也可以去star或者fork我,源码注释超清楚的哦~~之前看刘大神的文章和源码,感觉其他方面的内容太多了,对初学者来说可能一下子难以抓到Web Audio API的重点,所以我就从一个初学者的角度来给大家说说Web Audio API这些事吧。

Web Audio API与HTML5提供的Audio标签并不是同样的东西,他们之间的区别可以自行搜索。简单的说Audio就是一个自带GUI的标签,他对音频的操作还是比较弱的,而Web Audio API则封装了非常多的对音频的操作,功能十分强大。Web Audio API目前还是一个草案,最新版本可以浏览这里:https://www.w3.org/TR/webaudio/,当然还有MDN上面的介绍:https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API#Example

注意:由于Web Audio API还是非常新,所以现在浏览器的支持不是非常好,具体兼容性如下:

一、整体流程:

一般来说,一个典型的Audio应用的流程都是这样的:

1、创建音频环境(audio Context)

2、创建音源,可以通过audio标签、文件流等方式

3、创建效果节点,可以是混响、压缩、延迟、增益等效果器、也可以是分析节点。

4、为音频选一个输出,比说说你的扬声器。然后将源、效果器和输出连接起来。

玩过吉他或者贝斯的哥们应该很容易理解了:音频环境就是你整个乐器硬件系统,音源就是你的乐器,效果节点就是各种效果器,当然也需要音箱输出和各种连线了。要对音乐进行处理,最重要的就是效果器节点了。这个项目没有增加其他效果,只用了一个分析节点,用来量化音频信号再关联到图形元素上面,得到可视化效果。

二、最简版本代码

 <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>简单测试</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400">您的浏览器不支持canvas标签</canvas>
<audio id="myAudio" src="simplelove.mp3" controls="controls" >您的浏览器不支持audio标签</audio>
<script type="text/javascript">
window.onload = function () {
try {
var audioCtx = new (window.AudioContext ||window.webkitAudioContext)();
} catch (err) {
alert('!Your browser does not support Web Audio API!');
};
var myCanvas = document.getElementById('myCanvas'),
canvasCtx = myCanvas.getContext("2d"),
myAudio = document.getElementById("myAudio"),
source = audioCtx.createMediaElementSource(myAudio),
analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.connect(audioCtx.destination);
myAudio.oncanplaythrough = function draw () {
var cwidth = myCanvas.width,
cheight = myCanvas.height,
array = new Uint8Array(128);
analyser.getByteFrequencyData(array);
canvasCtx.clearRect(0, 0, cwidth, cheight);
for (var i = 0; i < array.length; i++) {
canvasCtx.fillRect(i * 3, cheight - array[i], 2, cheight);
}
requestAnimationFrame(draw);
};
};
</script>
</body>
</html>

这个是最精简的版本,相信可以帮助大家快速理解整个流程,但是需要注意的是这个版本不兼容移动端。 

三、关键代码分析

1、创建音频环境(audio Context)

创建音频环境非常简单,但是要考虑到浏览器兼容问题,需要用一个或语句来兼容不同浏览器。另外,对于不支持AudioContext的浏览器,还需要加一个try catch来避免报错。

try {
var audioCtx = new (window.AudioContext ||window.webkitAudioContext)();
} catch (err) {
alert('!Your browser does not support Web Audio API!');
};

2、创建音源

创建音源的方法有以下几种:

1) 直接从HTML的video/audio元素中获取,使用的方法为AudioContext.createMediaElementSource().

var source = audioCtx.createMediaElementSource(myMediaElement); //myMediaElement可以是页面中的元素,也可以是用new创建的audio/video对象 

注意:由于audio标签在移动端还有相当多的坑,大家可以搜索一下,避免踩坑哈!如果不考虑移动端的话,最简单的办法就是这种啦。

2) 从原始的PCM数据中创建音源,方法有AudioContext.createBuffer(),AudioContext.createBufferSource(), 和AudioContext.decodeAudioData(). 这里可以分成从网络中获取或者从本地文件中选择两种方式,关键代码如下。

var source = audioCtx.createBufferSource();  //创建一个空的音源,一般使用该方式,后续将解码的缓冲数据放入source中,直接对source操作。
// var buffer = audioCtx.createBuffer(2, 22050, 44100); //创建一个双通道、22050帧,44.1k采样率的缓冲数据。
audioCtx.decodeAudioData(audioData, function(buffer) {
source.buffer = buffer; //将解码出来的数据放入source中
//其他操作
}, function(err){
  alert('!Fail to decode the file!'); //解码出错处理
});

网络获取方式,需要开一个Ajax异步请求来获取文件,并且设置请求返回类型为'ArrayBuffer',代码如下:

// use XHR to load an audio track, and
// decodeAudioData to decode it and stick it in a buffer.
// Then we put the buffer into the source
function getData() {
var request = new XMLHttpRequest();  //开一个请求
request.open('GET', url, true);    //往url请求数据
request.responseType = 'arraybuffer'; //设置返回数据类型
request.onload = function() {
var audioData = request.response;
//数据缓冲完成之后,进行解码
audioCtx.decodeAudioData(audioData, function(buffer) {
source.buffer = buffer; //将解码出来的数据放入source中
//进行数据处理
}, function(err) {
       alert(‘!Fail to decode the file!’); //解码出错处理
     });
};
request.send();
}

本地获取的话需要通过文件类型的input标签来进行文件选择,监听input的onchnage事件,一担文件选中便开始在代码中进行文件读入,此处用到file reader来读取文件,同样的读取结果的数据类型也设置为'ArrayBuffer'。我的项目使用了file reader本地读取的办法,兼顾移动端。

var audioInput = document.getElementById("uploader"); //HTML语句:<input type="file" id="uploader" />
audioInput.onchange = function() {
  //文件长度不为0则真的选中了文件,因为用户点击取消也会触发onchange事件。
  if (audioInput.files.length !== 0) {
    files = audioInput.files[0]; //得到用户选取的文件
    //文件选定之后,马上用FileReader进行读入
    fr = new FileReader();
    fr.onload = function(e) {
      var fileResult = e.target.result;
      //文件读入完成,进行解码
      audioCtx.decodeAudioData(fileResult, function(buffer) {
        source.buffer = buffer;//将解码出来的数据放入source中
        //转到播放和分析环节        }, function(err) {
        alert('!Fail to decode the file'); //解码出错
      });
    };
    fr.onerror = function(err) {
      alert('!Fail to read the file'); //文件读入出错
    };
    fr.readAsArrayBuffer(rfile); //同样的,ArrayBuffer方式读取
  }
};

3、创建效果节点,选择输出并将源、效果器和输出连接起来。

前面也说过了,效果有非常多种,本文是建立分析节点。这里的连接是音源>>分析节点>>输出,为什么不直接将音源和输出连接起来呢?其实也可以直连,但因为分析节点需要做一定的处理,如果直接将音源和输出连接的话会有一定的延迟。另外,这里定义了一个status参数,用来指示状态值。最后的启动有两种写法,有兴趣的同学再去MDN上查查吧。

var status = 0,   //状态,播放中:1,停止:0
  arraySize = 128, //可以得到128组频率值
analyser = audioCtx.createAnalyser(); //创建分析节点
source.connect(analyser); //将音源和分析节点连接在一起
analyser.connect(audioCtx.destination); //将分析节点和输出连接在一起
source.start(0);  //启动音源
status = 1; //更改音频状态

4、可视化绘图

为了得到可视化效果,还需要对分析节点做一个傅里叶变换将信号从时域转到频域中,此处原理省略一万字。。。有兴趣的同学可以去看看信号处理相关的书籍。但是,这么难得的装逼机会,我舍不得呀!看不惯的同学可以跳过,也可以微信转账给我(什么鬼。。。)。

项目中getByteFrequencyData(array)这个函数来获取所需频率的能量值,其中array数组的长度为频率的个数。有看过资料的同学会发现这里很多时候用的是analyser.frequencyBinCount和analyser.fftsize两个值,其中analyser.fftsize是快速傅立叶变换(FFT)用于频域分析的尺寸,默认为2048。analyser.frequencyBinCount是fftsize的一半,这里我不需要那么多组数据,所以自定义了一个长度为128的8位无符号整形数组(谭浩强C语言后遗症,勿怪)。另外需要注意的是频率值的范围,是0到255,如果需要精度更高的值,可以使用AnalyserNode.getFloatFrequencyData()得到32位浮点数。经过上面的得到了这些值之后,我们就可以用它来跟某些视觉元素关联起来,比如说常见的柱状频谱的高度、圆的半径、线条的密度等等。我的项目里面采用的是能量球的方式。

var canvas = document.getElementById('drawCanvas'),
ctx = canvas.getContext('2d'),
cwidth = canvas.width,
cheight = canvas.height,
visualizer = [],    //可视化形状
animationId = null;
var random = function(m, n) {
return Math.round(Math.random() * (n - m) + m);  //返回m~n之间的随机数
};
for (var i = 0; i < num; i++) {
var x = random(0, cwidth),
y = random(0, cheight),
color = "rgba(" + random(0, 255) + "," + random(0, 255) + "," + random(0, 255) + ",0)";  //随机化颜色
visualizer.push({
x: x,
y: y,
dy: Math.random() + 0.1,   //保证dy>0.1
color: color,
radius: 30  //能量球初始化半径
});
} var draw = function() {
var array = new Uint8Array(128);  //创建频率数组
analyser.getByteFrequencyData(array);  //分析频率信息,结果返回到array数组中,频率值范围:0~255
if (status === 0) {
//array数组归零,有时候音频播完了但是频率值还残留着,这时候需要强制清零
for (var i = array.length - 1; i >= 0; i--) {
array[i] = 0;
};
var allBallstoZero = true;  //能量球归零
for (var i = that.visualizer.length - 1; i >= 0; i--) {
allBallstoZero = allBallstoZero && (visualizer[i].radius < 1);
};
if (allBallstoZero) {
cancelAnimationFrame(animationId);   //结束动画
return;
};
};
ctx.clearRect(0, 0, cwidth, cheight);
for (var n = 0; n < array.length; n++) {
var s = visualizer[n];
s.radius = Math.round(array[n] / 256 * (cwidth > cheight ? cwidth / 25 : cheight / 18));  //控制能量球大小与画布大小的比例
var gradient = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.radius);  //创建能量球的渐变样式
gradient.addColorStop(0, "#fff");
gradient.addColorStop(0.5, "#D2BEC0");
gradient.addColorStop(0.75, s.color.substring(0, s.color.lastIndexOf(",")) + ",0.4)");
gradient.addColorStop(1, s.color);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(s.x, s.y, s.radius, 0, Math.PI * 2, true);  //画一个能量球
ctx.fill();
s.y = s.y - 2 * s.dy;  //能量球向上移动
if ((s.y <= 0) && (status != 0)) {  //到画布顶端的时候重置s.y,随机化s.x
s.y = cheight;  
s.x = random(0, cwidth);
}
}
animationId = requestAnimationFrame(draw);  //动画
};

5、其他

音频播完:

source.onended = function() {
status = 0;  //更改播放状态
};

自适应方面,在window的onload和onresize调整canvas尺寸,但重点不在这里。重点是能量球的大小如何跟随画布大小调节,因为动画在进行中,所以最好的方案也是在动画中动态改变,所以上段代码中的cwidth和cheight的赋值应该放在绘图动画中。

var canvas = document.getElementById('drawCanvas');
canvas.width = window.clientWidth
       || document.documentElement.clientWidth
|| document.body.clientWidth;
canvas.height = window.clientHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;

增加点小玩法:鼠标捕捉能量球,鼠标进入能量球内的时候就捕捉到能量球,并要保持移动才能持续抓紧能量球

//鼠标捕捉能量球
canvas.onmousemove = function (e) {
if (status != 0) {
for (var n = 0; n < visualizer.length; n++) {
var s = visualizer[n];
if (Math.sqrt(Math.pow(s.x-e.pageX,2) + Math.pow(s.y-e.pageY,2)) < s.radius) {
s.x = e.pageX;
s.y = e.pageY;
}
}
}
};

最后说说非常重要的代码规范!

上述代码只是为了简化关系突出流程而改写的代码,并不符合代码规范。为了更好地进行编码,我们应该创建一个全局对象,把上述所有相关属性及方法写到其中。全局对象不但可以方便管理,而且在chrome中调试的时候,可以直接在控制台中查看并编辑,调试起来非常方便。按照惯例,对象的属性直接写在构造器里,对象的方法写到原型中方便日后扩展继承。对象内部使用的私有方法还有私有属性以短横线开头,也是从封装的角度考虑的。

这篇文章主要是参考了刘哇勇的博文和代码(下2),后来查看MDN的时候发现了下1的更通俗简单的版本,都是大力推荐!写文章真不容易,首先是思路重新整理,然后是材料的收集,代码的增减,工作量真心大。写作不易,赶紧点赞哈~

Reference:

1、Visualizations with Web Audio API(官方原版,强力推荐)   https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API

2、开大你的音响,感受HTML5 Audio API带来的视听盛宴 http://www.cnblogs.com/Wayou/p/html5_audio_api_visualizer.html

【HTML5】Web Audio API打造超炫的音乐可视化效果的更多相关文章

  1. HTML5 ——web audio API 音乐可视化(二)

    上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...

  2. HTML5 ——web audio API 音乐可视化(一)

    使用Web Audio API可以对音频进行分析和操作,最终实现一个音频可视化程序. 最终效果请戳这里; 完整版代码请戳这里,如果还看得过眼,请给一个start⭐ 一.API AudioContext ...

  3. 关于HTML5音频——audio标签和Web Audio API各平台浏览器的支持情况

    对比audio标签 和 Web Audio API 各平台浏览器的支持情况:   audio element Web Audio API desktop browsers Chrome 14 Yes  ...

  4. 【Web Audio API】 — 那些年的 web audio

    转 TAT.Jdo:[Web Audio API] - 那些年的 web audio 这主题主要是早期对 web audio api的一些尝试,这里整理一下以便以后翻阅,如有错误,诚请指正. 在这之前 ...

  5. Web Audio API 实现音频可视化

    声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! 一转眼就已经有三个月没写博客了,毕业季事情确实多,现在也终于完全毕业了,博客还是不能落下.偶尔还是要写一下. 玩HTML5的Audio A ...

  6. [Javascript] Intro to the Web Audio API

    An introduction to the Web Audio API. In this lesson, we cover creating an audio context and an osci ...

  7. 关于Web Audio API的入门

    Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...

  8. 使用Web Audio API绘制音波图

    摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音.音效.平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制. PS:本例子使用ES6编程, ...

  9. H5的Web Audio Api

    概述 研究Web Audio Api的主要原因是:工作中需要在ios中实现声音的淡出效果,主要是通过setInterval来改audio标签的volume属性实现的,但是ios上面volume属性是只 ...

随机推荐

  1. Android studio 自定义打包apk名

    Android Studio打包应用默认生成的apk名称是:app-release.apk .如果我们要让生成的apk名跟我们版本包名有联系的话,那我们就要自定义生成的apk名了 需要在build.g ...

  2. MySQL和Lucene索引对比分析

    MySQL和Lucene都可以对数据构建索引并通过索引查询数据,一个是关系型数据库,一个是构建搜索引擎(Solr.ElasticSearch)的核心类库.两者的索引(index)有什么区别呢?以前写过 ...

  3. FTP上传文件到服务器

    一.初始化上传控件. 1.我们这里用dropzone.js作为上传控件,下载地址http://www.dropzonejs.com/ 2.这里我们使用一个div元素作为dropzone载体. < ...

  4. centos配置虚拟主机

    首先注释掉 DocumentRoot /var/www/html 然后添加如下代码至文件底部:       NameVirtualHost 192.168.0.3     <virtualhos ...

  5. Element-ui,Mint-ui

    style-loader css-loader style!css 饿了么团队开源一个基于vue 组件库 elementUI PC MintUI 移动端 官网: http://element.elem ...

  6. npm-async使用

    async.series(tasks, callback) tasks可以是对象或数组,返回类型就是参数类型 tasks中传入回调函数的第一个值非空即停止后面函数执行 按照顺序流程进行 async.s ...

  7. 转:Delphi2010新发现-类的构造和析构函数功能

    Delphi2010发布了. 虽然凭着对Delphi的热爱第一时间就安装了,但是现在可能是年纪大了,对新事物缺乏兴趣了.一直都没有仔细研究. 今天有点时间试了一下新功能. 本来C#和Delphi.NE ...

  8. 转: Delphi的OverRide、OverLoad和Virtual方法

    http://blog.csdn.net/ckli/article/details/2201418 override 重写 也叫覆盖 .方法的重写Overriding和重载Overloading是Ja ...

  9. 复利计算器(软件工程)及Junit测试———郭志豪

    计算:1.本金为100万,利率或者投资回报率为3%,投资年限为30年,那么,30年后所获得的利息收入:按复利计算公式来计算就是:1,000,000×(1+3%)^30 客户提出: 2.如果按照单利计算 ...

  10. XmlRpc.net 出参字符串还原为结构体

    上一篇随笔写的是入参结构体转字符串,现在需要把保存到服务器的字符串还原为结构体,这里记录一下操作步骤: 1. 格式化字符串. XmlRpcDeserializer 支持反序列化<struct&g ...