使用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
 
效果图:

 

posted @ 2018-04-18 21:14  油炸土豆  阅读(2097)  评论(0编辑  收藏  举报