使用HTML5 API(AudioContext)实现可视化频谱效果
如今的HTML5技术正让网页变得越来越强大,通过其Canvas标签与AudioContext对象可以轻松实现之前在Flash或Native App中才能实现的频谱指示器的功能。
Demo: Cyandev Works - HTML5 Audio Visualizing
The AudioContext interface represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode.
根据MDN的文档,AudioContext是一个专门用于音频处理的接口,并且工作原理是将AudioContext创建出来的各种节点(AudioNode)相互连接,音频数据流经这些节点并作出相应处理。
创建AudioContext对象
由于浏览器兼容性问题,我们需要为不同浏览器配置AudioContext,在这里我们可以用下面这个表达式来统一对AudioContext的访问。
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext(); //实例化AudioContext对象
附. 浏览器兼容性
| 浏览器 | Chrome | Firefox | IE | Opera | Safari | 
|---|---|---|---|---|---|
| 支持版本 | 10.0 | 25.0 | 不支持 | 15.0 | 6.0 | 
当然,如果浏览器不支持的话,我们也没有办法,用IE的人们我想也不需要这些效果。但最佳实践是使用的时候判断一下上面声明的变量是否为空,然后再做其他操作。
解码音频文件
读取到的音频文件是二进制类型,我们需要让AudioContext先对其解码,然后再进行后续操作。
audioContext.decodeAudioData(binary, function(buffer) { ... });
方法decodeAudioData被调用后,浏览器将开始解码音频文件,这需要一定时间,我们应该让用户知道浏览器正在解码,解码成功后会调用传进去的回调函数,decodeAudioData还有第三个可选参数是在解码失败时调用的,我们这里就先不实现了。
创建音频处理节点
这是最关键的一步,我们需要两个音频节点:
AudioBufferSourceNode
AnalyserNode
前者是用于播放解码出来的buffer的节点,而后者是用于分析音频频谱的节点,两个节点顺次连接就能完成我们的工作。
创建AudioBufferSourceNode
var audioBufferSourceNode;
audioBufferSourceNode = audioContext.createBufferSource();
创建AnalyserNode
var analyser;
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
上面的fftSize是用于确定FFT大小的属性,那FFT是什么高三的博主还不知道,其实也不需要知道,总之最后获取到的数组长度应该是fftSize值的一半,还应该保证它是以2为底的幂。
连接节点
audioBufferSourceNode.connect(analyser);
analyser.connect(audioContext.destination);
上面的audioContext.destination是音频要最终输出的目标,我们可以把它理解为声卡。所以所有节点中的最后一个节点应该再连接到audioContext.destination才能听到声音。
播放音频
所有工作就绪,在解码完毕时调用的回调函数中我们就可以开始播放了。
audioBufferSourceNode.buffer = buffer; //回调函数传入的参数
audioBufferSourceNode.start(0); //部分浏览器是noteOn()函数,用法相同
参数代表播放起点,我们这里设置为0意味着从头播放。
文件读取
HTML5支持文件选择、读取的特性,我们利用这个特性可以实现不上传,即播放的功能。
HTML标签
在你的页面中找个位置插入:
<input id="fileChooser" type="file" />
Js逻辑
var file;
var fileChooser = document.getElementById('fileChooser');
fileChooser.onchange = function() {
    if (fileChooser.files[0]) {
        file = fileChooser.files[0];
        // Do something with 'file'...
    }
}
使用FileReader异步读取文件
var fileContent;
var fileReader = new FileReader();
fileReader.onload = function(e) {
    fileContent = e.target.result;
    // Do something with 'fileContent'...
}
fileReader.readAsArrayBuffer(file);
其实这里的fileContent就是上面AudioContext要解码的那个binary,至此两部分的工作就可以连起来了。
WARNING:
Chrome或Firefox浏览器的跨域访问限制会使FileReader在本地失效,Chrome用户可在调试时添加命令行参数:
chrome.exe --disable-web-security
Canvas绘制频谱
这一部分我不打算详细叙述,就提几个重点。
AnalyserNode数据解析
在绘制之前通过下面的方法获取到AnalyserNode分析的数据:
var dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
数组中每个元素是从0到fftSize属性值的数值,这样我们通过一定比例就能控制能量条的高度等状态。
requestAnimationFrame的使用
要使动画动起来,我们需要不断重绘Canvas标签里的内容,这就需要requestAnimationFrame这个函数了,它可以帮你以60fps的帧率绘制动画。
使用方法:
var draw = function() {
    // ...
    window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
这段代码应该不难理解,就是一个类似递归的调用,但不是递归,有点像Android中的postInvalidate
实例代码
贴上我写的一段绘制代码:
var render = function() {
    ctx = canvas.getContext("2d");
    ctx.strokeStyle = "#00d0ff";
    ctx.lineWidth = 2;
    ctx.clearRect(0, 0, canvas.width, canvas.height); //清理画布
    var dataArray = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(dataArray);
    var step = Math.round(dataArray.length / 60); //采样步长
    for (var i = 0; i < 40; i++) {
        var energy = (dataArray[step * i] / 256.0) * 50;
        for (var j = 0; j < energy; j++) {
            ctx.beginPath();
            ctx.moveTo(20 * i + 2, 200 + 4 * j);
            ctx.lineTo(20 * (i + 1) - 2, 200 + 4 * j);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(20 * i + 2, 200 - 4 * j);
            ctx.lineTo(20 * (i + 1) - 2, 200 - 4 * j);
            ctx.stroke();
        }
        ctx.beginPath();
        ctx.moveTo(20 * i + 2, 200);
        ctx.lineTo(20 * (i + 1) - 2, 200);
        ctx.stroke();
    }
    window.requestAnimationFrame(render);
}
OK,大致就是这样,之后可以加一些css样式,完善一下业务逻辑,这里就不再阐释了。最后贴上整理好的全部代码:
HTML 部分
<html>
    <head>
        <title>HTML5 Audio Visualizing</title>
        <style type="text/css">
            body {
                background-color: #222222
            }
            input {
                color: #ffffff
            }
            #wrapper {
                display: table;
                width: 100%;
                height: 100%;
            }
            #wrapper-inner {
                display: table-cell;
                vertical-align: middle;
                padding-left: 25%;
                padding-right: 25%;
            }
            #tip {
                color: #fff;
                opacity: 0;
                transition: opacity 1s;
                -moz-transition: opacity 1s;
                -webkit-transition: opacity 1s;
                -o-transition: opacity 1s;
            }
            #tip.show {
                opacity: 1
            }
        </style>
        <script type="text/javascript" src="./index.js"></script>
    </head>
    <body>
        <div id="wrapper">
            <div id="wrapper-inner">
                <p id="tip">Decoding...</p>
                <input id="fileChooser" type="file" />
                <br>
                <canvas id="visualizer" width="800" height="400">Your browser does not support Canvas tag.</canvas>
            </div>
        </div>
    </body>
</html>
Js部分
var AudioContext = window.AudioContext || window.webkitAudioContext; //Cross browser variant.
var canvas, ctx;
var audioContext;
var file;
var fileContent;
var audioBufferSourceNode;
var analyser;
var loadFile = function() {
    var fileReader = new FileReader();
    fileReader.onload = function(e) {
        fileContent = e.target.result;
        decodecFile();
    }
    fileReader.readAsArrayBuffer(file);
}
var decodecFile = function() {
    audioContext.decodeAudioData(fileContent, function(buffer) {
        start(buffer);
    });
}
var start = function(buffer) {
    if(audioBufferSourceNode) {
        audioBufferSourceNode.stop();
    }
    audioBufferSourceNode = audioContext.createBufferSource();
    audioBufferSourceNode.connect(analyser);
    analyser.connect(audioContext.destination);
    audioBufferSourceNode.buffer = buffer;
    audioBufferSourceNode.start(0);
    showTip(false);
    window.requestAnimationFrame(render);
}
var showTip = function(show) {
    var tip = document.getElementById('tip');
    if (show) {
        tip.className = "show";
    } else {
        tip.className = "";
    }
}
var render = function() {
    ctx = canvas.getContext("2d");
    ctx.strokeStyle = "#00d0ff";
    ctx.lineWidth = 2;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    var dataArray = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(dataArray);
    var step = Math.round(dataArray.length / 60);
    for (var i = 0; i < 40; i++) {
        var energy = (dataArray[step * i] / 256.0) * 50;
        for (var j = 0; j < energy; j++) {
            ctx.beginPath();
            ctx.moveTo(20 * i + 2, 200 + 4 * j);
            ctx.lineTo(20 * (i + 1) - 2, 200 + 4 * j);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(20 * i + 2, 200 - 4 * j);
            ctx.lineTo(20 * (i + 1) - 2, 200 - 4 * j);
            ctx.stroke();
        }
        ctx.beginPath();
        ctx.moveTo(20 * i + 2, 200);
        ctx.lineTo(20 * (i + 1) - 2, 200);
        ctx.stroke();
    }
    window.requestAnimationFrame(render);
}
window.onload = function() {
    audioContext = new AudioContext();
    analyser = audioContext.createAnalyser();
    analyser.fftSize = 256;
    var fileChooser = document.getElementById('fileChooser');
    fileChooser.onchange = function() {
        if (fileChooser.files[0]) {
            file = fileChooser.files[0];
            showTip(true);
            loadFile();
        }
    }
    canvas = document.getElementById('visualizer');
}												
											使用HTML5 API(AudioContext)实现可视化频谱效果的更多相关文章
- HTML5音频可视化频谱跳动代码
		
今天学习到用canvas来写 HTML5音频可视化频谱跳动代码 将代码在此做一总结: <!DOCTYPE html> <html lang="en"> ...
 - Three.js + HTML5 Audio API 打造3D音乐频谱,Let’s ROCK!
		
继续玩味之前写的音乐频谱作品,将原来在Canvas标签上的 作图利用Three.js让它通过WebGL呈现,这样就打造出了一个全立体感的频谱效果了. 项目详情及源码 项目GitHub地址:https: ...
 - HTML5 API 是什么
		
HTML5 API 是什么 一.总结 1.html5有很多好的api可以用:例如绘图的canvas,获取地理位置的,获取手机电池信息的等等,后面用的时候可以百度 2.html5 API是什么:html ...
 - 经典!HTML5 Canvas 模拟可撕裂布料效果
		
这是一个模拟可撕裂布料效果的 HTML5 Canvas 应用演示,效果逼真.你会看到,借助 Canvas 的强大绘图和动画功能,只需很少的代码就能实现让您屏息凝神的效果. 温馨提示:为保证最佳的效果, ...
 - 基于html5页面滚动背景图片动画效果
		
基于html5页面滚动背景图片动画效果是一款带索引按钮的页面滚动动画特效代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <div id="fullpage&q ...
 - HTML5 API 之 history
		
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
 - HTML5 API's (Application Programming Interfaces)
		
New HTML5 API's (Application Programming Interfaces) The most interesting new API's are: HTML Geoloc ...
 - Web开发者的最爱 5个超实用的HTML5 API
		
摘要:毫无疑问,HTML5已经成为当今最流行的一门技术,尤其是Web开发者们对HTML5的兴趣是日趋渐浓.HTML5的许多功能也都能在现代浏览器中得以实现.然而,作为开发者,除了关注HTML5的功能和 ...
 - html5 +css3 点击后水波纹扩散效果 兼容移动端
		
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
 
随机推荐
- Ultra UltraEdit中取消提示:你要转换 File 为 DOS 格式吗?
			
Ultra Edit中取消提示:文件可能不是DOS格式,你要转换 File 为 DOS 格式吗? UE 提示 取消取消这个提示: 高级 -> 配置 -> 文件处理 -> DOS/UN ...
 - Delphi应用程序的调试(十)调试器选项(在IDE中不要使用异常)
			
可在两个级别上设置调试选项:工程级和环境级.在前面的讲解中讲解了工程级调试选项,通过主菜单[Project | Options…]打开如下对话框: 可在Debugger Options对话框中设置全局 ...
 - 海美迪Q5智能机顶盒的蓝牙功能
			
虽然在硬件上,海美迪Q5智能机顶盒没有集成蓝牙模块,但是在软件系统上,Q5是支持蓝牙驱动的,所以它可以通过USB外接蓝牙适配器来扩展出蓝牙功能,简单来说,就是你另外买个蓝牙适配器,插到Q5上面,就能用 ...
 - RMQ——忠诚题解
			
题目:忠诚 描述: [题目描述] 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满 意.但是由于一些人的 ...
 - SRM475 - SRM479(1-250pt,500pt)
			
SRM 475 DIV1 300pt 题意:玩游戏.给一个棋盘,它有1×n(1行n列,每列标号分别为0,1,2..n-1)的格子,每个格子里面可以放一个棋子,并且给定一个只含三个字母WBR,长度为n的 ...
 - springmvc实现REST中的GET、POST、PUT和DELETE
			
spring mvc 支持REST风格的请求方法,GET.POST.PUT和DELETE四种请求方法分别代表了数据库CRUD中的select.insert.update.delete,下面演示一个简单 ...
 - windows 编程 —— 子窗口类别化(Window Subclassing)
			
对于子窗口控件,有时我们可能会想要获取子窗口的某些消息,比如在一个主窗口下有三个按钮,如果想要实现使用键盘Tab或者Shift-Tab键来使焦点切换于不同按钮之间,这时就可以使用子窗口类别化(Wind ...
 - 《JAVA课程设计》实训第四天——《猜猜看》游戏
			
第四天,本来想进一步去改进<猜猜看>游戏的.可是非常多问题都不理解.也不熟悉怎么去弄到连接数据库.统计猜对次数,所以并没有进行再多的改动. 基本上就是这种执行结果了 import java ...
 - Php面向对象 – 单例模式
			
Php面向对象 – 单例模式 保证类仅仅有一个实例 1. 怎样能够解决一个类能够被无限地实例化? New,就能实例化一次,怎么去限制,用户不能无限次地new? 将构造方法私有化.全部外部的new ...
 - Zend Studio 10正式版破解(2013-02-26更新)
			
Zend Studio 10正式版注册破解(2013-02-26完成更新) 1.以下方法仅供技术交流学习,请勿非法使用,如长期使用请支持购买正版. 2.若你还没有最新安装程序? ZendStudio ...