溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

基于Web Audio API實(shí)現(xiàn)音頻可視化效果的方法

發(fā)布時(shí)間:2020-06-23 15:55:43 來源:億速云 閱讀:445 作者:清晨 欄目:web開發(fā)

這篇文章將為大家詳細(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ì)帶來下面的效果:

基于Web Audio API實(shí)現(xiàn)音頻可視化效果的方法

源碼:

<!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ò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(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)容。

AI