使用Web Audio API绘制音波图
摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音、音效、平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制。
PS:本例子使用ES6编程,最好在新版chrome中运行。
一、前端文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>audiogram</title> </head> <body> <canvas id="canvas" width="400" height="100"></canvas> <button class="play" style="width: 50px;height: 50px;">开始</button> <button class="stop" style="width: 50px;height: 50px;">停止</button> </body> <script type="module" src="Main.js"></script> </html>
PS:浏览器加载ES6模板使用<script>标签时,要加入type = “module”属性,浏览器会默认异步加载,并知道Main.js是一个ES6模块。
二、创建Audio类
//audit类,用于加载,播放音乐 export class Audio { //单例 static getInstance() { if (!Audio.instance) { Audio.instance = new Audio(); } return Audio.instance; } //构造函数 constructor() { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); } //加载资源 getData() { this.analyser = this.ctx.createAnalyser(); //从元素创建媒体节点 可以直接将audio元素传入后创建,就不用request来请求资源 //this.source = this.ctx.createMediaElementSource(audio); this.source = this.ctx.createBufferSource(); this.source.loop = true; //创建AnalyserNode,用来显示音频时间和频率的数据 this.source.connect(this.analyser); //最后连接到音频渲染设备,发出声音 this.analyser.connect(this.ctx.destination); //获取频率 this.freqs = new Uint8Array(this.analyser.frequencyBinCount); //请求资源 let request = new XMLHttpRequest(); request.open('get', 'res/bgm.mp3', true); //responseType属性须设置为arraybuffer request.responseType = 'arraybuffer'; //decodeAudioData方法用于解码音频文件 request.onload = () => { var audioData = request.response; this.ctx.decodeAudioData(audioData, (buffer) => { //将解码后的音频文件作为声音的来源 this.source.buffer = buffer; //立即开始播放声音 this.source.start(0); }, (e) => { "Error with decoding audio data" + e.error }); }; request.send(); } }
三、创建Main类
import {Audio} from "./js/Audio.js"; //用于控制整个页面的流程 class Main { constructor() { //获取audio实例 this.audio = Audio.getInstance(); this.init(); } //初始化 init() { //初始化按钮 this.play = document.querySelector('.play'); this.stop = document.querySelector('.stop'); //确保加载完资源后开始输出 let promise = new Promise((resolve) => { this.audio.getData(); resolve(); }); promise.then(() => { this.initCanvas(); this.outPut() }); //播放按钮 this.play.onclick = () => { this.audio.ctx.resume(); this.outPut(); this.play.setAttribute('disabled', 'disabled'); } //停止按钮 this.stop.onclick = () => { this.audio.ctx.suspend(); //this.audio.source.stop(0);使用stop停止时无法恢复,需要重载资源 cancelAnimationFrame(this.timer); this.play.removeAttribute('disabled'); } } //初始化canvas initCanvas() { let cv = document.querySelector('#canvas'); this.canvasWidth = cv.width; this.canvasHeight = cv.height; this.canvas = cv.getContext("2d"); this.canvas.translate(0.5, 0.5); this.outPutData = this.audio.freqs; } //输出图像 outPut() { var height = this.canvasHeight; var width = this.canvasWidth; var outPutData = this.outPutData; var length = outPutData.length; this.audio.analyser.getByteFrequencyData(outPutData); //将缓冲区的数据绘制到Canvas上 this.canvas.clearRect(-0.5, -0.5, width, height); this.canvas.beginPath(), this.canvas.moveTo(0, height); for (var i = 0; i < width; i++) this.canvas.lineTo(i, height - height * outPutData[Math.round(length * i / width)] / 255); this.canvas.lineTo(i, height), this.canvas.fill(); //请求下一帧 this.timer = requestAnimationFrame(() => { this.outPut() }); } } new Main();
PS:在ES6中使用箭头函数时,箭头函数中的this指向Main,真的超级好用。
注意:使用chrome打开时会出现 Origin 'null' is therefore not allowed access.这是由于在本地html页面ajax请求本地或者局域网server的资源时,被浏览器禁止了,跨域请求会带来安全隐患,因此谷歌浏览器对此做出了限制。
在服务端我们可以设置response.setHeader("Access-Control-Allow-Origin: *")允许这么做,如果是客户端的话我们可以右键chrome快捷方式—>属性->目标-> 将"--allow-file-access-from-files"添加至最后重启浏览器即可。
工程目录结构:
└── audiogram
├── js
│ └── Audio.js
├──
res
│ └── bgm.mp3
├ ─ ─ index.html
└ ─ ─ Main.js
效果图: