前端音頻可視化Web?Audio實(shí)現(xiàn)示例詳解
背景
最近聽音樂的時(shí)候,看到各種動(dòng)效,突然好奇這些音頻數(shù)據(jù)是如何獲取并展示出來(lái)的,于是花了幾天功夫去研究相關(guān)的內(nèi)容,這里只是給大家一些代碼實(shí)例,具體要看懂、看明白,還是建議大家大家結(jié)合相關(guān)API文檔來(lái)閱讀這篇文章。
參考資料地址:Web Audio API - Web API 接口參考 | MDN (mozilla.org)
實(shí)現(xiàn)思路
首先畫肯定是用canvas去畫,關(guān)于音頻的相關(guān)數(shù)據(jù)(如頻率、波形)如何去獲取,需要去獲取相關(guān)audio的DOM 或通過請(qǐng)求處理去拿到相關(guān)的音頻數(shù)據(jù),然后通過Web Audio API 提供相關(guān)的方法來(lái)實(shí)現(xiàn)。(當(dāng)然還要考慮要音頻請(qǐng)求跨域的問題,留在最后。)
一個(gè)簡(jiǎn)單而典型的 web audio 流程如下(取自MDN):
- 創(chuàng)建音頻上下文
- 在音頻上下文里創(chuàng)建源 — 例如
<audio>
, 振蕩器,流 - 創(chuàng)建效果節(jié)點(diǎn),例如混響、雙二階濾波器、平移、壓縮
- 為音頻選擇一個(gè)目的地,例如你的系統(tǒng)揚(yáng)聲器
連接源到效果器,對(duì)目的地進(jìn)行效果輸出
實(shí)現(xiàn)
一、頻率圖
實(shí)現(xiàn)第一種類型,首先我們需要通過fetch或xhr來(lái)獲取一個(gè)線上音頻的數(shù)據(jù),這里以fetch為例;
//創(chuàng)建一個(gè)音頻上下文、考慮兼容性問題 let audioCtx = new (window.AudioContext || window.webkitAudioContext)(); //添加一個(gè)音頻源節(jié)點(diǎn) 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解碼完成后,返回一個(gè)AudioBuffer對(duì)象 // 繪制音頻波形圖 draw(buffer); // 連接音頻源 source.buffer = buffer; source.connect(audioCtx.destination); // 音頻數(shù)據(jù)處理完畢 }); });
需要明白的是,source.connect(audioCtx.destination)是將音頻源節(jié)點(diǎn)鏈接到輸出設(shè)備,否則會(huì)沒聲音哦。那么現(xiàn)在有了數(shù)據(jù)、我們只需要通過canvas將數(shù)據(jù)畫出來(lái)即可。
function draw(buffer) { // buffer.numberOfChannels返回音頻的通道數(shù)量,1即為單聲道,2代表雙聲道。這里我們只取一條通道的數(shù)據(jù) let data = []; let originData = buffer.getChannelData(0); // 存儲(chǔ)所有的正數(shù)據(jù) let positives = []; // 存儲(chǔ)所有的負(fù)數(shù)據(jù) let negatives = []; // 先每隔50條數(shù)據(jù)取1條 for (let i = 0; i < originData.length; i += 50) { data.push(originData[i]); } // 再?gòu)膁ata中每10條取一個(gè)最大值一個(gè)最小值 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表示漸變開始和結(jié)束之間的位置(用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); // 橫坐標(biāo)上方繪制正數(shù)據(jù),下方繪制負(fù)數(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]); } // 再?gòu)挠彝罄L制負(fù)數(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 - 繪制音頻圖譜
二、實(shí)時(shí)頻率圖
實(shí)現(xiàn)第二種類型,獲取實(shí)時(shí)頻率,用到的API與第一種有區(qū)別,但流程一直,都是通過一個(gè)音頻源節(jié)點(diǎn)通過連接達(dá)到效果。只不過在連接的中間加入了一個(gè)分析器analyser,在將分析器連接到輸出設(shè)備。
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ù)提出來(lái) let dataArray,analyser; //播放事件 audio.onplay=function(){ //創(chuàng)建一個(gè)音頻上下文實(shí)例 const audioCtx=new (window.AudioContext || window.webkitAudioContext)(); //添加一個(gè)音頻源節(jié)點(diǎn) const source=audioCtx.createMediaElementSource(audio); //分析器節(jié)點(diǎn) analyser=audioCtx.createAnalyser(); //fft分析器 越大 分析越細(xì) analyser.fftSize=512 //創(chuàng)建一個(gè)無(wú)符號(hào)字節(jié)的數(shù)組 dataArray=new Uint8Array( analyser.frequencyBinCount); //音頻源節(jié)點(diǎn) 鏈接分析器 source.connect(analyser) //分析器鏈接輸出設(shè)備 analyser.connect(audioCtx.destination,) }
那么接下來(lái)至于怎么把數(shù)據(jù)畫出來(lái),就憑大家的想法了。
requestAnimationFrame(draw) // const {width ,height}=canvas; ctx.clearRect(0,0,width,height) //分析器節(jié)點(diǎn)分析出的數(shù)據(jù)到數(shù)組中 ctx.fillStyle='#78C5F7' ctx.lineWidth = 2; ctx.beginPath(); //getByteFrequencyData,分析當(dāng)前音頻源的數(shù)據(jù) 裝到dataArray數(shù)組中去 //獲取實(shí)時(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();
關(guān)于請(qǐng)求音頻跨域問題解決方案
給獲取的audio DOM添加一條屬性即可
audio.crossOrigin ='anonymous'
或者直接在 aduio標(biāo)簽中 加入 crossorigin="anonymous"
總結(jié)
雖然現(xiàn)在已經(jīng)有很多開源的對(duì)于音頻相關(guān)的庫(kù),但如果真正的想要去了解,去學(xué)習(xí)音頻相關(guān)的東西。必須要去深入學(xué)習(xí)相關(guān)的Web Audio API,當(dāng)然這里只是用了其中兩種的方法去實(shí)現(xiàn)Web Audio去實(shí)現(xiàn)可視化,算是一個(gè)基礎(chǔ)入門,對(duì)于文中的createBufferSource,createMediaElementSource,createAnalyser,AudioContext,arrayBuffer,decodeAudioData等等相關(guān)的API都需要去了解,在可視化方面,還有多種多樣的方式去繪制動(dòng)畫,如WebGL。對(duì)音頻的處理也不只是在可視化方面。
以上就是前端音頻可視化Web Audio實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Web Audio音頻可視化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)的DOM插入節(jié)點(diǎn)操作示例
這篇文章主要介紹了JS實(shí)現(xiàn)的DOM插入節(jié)點(diǎn)操作,結(jié)合實(shí)例形式分析了javascript針對(duì)頁(yè)面dom元素動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-04-04Javascript圖片上傳前的本地預(yù)覽實(shí)例
圖片的上傳預(yù)覽功能主要用于圖片上傳前的一個(gè)效果的預(yù)覽,這篇文章主要介紹了Javascript圖片上傳前的本地預(yù)覽實(shí)例,需要的朋友可以參考下2014-06-06JavaScript 井字棋人工智能實(shí)現(xiàn)代碼
JavaScript fights back in this artificial Tic Tac Toe game. Great script to have to entertain yourself and your visitors.2009-12-12javascript 10進(jìn)制和62進(jìn)制的相互轉(zhuǎn)換
本節(jié)主要介紹了javascript 10進(jìn)制和62進(jìn)制的相互轉(zhuǎn)換,需要的朋友可以參考下2014-07-07JS從數(shù)組中隨機(jī)取出幾個(gè)數(shù)組元素的方法
JS如何從一個(gè)數(shù)組中隨機(jī)取出一個(gè)元素或者幾個(gè)元素呢?其實(shí)方法很簡(jiǎn)單,下面小編給大家分享了JS隨機(jī)取出幾個(gè)數(shù)組元素的方法,非常不錯(cuò),需要的朋友參考下2016-08-08js為數(shù)字添加逗號(hào)并格式化數(shù)字的代碼
數(shù)字添加逗號(hào)的方法有很多,在本將為大家介紹下使用js來(lái)實(shí)現(xiàn),具體如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08js實(shí)現(xiàn)html table 行,列鎖定的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇js實(shí)現(xiàn)html table 行,列鎖定的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2016-10-10