使用Web Audio API实现简单的音频可视化

前言

以前恰好看到Web Audio API方面的内容,所以用了相关api作了个音频可视化的页面。实现:javascript

  • 音频播放/暂停
  • 音频声量控制
  • 音频立体声控制
  • 音频频率可视化
  • 音频切换

预备知识

Web Audio API中一个关键的对象就是音频上下文(AudioContext),能够类比canvas context,在AudioContext咱们进行相关的操做。音频处理的一个典型流程为:java

  • 建立音频上下文(AudioContext)
  • 在音频上下文里建立源 — 例如 <audio>(HTMLAudioElement), 音频数据(Ajax 获取的 AudioBufferSourceNode ), 流(MediaStreamAudioSourceNode)
  • 建立效果节点,例如混响、双二阶滤波器、平移、压缩
  • 为音频选择一个目的地(destination),例如你的系统扬声器
  • 链接(connect)源到效果器,对目的地进行效果输出

description

  • 具体创建过程
// 1. 建立上下文
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// 2. 获取音频源(MediaElementAudioSourceNode)
// 这里音频源可使用 OscillatorNode 建立,也使用麦克风源等
var audioElement = document.querySelector('audio');
var track = audioContext.createMediaElementSource(audioElement);
// ...这里咱们已经能够将声音链接到系统扬声器
// track.connect(audioContext.destination);
// 3. 须要先启动音频环境,不然可能会提示须要手势触发的警告
// resume() 和 suspend() 返回的都是异步对象,最好使用then等待异步处理结束
if (audioContext.state === 'suspended') {
    audioContext.resume();
}
// 4. 播放控制
audioElement.play();
audioElement.pause();
// 5. 声音控制
const gainNode = audioContext.createGain();
track.connect(gainNode).connect(audioContext.destination);
// 6. 立体声平移控制
const pannerOptions = { pan: 0 };
const panner = new StereoPannerNode(audioContext, pannerOptions);
// 官网使用构造器方法,可是在Edge中会提示错误,可使用如下的工厂方法代替
this.panner = this.audioContext.createStereoPanner();
this.panner.pan.value = 0.0;
// 7. 链接到系统扬声器
track.connect(gainNode).connect(panner).connect(audioContext.destination)
复制代码

track

实现细节

  • 可视化
// 1. 建立AnalyserNode对象
var analyser = audioContext.createAnalyser();
// 2. 获取柱形图须要的数量,初始化一个dataArray
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
// 3. 得到绘制输出
var canvasCtx = canvasElement.getContext('2d');
// 4. 得到 canvas 相关参数
var WIDTH = canvasElement.width;
var HEIGHT = canvasElement.height;
// 5. 定义须要的条形数目,可本身定制
count = dataArray.length;
// 6. 绘制canvas
function draw () {
    canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
    analyser.getByteFrequencyData(dataArray);
    let value = 0,
        step = Math.round(dataArray.length / count), // 得到绘制间隔
        x = 0,
        y = 0,
        lineWidth = canvasCtx.lineWidth = WIDTH / count, // 描边宽度
        index = count;
    canvasCtx.strokeStyle = "#fff"; // 描边颜色
    while (index) {
        value = dataArray[index * step + step]; // 描边高度相关
        x = index * lineWidth;
        y = HEIGHT - value * 1.5;
        canvasCtx.beginPath(); // 开始绘制
        canvasCtx.moveTo(x, HEIGHT); // 从坐标轴x点开始绘制
        canvasCtx.lineTo(x, y); // 到须要的高度结束
        canvasCtx.stroke();
        index -= 2; // 调整绘制间隔
    }
    requestAnimationFrame(() => draw());
};
复制代码
  • 播放控制
function playHandler () {
    if (playState === false) {
        audioElement.play();
        playState = true;
        playButton.dataset.playing = 'true';
    } else {
        audioElement.pause();
        playState = false;
        playButton.dataset.playing = 'false';
    }
}
复制代码
  • 音频切换

若是多个音频关联到一个 GainNode 对象,切换前注意断开链接 git

咱们经过 audioContext.createMediaElementSource(audioElement) 已经将上下文对象与一个 HTMLAudioElement 对象关联,咱们后面进行操做能够经过切换这个媒体对象的src实现(或者从新new一个AudioContext与新的媒体对象关联,不推荐)github

// 假如咱们以前有这样的链接
function setTrack () {
    track.connect(gainNode)
        .connect(panner)
        .connect(analyser)
        .connect(audioContext.destination);
}
// 那么咱们进行音频切换时要进行以下操做
function changeAudio (index) {
    let idx = index || 0;
    if (playList.length <= 0 || idx > playList.length) return;
        playState = false;
        track && track.disconnect();
        gainNode && gainNode.disconnect();
        panner && panner.disconnect();
        analyser && analyser.disconnect();
        audioElement.src = playList[idx].src;
        audioElement.load();
        _enableControls();
}
复制代码
  • 具体实现效果

经常使用API

  • AudioContext.currentTime 以双精度浮点型数字返回硬件调用的秒数 (readonly)
  • AudioContext.state 返回AudioContext当前状态 (readonly)
  • AnalyserNode.frequencyBinCount 一个无符号长整形 (unsigned long) 的值, 值为fftSize的一半。这一般等于将要用于可视化的数据值的数量。
  • AudioContext.createMediaElementSource() 建立一个 MediaElementAudioSourceNode 接口来关联 HTMLMediaElement . 这能够用来播放和处理来自<video><audio> 元素的音频
  • AudioContext.resume() 从新启动一个已被暂停的音频环境
  • AudioContext.suspend() 暂停音频内容的进度.暂时中止音频硬件访问和减小在过程当中的CPU/电池使用
  • AudioContext.close() 关闭一个音频环境, 释听任何正在使用系统资源的音频
  • AudioContext.decodeAudioData() 从ArrayBuffer对象中异步解码音频文件
  • AudioContext.createGain() 建立一个GainNode,它能够控制音频的总音量
  • AudioContext.createPanner() 建立一个PannerNode, 它为音源建立一个3D音源环境
  • AudioContext.createAnalyser() 建立一个AnalyserNode,它能够用来显示音频时间和频率的数据
  • AudioContext.createStereoPanner() 建立一个使用立体声的音频源 StereoPannerNode
  • AudioContext.createAnalyser() 建立一个AnalyserNode,能够用来获取音频时间和频率数据,以及实现数据可视化。
  • AnalyserNode.getByteFrequencyData() 将当前频域数据拷贝进Uint8Array数组
  • 这里注意closed状态是不可逆的(close后要从新new),三种状态操做都是异步操做
    new AudioContext()
          |
          V
    +----------+                 +------------+
    | running  | -- suspend() -> | suspended  |
    |          | <- resume() --- |            |
    +----------+                 +------------+
          |                              |
          | close()                      | close()
          +------------------------------+
          |
          V
    +-----------+
    |  closed   |
    +-----------+
    复制代码

不兼容IE
项目预览codepen
项目地址githubweb

参考资料

相关文章
相关标签/搜索