這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 背景 最近聽音樂的時候,看到各種動效,突然好奇這些音頻數據是如何獲取並展示出來的,於是花了幾天功夫去研究相關的內容,這裡只是給大家一些代碼實例,具體要看懂、看明白,還是建議大家大家結合相關API文檔來閱讀這篇文章。 參考資料地址:Web ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
背景
最近聽音樂的時候,看到各種動效,突然好奇這些音頻數據是如何獲取並展示出來的,於是花了幾天功夫去研究相關的內容,這裡只是給大家一些代碼實例,具體要看懂、看明白,還是建議大家大家結合相關API文檔來閱讀這篇文章。
參考資料地址:Web Audio API - Web API 介面參考 | MDN (mozilla.org)
實現思路
首先畫肯定是用canvas去畫,關於音頻的相關數據(如頻率、波形)如何去獲取,需要去獲取相關audio的DOM 或通過請求處理去拿到相關的音頻數據,然後通過Web Audio API 提供相關的方法來實現。(當然還要考慮要音頻請求跨域的問題,留在最後。)
一個簡單而典型的 web audio 流程如下(取自MDN):
- 創建音頻上下文
- 在音頻上下文里創建源 — 例如
<audio>
, 振蕩器,流 - 創建效果節點,例如混響、雙二階濾波器、平移、壓縮
- 為音頻選擇一個目的地,例如你的系統揚聲器
- 連接源到效果器,對目的地進行效果輸出
實現
一、頻率圖
實現第一種類型,首先我們需要通過fetch或xhr來獲取一個線上音頻的數據,這裡以fetch為例;
//創建一個音頻上下文、考慮相容性問題 let audioCtx = new (window.AudioContext || window.webkitAudioContext)(); //添加一個音頻源節點 let source = audioCtx.createBufferSource(); //res.arrayBuffer是將數據轉換為arrayBuffer格式 fetch(url).then((res) => res.arrayBuffer()).then((res) => { //decodeAudioData是將arrayBuffer格式數據轉換為audioBuffer audioCtx.decodeAudioData(res).then((buffer) => { // decodeAudioData解碼完成後,返回一個AudioBuffer對象 // 繪製音頻波形圖 draw(buffer); // 連接音頻源 source.buffer = buffer; source.connect(audioCtx.destination); // 音頻數據處理完畢 }); });
需要明白的是,source.connect(audioCtx.destination)是將音頻源節點鏈接到輸出設備,否則會沒聲音哦。那麼現在有了數據、我們只需要通過canvas將數據畫出來即可。
function draw(buffer) { // buffer.numberOfChannels返迴音頻的通道數量,1即為單聲道,2代表雙聲道。這裡我們只取一條通道的數據 let data = []; let originData = buffer.getChannelData(0); // 存儲所有的正數據 let positives = []; // 存儲所有的負數據 let negatives = []; // 先每隔50條數據取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); // 為漸變添加顏色,參數1表示漸變開始和結束之間的位置(用0至1的占比表示),參數2位顏色 grd.addColorStop(0, "yellow"); grd.addColorStop(0.5, "red"); grd.addColorStop(1, "blue"); ctx.fillStyle = grd; ctx.beginPath(); ctx.moveTo(x, y); // 橫坐標上方繪製正數據,下方繪製負數據 // 先從左往右繪製正數據 // x + 0.5是為瞭解決canvas 1像素線條模糊的問題 for (let k = 0; k < positives.length; k++) { ctx.lineTo(x + k + 0.5, y - 50 * positives[k]); } // 再從右往左繪製負數據 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 - 繪製音頻圖譜 - 掘金 (juejin.cn))
二、實時頻率圖
實現第二種類型,獲取實時頻率,用到的API與第一種有區別,但流程一直,都是通過一個音頻源節點通過連接達到效果。只不過在連接的中間加入了一個分析器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() //將數據提出來 let dataArray,analyser; //播放事件 audio.onplay=function(){ //創建一個音頻上下文實例 const audioCtx=new (window.AudioContext || window.webkitAudioContext)(); //添加一個音頻源節點 const source=audioCtx.createMediaElementSource(audio); //分析器節點 analyser=audioCtx.createAnalyser(); //fft分析器 越大 分析越細 analyser.fftSize=512 //創建一個無符號位元組的數組 dataArray=new Uint8Array( analyser.frequencyBinCount); //音頻源節點 鏈接分析器 source.connect(analyser) //分析器鏈接輸出設備 analyser.connect(audioCtx.destination,) }那麼接下來至於怎麼把數據畫出來,就憑大家的想法了。
requestAnimationFrame(draw) // const {width ,height}=canvas; ctx.clearRect(0,0,width,height) //分析器節點分析出的數據到數組中 ctx.fillStyle='#78C5F7' ctx.lineWidth = 2; ctx.beginPath(); //getByteFrequencyData,分析當前音頻源的數據 裝到dataArray數組中去 //獲取實時數據 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"
總結
雖然現在已經有很多開源的對於音頻相關的庫,但如果真正的想要去瞭解,去學習音頻相關的東西。必須要去深入學習相關的Web Audio API,當然這裡只是用了其中兩種的方法去實現Web Audio去實現可視化,算是一個基礎入門,對於文中的createBufferSource,createMediaElementSource,createAnalyser,AudioContext,arrayBuffer,decodeAudioData等等相關的API都需要去瞭解,在可視化方面,還有多種多樣的方式去繪製動畫,如WebGL。對音頻的處理也不只是在可視化方面。