您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)基于Web Audio API實(shí)現(xiàn)音頻可視化效果的方法,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
網(wǎng)頁音頻接口最有趣的特性之一它就是可以獲取頻率、波形和其它來自聲源的數(shù)據(jù),這些數(shù)據(jù)可以被用作音頻可視化。這篇文章將解釋如何做到可視化,并提供了一些基礎(chǔ)使用案例。
基本概念節(jié)
要從你的音頻源獲取數(shù)據(jù),你需要一個(gè) AnalyserNode
節(jié)點(diǎn),它可以用 AudioContext.createAnalyser()
方法創(chuàng)建,比如:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); var analyser = audioCtx.createAnalyser();
然后把這個(gè)節(jié)點(diǎn)(node)
連接到你的聲源:
source = audioCtx.createMediaStreamSource(stream); source.connect(analyser); analyser.connect(distortion);
// etc.
注意: 分析器節(jié)點(diǎn)(Analyser Node)
不一定輸出到另一個(gè)節(jié)點(diǎn),不輸出時(shí)也可以正常使用。但前提是它必須與一個(gè)聲源相連(直接或者通過其他節(jié)點(diǎn)間接相連都可以)。
分析器節(jié)點(diǎn)(Analyser Node)
將在一個(gè)特定的頻率域里使用快速傅立葉變換(Fast Fourier Transform (FFT) )
來捕獲音頻數(shù)據(jù),這取決于你給 AnalyserNode.fftSize
屬性賦的值(如果沒有賦值,默認(rèn)值為2048)。
注意: 你也可以為FFT數(shù)據(jù)縮放范圍指定一個(gè)最小值和最大值,使用AnalyserNode.minDecibels 和AnalyserNode.maxDecibels
進(jìn)行設(shè)置,要獲得不同數(shù)據(jù)的平均常量,使用 AnalyserNode.smoothingTimeConstant
。閱讀這些頁面以獲得更多如何使用它們的信息。
要捕獲數(shù)據(jù),你需要使用 AnalyserNode.getFloatFrequencyData()
或 AnalyserNode.getByteFrequencyData()
方法來獲取頻率數(shù)據(jù),用 AnalyserNode.getByteTimeDomainData() 或 AnalyserNode.getFloatTimeDomainData()
來獲取波形數(shù)據(jù)。
這些方法把數(shù)據(jù)復(fù)制進(jìn)了一個(gè)特定的數(shù)組當(dāng)中,所以你在調(diào)用它們之前要先創(chuàng)建一個(gè)新數(shù)組。第一個(gè)方法會(huì)產(chǎn)生一個(gè)32位浮點(diǎn)數(shù)組,第二個(gè)和第三個(gè)方法會(huì)產(chǎn)生8位無符號(hào)整型數(shù)組,因此一個(gè)標(biāo)準(zhǔn)的JavaScript數(shù)組就不能使用 —— 你需要用一個(gè) Float32Array 或者 Uint8Array 數(shù)組,具體需要哪個(gè)視情況而定。
那么讓我們來看看例子,比如我們正在處理一個(gè)2048尺寸的FFT。我們返回 AnalyserNode.frequencyBinCount 值
,它是FFT的一半,然后調(diào)用Uint8Array()
,把frequencyBinCount
作為它的長度參數(shù) —— 這代表我們將對(duì)這個(gè)尺寸的FFT收集多少數(shù)據(jù)點(diǎn)。
analyser.fftSize = 2048; var bufferLength = analyser.frequencyBinCount; var dataArray = new Uint8Array(bufferLength);
要正確檢索數(shù)據(jù)并把它復(fù)制到我們的數(shù)組里,就要調(diào)用我們想要的數(shù)據(jù)收集方法,把數(shù)組作為參數(shù)傳遞給它,例如:
analyser.getByteTimeDomainData(dataArray);
現(xiàn)在我們就獲取了那時(shí)的音頻數(shù)據(jù),并存到了我們的數(shù)組里,而且可以把它做成我們喜歡的可視化效果了,比如把它畫在一個(gè)HTML5 <canvas>
畫布上。
創(chuàng)建一個(gè)頻率條形圖節(jié)
另一種小巧的可視化方法是創(chuàng)建頻率條形圖,
現(xiàn)在讓我們來看看它是如何實(shí)現(xiàn)的。
首先,我們?cè)O(shè)置好解析器和空數(shù)組,之后用 clearRect()
清空畫布。與之前的唯一區(qū)別是我們這次大大減小了FFT
的大小,這樣做的原因是為了使得每個(gè)頻率條足夠?qū)?,讓它們看著像“條”而不是“細(xì)桿”。
analyser.fftSize = 256; var bufferLength = analyser.frequencyBinCount; console.log(bufferLength); var dataArray = new Uint8Array(bufferLength); canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
接下來我們寫好 draw() 函數(shù),再一次用 requestAnimationFrame()
設(shè)置一個(gè)循環(huán),這樣顯示的數(shù)據(jù)就可以保持刷新,并且每一幀都清空一次畫布。
function draw() { drawVisual = requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvasCtx.fillStyle = 'rgb(0, 0, 0)'; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); }
現(xiàn)在我們來設(shè)置一個(gè) barWidth 變量,它等于每一個(gè)條形的寬度。理論上用花布寬度除以條的個(gè)數(shù)就可以得到它,但是在這里我們還要乘以2.5。這是因?yàn)橛泻芏喾祷氐念l率區(qū)域中是沒有聲音的,我們每天聽到的大多數(shù)聲音也只是在一個(gè)很小的頻率區(qū)域當(dāng)中。在條形圖中我們肯定不想看到大片的空白條,所以我們就把一些能正常顯示的條形拉寬來填充這些空白區(qū)域。
我們還要設(shè)置一個(gè)條高度變量 barHeight
,還有一個(gè) x 變量來記錄當(dāng)前條形的位置。
var barWidth = (WIDTH / bufferLength) * 2.5; var barHeight; var x = 0;
像之前一樣,我們進(jìn)入循環(huán)來遍歷 dataArray 數(shù)組中的數(shù)據(jù)。在每一次循環(huán)過程中,我們讓條形的高度 barHeight
等于數(shù)組的數(shù)值,之后根據(jù)高度設(shè)置條形的填充色(條形越高,填充色越亮),然后在橫坐標(biāo) x 處按照設(shè)置的寬度和高度的一半把條形畫出來(我們最后決定只畫高度的一半因?yàn)檫@樣條形看起來更美觀)。
需要多加解釋的一點(diǎn)是每個(gè)條形豎直方向的位置,我們?cè)?HEIGHT-barHeight/2
的位置畫每一條,這是因?yàn)槲蚁胱屆總€(gè)條形從底部向上伸出,而不是從頂部向下(如果我們把豎直位置設(shè)置為0它就會(huì)這樣畫)。所以,我們把豎直位置設(shè)置為畫布高度減去條形高度的一半,這樣每個(gè)條形就會(huì)從中間向下畫,直到畫布最底部。
for(var i = 0; i < bufferLength; i++) { barHeight = dataArray[i]/2; canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)'; canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight); x += barWidth + 1; } };
和剛才一樣,我們?cè)谧詈笳{(diào)用 draw()
函數(shù)來開啟整個(gè)可視化過程。
draw();
這些代碼會(huì)帶來下面的效果:
源碼:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>可視化音樂播放器</title> </head> <body> <input type="file" name="" value="" id="musicFile"> <p id="tip"></p> <canvas id="casvased" width="500" height="500"></canvas> </body> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script type="text/javascript"> //隨機(jī)變顏色 function randomRgbColor() { //隨機(jī)生成RGB顏色 var r = Math.floor(Math.random() * 256); //隨機(jī)生成256以內(nèi)r值 var g = Math.floor(Math.random() * 256); //隨機(jī)生成256以內(nèi)g值 var b = Math.floor(Math.random() * 256); //隨機(jī)生成256以內(nèi)b值 return `rgb(${r},${g},$)`; //返回rgb(r,g,b)格式顏色 } //隨機(jī)數(shù) 0-255 function sum (m,n){ var num = Math.floor(Math.random()*(m - n) + n); } console.log(sum(0,100)); console.log(sum(100,255)); //展示音頻可視化 var canvas = document.getElementById("casvased"); var canvasCtx = canvas.getContext("2d"); //首先實(shí)例化AudioContext對(duì)象 很遺憾瀏覽器不兼容,只能用兼容性寫法;audioContext用于音頻處理的接口,并且工作原理是將AudioContext創(chuàng)建出來的各種節(jié)點(diǎn)(AudioNode)相互連接,音頻數(shù)據(jù)流經(jīng)這些節(jié)點(diǎn)并作出相應(yīng)處理。 //總結(jié)就一句話 AudioContext 是音頻對(duì)象,就像 new Date()是一個(gè)時(shí)間對(duì)象一樣 var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext; if (!AudioContext) { alert("您的瀏覽器不支持audio API,請(qǐng)更換瀏覽器(chrome、firefox)再嘗試,另外本人強(qiáng)烈建議使用谷歌瀏覽器!") } var audioContext = new AudioContext();//實(shí)例化 // 總結(jié)一下接下來的步驟 // 1 先獲取音頻文件(目前只支持單個(gè)上傳) // 2 讀取音頻文件,讀取后,獲得二進(jìn)制類型的音頻文件 // 3 對(duì)讀取后的二進(jìn)制文件進(jìn)行解碼 $('#musicFile').change(function(){ if (this.files.length == 0) return; var file = $('#musicFile')[0].files[0];//通過input上傳的音頻文件 var fileReader = new FileReader();//使用FileReader異步讀取文件 fileReader.readAsArrayBuffer(file);//開始讀取音頻文件 fileReader.onload = function(e) {//讀取文件完成的回調(diào) //e.target.result 即為讀取的音頻文件(此文件為二進(jìn)制文件) //下面開始解碼操作 解碼需要一定時(shí)間,這個(gè)時(shí)間應(yīng)該讓用戶感知到 var count = 0; $('#tip').text('開始解碼') var timer = setInterval(function(){ count++; $('#tip').text('解碼中,已用時(shí)'+count+'秒') },1000) //開始解碼,解碼成功后執(zhí)行回調(diào)函數(shù) audioContext.decodeAudioData(e.target.result, function(buffer) { clearInterval(timer) $('#tip').text('解碼成功,用時(shí)共計(jì):'+count+'秒') // 創(chuàng)建AudioBufferSourceNode 用于播放解碼出來的buffer的節(jié)點(diǎn) var audioBufferSourceNode = audioContext.createBufferSource(); // 創(chuàng)建AnalyserNode 用于分析音頻頻譜的節(jié)點(diǎn) var analyser = audioContext.createAnalyser(); //fftSize (Fast Fourier Transform) 是快速傅里葉變換,一般情況下是固定值2048。具體作用是什么我也不太清除,但是經(jīng)過研究,這個(gè)值可以決定音頻頻譜的密集程度。值大了,頻譜就松散,值小就密集。 analyser.fftSize = 256; // 連接節(jié)點(diǎn),audioContext.destination是音頻要最終輸出的目標(biāo), // 我們可以把它理解為聲卡。所以所有節(jié)點(diǎn)中的最后一個(gè)節(jié)點(diǎn)應(yīng)該再 // 連接到audioContext.destination才能聽到聲音。 audioBufferSourceNode.connect(analyser); analyser.connect(audioContext.destination); console.log(audioContext.destination) // 播放音頻 audioBufferSourceNode.buffer = buffer; //回調(diào)函數(shù)傳入的參數(shù) audioBufferSourceNode.start(); //部分瀏覽器是noteOn()函數(shù),用法相同 //可視化 創(chuàng)建數(shù)據(jù) // var dataArray = new Uint8Array(analyser.fftSize); // analyser.getByteFrequencyData(dataArray)//將數(shù)據(jù)放入數(shù)組,用來進(jìn)行頻譜的可視化繪制 // console.log(analyser.getByteFrequencyData) var bufferLength = analyser.frequencyBinCount; console.log(bufferLength); var dataArray = new Uint8Array(bufferLength); console.log(dataArray) canvasCtx.clearRect(0, 0, 500, 500); function draw() { drawVisual = requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvasCtx.fillStyle = 'rgb(0, 0, 0)'; //canvasCtx.fillStyle = ; canvasCtx.fillRect(0, 0, 500, 500); var barWidth = (500 / bufferLength) * 2.5; var barHeight; var x = 0; for(var i = 0; i < bufferLength; i++) { barHeight = dataArray[i]; //隨機(jī)數(shù)0-255 Math.floor(Math.random()*255) // 隨機(jī)數(shù) 10*Math.random() canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ','+Math.floor(Math.random()*(20- 120) + 120)+','+Math.floor(Math.random()*(10 - 50) + 50)+')'; canvasCtx.fillRect(x,500-barHeight/2,barWidth,barHeight/2); x += barWidth + 1; } }; draw(); }); } }) </script> </html>
關(guān)于基于Web Audio API實(shí)現(xiàn)音頻可視化效果的方法就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。