用HTML5实现音频可视化
本文作者:辰光
最近在coding上观摩了一个可视化音频播放的项目,进而在MDN上研究了Web Audio API,
觉得结合canvas绘图还是非常酷炫的,Web Audio API可以利用AnalyserNode来截取音频源数据,可以是频率,波形或是其他的数据,MDN上有相关的API文档。
在Coding的项目的链接:
https://coding.net/u/luojia/p/Audio-Visualizations/git
个人觉得真是大神,能做得那么炫酷,特地把这个项目下下来自己研究一番。
今儿打算用html来读取音频文件,通过截取音频在canvas上绘图。
起步-Analyser
首先,需要一个Analyser来截取,而Analyser由AudioContext来创建:
MDN上给出的写法是:
var audioCtx= new(window.AudioContext || window.webkitAudioContext) ();
var analyser = audioCtx.createAnalyser();
考虑兼容性问题,可以这样写:
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
这样就相当打了兼容补丁上去,能保证可以使用AudioContext了。
在这里,打算用AudioContext加requestAnimationFrame来实现。
接下来的思路
1.定义一个可视化音频的类,类存放文件,文件名,audioContext;
2.初始化这个类;
3.类上定义监听事件的方法,即监听文件,获取文件相关数据;
4.解码文件数据,解码后的数据进行canvas可视化;
var AudioCanvas = function() {
this.file = null, //文件,获取文件封装在监听器里
this.fileName = null, //文件名
this.audioContext = null, //截取音频处理,通过initial初始化
};
以下的方法可以写到AudioCanvas.prototype中:
initial
requestAnimationFrame保持绘图循环,部分浏览器不支持此特性,故采用了与AudioContext相同的方法,完成初始化
function () {
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
try {
this.audioContext = new AudioContext();
} catch (e) {
console.log('!你的浏览器不支持AudioContext:');
console.log(e);
}
}
初始化后应该考虑的是如何获取到文件了,这里我们用一个监听方法实现:
addEventListener
function() {
var that = this,
fileInput = document.getElementById('uploadedFile'),
dropContainer = document.getElementsByTagName("canvas")[0];
//监听是否有文件被选中
fileInput.onchange = function() {
//通过判断文件长度,若为0,则没有读取到文件
if (fileInput.files.length !== 0) {
that.file = fileInput.files[0]; //获取文件
that.fileName = that.file.name; //获取文件名
that.decodeFile(); //解码文件数据
}
};
}
获取文件后,对文件进行相关处理,解码数据:
decodeFile
function() {
var that = this,
file = this.file, //文件
fRead = new FileReader(); //实例化一个FileReader用于读取文件
//将文件传给fRead的readAsArrayBuffer方法,获取ArrayBuffer格式的数据
fRead.readAsArrayBuffer(file);
fRead.onload = function(e) { //文件读取完后调用此函数
var fileResult = e.target.result; //这是读取成功得到的结果ArrayBuffer数据
var audioContext = that.audioContext;
audioContext.decodeAudioData(fileResult, function(buffer) {
that.visual(audioContext, buffer); //visual将进行一些相关的连接,看后面的说明和代码
}, function(e) { //如果失败
console.log("文件解码失败!");
});
};
}
visual
MDN上给出的相关接口:
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
个人觉得看不太明白这里用Node是怎么来连接音源的,上网查阅了相关信息,
觉得MDN这段话是先创建一个源,源连接analyserNode节点,节点连接扬声器,最后再将
Buffer数据传递给源。
function(audioContext, buffer) {
var audioBufferSouceNode = audioContext.createBufferSource(),//创建源
analyser = audioContext.createAnalyser();//创建AnalyserNode节点
//连接源和AnalyserNode节点,在中途可以截取音频
audioBufferSouceNode.connect(analyser);
//连接扬声器,输出音频
analyser.connect(audioContext.destination);
//将上一步解码得到的buffer数据赋值给源
audioBufferSouceNode.buffer = buffer;
//指代开始的时间
audioBufferSouceNode.start(0);
//音乐响起后,就捕捉到音频频率了,接下来要做的就是如何绘图,即用canvas来实现
this._drawAudio(analyser);
}
绘制之前,需要了解几个接口:
MDN上的说明:
AnalyserNode.getFloatFrequencyData()和AnalyserNode.getByteFrequencyData()获取频率;
AnalyserNode.getByteTimeDomainData()和AnalyserNode.getFloatTimeDomainData()获取波形
实际上这些方法返回一个数组,但并不是一个普通的数组,AnalyserNode.getFloatFrequencyData()
和AnalyserNode.getFloatTimeDomainData()
产生32位float数组,可以用new Float32Array()
来创建,
而AnalyserNode.getByteFrequencyData()
和AnalyserNode.getByteTimeDomainData()
产生8位无符号数组,可以用new Uint8Array()
创建;
这些在MDN上都有相关的说明,不懂什么是Float32Array
和Uint8Array
的去翻翻MDN的文档就知道了。
AnalyserNode.frequencyBinCount
, 它是FFT的一部分, 可以调用Uint8Array()
,把frequencyBinCount
作为它的参数 — 这代表我们将对这个尺寸的FFT收集多少数据点。
用法:
analyser.fftsize= 2048;
var bufferLength=analyser.frequencyBinCount;
var dataArray= new Uint8Array(bufferLength);
然后将dataArray传到上述讲的获取频率或是波形的方法中:
analyser.getByteTimeDomainData(dataArray);
drawAudio
function(analyser) {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
cwidth = canvas.width,
cheight = canvas.height,
meterWidth = 10, //频谱条宽度
capHeight = 2,
capStyle = '#fff',
meterNum = 800 / (10 + 2), //频谱条数量
capYPositionArray = [], //将上一画面各帽头的位置保存到这个数组
gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(1, '#f00');
gradient.addColorStop(0.5, '#f00');
gradient.addColorStop(0, '#f00');
var drawRect = function() {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / meterNum); //取部分样式
ctx.clearRect(0, 0, cwidth, cheight);
for (var i = 0; i < meterNum; i++) {
var value = array[i * step]; //获取对应的频率值
if (capYPositionArray.length < Math.round(meterNum)) {
capYPositionArray.push(value); //初始化顶端的值,将第一次压入数组中
//如果想看到具体的效果,可以把capStyle的值改为"#000",会有很明显的不同
}
ctx.fillStyle = capStyle;
//开始绘制
if (value < capYPositionArray[i]) { //如果当前值小于之前值
ctx.fillRect(i * 12, cheight - (--capYPositionArray[i]), meterWidth, capHeight); //则使用前一次保存的值来绘制顶端
} else {
ctx.fillRect(i * 12, cheight - value, meterWidth, capHeight); //否则使用当前值直接绘制
capYPositionArray[i] = value;
}
//开始绘制频谱条
ctx.fillStyle = gradient;
ctx.fillRect(i * 12, cheight - value + capHeight, meterWidth, cheight);
}
requestAnimationFrame(drawRect); //绘图循环,持续到音乐播放结束,无法再获取音频数据
};
requestAnimationFrame(drawRect);
}
最后,在加载脚本的时候进行调用:
window.onload=function()
{
var CA=new CanvasAudio();
CA.initial();
CA.addEventListner();
CA.drawAudio();
};
效果
在这里只能截图了,具体怎么样可以自己去尝试一番~
除非注明,麦麦小家文章均为原创,转载请以链接形式标明本文地址。