前端音頻可視化Web?Audio實現(xiàn)示例詳解
背景
最近聽音樂的時候,看到各種動效,突然好奇這些音頻數(shù)據(jù)是如何獲取并展示出來的,于是花了幾天功夫去研究相關的內(nèi)容,這里只是給大家一些代碼實例,具體要看懂、看明白,還是建議大家大家結合相關API文檔來閱讀這篇文章。
參考資料地址:Web Audio API - Web API 接口參考 | MDN (mozilla.org)



實現(xiàn)思路
首先畫肯定是用canvas去畫,關于音頻的相關數(shù)據(jù)(如頻率、波形)如何去獲取,需要去獲取相關audio的DOM 或通過請求處理去拿到相關的音頻數(shù)據(jù),然后通過Web Audio API 提供相關的方法來實現(xiàn)。(當然還要考慮要音頻請求跨域的問題,留在最后。)
一個簡單而典型的 web audio 流程如下(取自MDN):
- 創(chuàng)建音頻上下文
- 在音頻上下文里創(chuàng)建源 — 例如
<audio>, 振蕩器,流 - 創(chuàng)建效果節(jié)點,例如混響、雙二階濾波器、平移、壓縮
- 為音頻選擇一個目的地,例如你的系統(tǒng)揚聲器
連接源到效果器,對目的地進行效果輸出

實現(xiàn)
一、頻率圖
實現(xiàn)第一種類型,首先我們需要通過fetch或xhr來獲取一個線上音頻的數(shù)據(jù),這里以fetch為例;
//創(chuàng)建一個音頻上下文、考慮兼容性問題
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
//添加一個音頻源節(jié)點
let source = audioCtx.createBufferSource();
//res.arrayBuffer是將數(shù)據(jù)轉(zhuǎn)換為arrayBuffer格式
fetch(url).then((res) => res.arrayBuffer()).then((res) => {
//decodeAudioData是將arrayBuffer格式數(shù)據(jù)轉(zhuǎn)換為audioBuffer
audioCtx.decodeAudioData(res).then((buffer) => {
// decodeAudioData解碼完成后,返回一個AudioBuffer對象
// 繪制音頻波形圖
draw(buffer);
// 連接音頻源
source.buffer = buffer;
source.connect(audioCtx.destination);
// 音頻數(shù)據(jù)處理完畢
});
});

需要明白的是,source.connect(audioCtx.destination)是將音頻源節(jié)點鏈接到輸出設備,否則會沒聲音哦。那么現(xiàn)在有了數(shù)據(jù)、我們只需要通過canvas將數(shù)據(jù)畫出來即可。
function draw(buffer) {
// buffer.numberOfChannels返回音頻的通道數(shù)量,1即為單聲道,2代表雙聲道。這里我們只取一條通道的數(shù)據(jù)
let data = [];
let originData = buffer.getChannelData(0);
// 存儲所有的正數(shù)據(jù)
let positives = [];
// 存儲所有的負數(shù)據(jù)
let negatives = [];
// 先每隔50條數(shù)據(jù)取1條
for (let i = 0; i < originData.length; i += 50) {
data.push(originData[i]);
}
// 再從data中每10條取一個最大值一個最小值
for (let j = 0, len = data.length / 10; j < len; j++) {
let temp = data.slice(j * 10, (j + 1) * 10);
positives.push(Math.max(...temp));
negatives.push(Math.min(...temp));
}
if (canvas.getContext) {
let ctx = canvas.getContext("2d");
canvas.width = positives.length;
let x = 0;
let y = 75;
let offset = 0;
var grd = ctx.createLinearGradient(0, 0, canvas.width, 0);
// 為漸變添加顏色,參數(shù)1表示漸變開始和結束之間的位置(用0至1的占比表示),參數(shù)2位顏色
grd.addColorStop(0, "yellow");
grd.addColorStop(0.5, "red");
grd.addColorStop(1, "blue");
ctx.fillStyle = grd;
ctx.beginPath();
ctx.moveTo(x, y);
// 橫坐標上方繪制正數(shù)據(jù),下方繪制負數(shù)據(jù)
// 先從左往右繪制正數(shù)據(jù)
// x + 0.5是為了解決canvas 1像素線條模糊的問題
for (let k = 0; k < positives.length; k++) {
ctx.lineTo(x + k + 0.5, y - 50 * positives[k]);
}
// 再從右往左繪制負數(shù)據(jù)
for (let l = negatives.length - 1; l >= 0; l--) {
ctx.lineTo(x + l + 0.5, y + 50 * Math.abs(negatives[l]));
}
// 填充圖形
ctx.fill();
}
}
[參考文章](Web Audio - 繪制音頻圖譜
二、實時頻率圖
實現(xiàn)第二種類型,獲取實時頻率,用到的API與第一種有區(qū)別,但流程一直,都是通過一個音頻源節(jié)點通過連接達到效果。只不過在連接的中間加入了一個分析器analyser,在將分析器連接到輸出設備。
const audio =document.querySelector('audio')
//解決音頻跨域問題
audio.crossOrigin ='anonymous'
const canvas =document.querySelector('canvas')
const ctx=canvas.getContext("2d")
function initCanvas(){
//初始化canvas
canvas.width=window.innerWidth*devicePixelRatio
canvas.height=(window.innerHeight/2)*devicePixelRatio
}
initCanvas()
//將數(shù)據(jù)提出來
let dataArray,analyser;
//播放事件
audio.onplay=function(){
//創(chuàng)建一個音頻上下文實例
const audioCtx=new (window.AudioContext || window.webkitAudioContext)();
//添加一個音頻源節(jié)點
const source=audioCtx.createMediaElementSource(audio);
//分析器節(jié)點
analyser=audioCtx.createAnalyser();
//fft分析器 越大 分析越細
analyser.fftSize=512
//創(chuàng)建一個無符號字節(jié)的數(shù)組
dataArray=new Uint8Array( analyser.frequencyBinCount);
//音頻源節(jié)點 鏈接分析器
source.connect(analyser)
//分析器鏈接輸出設備
analyser.connect(audioCtx.destination,)
}
那么接下來至于怎么把數(shù)據(jù)畫出來,就憑大家的想法了。
requestAnimationFrame(draw)
//
const {width ,height}=canvas;
ctx.clearRect(0,0,width,height)
//分析器節(jié)點分析出的數(shù)據(jù)到數(shù)組中
ctx.fillStyle='#78C5F7'
ctx.lineWidth = 2;
ctx.beginPath();
//getByteFrequencyData,分析當前音頻源的數(shù)據(jù) 裝到dataArray數(shù)組中去
//獲取實時數(shù)據(jù)
analyser.getByteFrequencyData(dataArray)
// console.log(dataArray);
const len =dataArray.length;
const barWidth=width/len;
let x=0;
for(let i=0;i<len;i++){
const data=dataArray[i];
const barHeight=data/255*height;
// ctx.fillRect(x,y,barWidth,height)
let v = dataArray[i] / 128.0;
let y = v * height/2;
if(i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
x += barWidth;
}
// ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
}
draw();
關于請求音頻跨域問題解決方案
給獲取的audio DOM添加一條屬性即可
audio.crossOrigin ='anonymous'
或者直接在 aduio標簽中 加入 crossorigin="anonymous"
總結
雖然現(xiàn)在已經(jīng)有很多開源的對于音頻相關的庫,但如果真正的想要去了解,去學習音頻相關的東西。必須要去深入學習相關的Web Audio API,當然這里只是用了其中兩種的方法去實現(xiàn)Web Audio去實現(xiàn)可視化,算是一個基礎入門,對于文中的createBufferSource,createMediaElementSource,createAnalyser,AudioContext,arrayBuffer,decodeAudioData等等相關的API都需要去了解,在可視化方面,還有多種多樣的方式去繪制動畫,如WebGL。對音頻的處理也不只是在可視化方面。
以上就是前端音頻可視化Web Audio實現(xiàn)示例詳解的詳細內(nèi)容,更多關于Web Audio音頻可視化的資料請關注腳本之家其它相關文章!
相關文章
javascript 10進制和62進制的相互轉(zhuǎn)換
本節(jié)主要介紹了javascript 10進制和62進制的相互轉(zhuǎn)換,需要的朋友可以參考下2014-07-07
js實現(xiàn)html table 行,列鎖定的簡單實例
下面小編就為大家?guī)硪黄猨s實現(xiàn)html table 行,列鎖定的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10

