使用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

參考資料

相關文章
相關標籤/搜索