audiocontext html5,使用HTML5 API(AudioContext)實現可視化頻譜效果

HTML5 可視化頻譜效果

如今的HTML5技術正讓網頁變得越來越強大,通過其Canvas標簽與AudioContext對象可以輕松實現之前在Flash或Native App中才能實現的頻譜指示器的功能。

開始使用AudioContext

The AudioContext interface represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode.

根據MDN的文檔,AudioContext是一個專門用於音頻處理的接口,並且工作原理是將AudioContext創建出來的各種節點(AudioNode)相互連接,音頻數據流經這些節點並作出相應處理。

創建AudioContext對象

由於瀏覽器兼容性問題,我們需要為不同瀏覽器配置AudioContext,在這里我們可以用下面這個表達式來統一對AudioContext的訪問。var AudioContext = window.AudioContext || window.webkitAudioContext;

var audioContext = new AudioContext(); //實例化AudioContext對象

附. 瀏覽器兼容性

瀏覽器

Chrome

Firefox

IE

Opera

Safari

支持版本

10.0

25.0

不支持

15.0

6.0

當然,如果瀏覽器不支持的話,我們也沒有辦法,用IE的人們我想也不需要這些效果。但最佳實踐是使用的時候判斷一下上面聲明的變量是否為空,然后再做其他操作。

解碼音頻文件

讀取到的音頻文件是二進制類型,我們需要讓AudioContext先對其解碼,然后再進行后續操作。audioContext.decodeAudioData(binary, function(buffer) { ... });

方法decodeAudioData被調用后,瀏覽器將開始解碼音頻文件,這需要一定時間,我們應該讓用戶知道瀏覽器正在解碼,解碼成功后會調用傳進去的回調函數,decodeAudioData還有第三個可選參數是在解碼失敗時調用的,我們這里就先不實現了。

創建音頻處理節點

這是最關鍵的一步,我們需要兩個音頻節點:

AudioBufferSourceNode

AnalyserNode

前者是用於播放解碼出來的buffer的節點,而后者是用於分析音頻頻譜的節點,兩個節點順次連接就能完成我們的工作。

創建AudioBufferSourceNodevar audioBufferSourceNode;

audioBufferSourceNode = audioContext.createBufferSource();

創建AnalyserNodevar analyser;

analyser = audioContext.createAnalyser();

analyser.fftSize = 256;

上面的fftSize是用於確定FFT大小的屬性,那FFT是什么高三的博主還不知道,其實也不需要知道,總之最后獲取到的數組長度應該是fftSize值的一半,還應該保證它是以2為底的冪。

連接節點audioBufferSourceNode.connect(analyser);

analyser.connect(audioContext.destination);

上面的audioContext.destination是音頻要最終輸出的目標,我們可以把它理解為聲卡。所以所有節點中的最后一個節點應該再連接到audioContext.destination才能聽到聲音。

播放音頻

所有工作就緒,在解碼完畢時調用的回調函數中我們就可以開始播放了。audioBufferSourceNode.buffer = buffer; //回調函數傳入的參數

audioBufferSourceNode.start(0); //部分瀏覽器是noteOn()函數,用法相同

參數代表播放起點,我們這里設置為0意味着從頭播放。

文件讀取

HTML5支持文件選擇、讀取的特性,我們利用這個特性可以實現不上傳,即播放的功能。

HTML標簽

在你的頁面中找個位置插入:

Js邏輯var file;

var fileChooser = document.getElementById('fileChooser');

fileChooser.onchange = function() {

if (fileChooser.files[0]) {

file = fileChooser.files[0];

// Do something with 'file'...

}

}

使用FileReader異步讀取文件var fileContent;

var fileReader = new FileReader();

fileReader.onload = function(e) {

fileContent = e.target.result;

// Do something with 'fileContent'...

}

fileReader.readAsArrayBuffer(file);

其實這里的fileContent就是上面AudioContext要解碼的那個binary,至此兩部分的工作就可以連起來了。

WARNING:

Chrome或Firefox瀏覽器的跨域訪問限制會使FileReader在本地失效,Chrome用戶可在調試時添加命令行參數:

chrome.exe --disable-web-security

Canvas繪制頻譜

這一部分我不打算詳細敘述,就提幾個重點。

AnalyserNode數據解析

在繪制之前通過下面的方法獲取到AnalyserNode分析的數據:var dataArray = new Uint8Array(analyser.frequencyBinCount);

analyser.getByteFrequencyData(dataArray);

數組中每個元素是從0到fftSize屬性值的數值,這樣我們通過一定比例就能控制能量條的高度等狀態。

requestAnimationFrame的使用

要使動畫動起來,我們需要不斷重繪Canvas標簽里的內容,這就需要requestAnimationFrame這個函數了,它可以幫你以60fps的幀率繪制動畫。

使用方法:var draw = function() {

// ...

window.requestAnimationFrame(draw);

}

window.requestAnimationFrame(draw);

這段代碼應該不難理解,就是一個類似遞歸的調用,但不是遞歸,有點像Android中的postInvalidate

實例代碼

貼上我寫的一段繪制代碼:var render = function() {

ctx = canvas.getContext("2d");

ctx.strokeStyle = "#00d0ff";

ctx.lineWidth = 2;

ctx.clearRect(0, 0, canvas.width, canvas.height); //清理畫布

var dataArray = new Uint8Array(analyser.frequencyBinCount);

analyser.getByteFrequencyData(dataArray);

var step = Math.round(dataArray.length / 60); //采樣步長

for (var i = 0; i < 40; i++) {

var energy = (dataArray[step * i] / 256.0) * 50;

for (var j = 0; j < energy; j++) {

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200 + 4 * j);

ctx.lineTo(20 * (i + 1) - 2, 200 + 4 * j);

ctx.stroke();

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200 - 4 * j);

ctx.lineTo(20 * (i + 1) - 2, 200 - 4 * j);

ctx.stroke();

}

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200);

ctx.lineTo(20 * (i + 1) - 2, 200);

ctx.stroke();

}

window.requestAnimationFrame(render);

}

OK,大致就是這樣,之后可以加一些css樣式,完善一下業務邏輯,這里就不再闡釋了。最后貼上整理好的全部代碼:

HTML 部分

HTML5 Audio Visualizing

background-color:#222222}

input {

color:#ffffff}

#wrapper {

display:table;

width:100%;

height:100%;

}

#wrapper-inner {

display:table-cell;

vertical-align:middle;

padding-left:25%;

padding-right:25%;

}

#tip {

color:#fff;

opacity:0;

transition:opacity 1s;

-moz-transition:opacity 1s;

-webkit-transition:opacity 1s;

-o-transition:opacity 1s;

}

#tip.show {

opacity:1}

Decoding...

Your browser does not support Canvas tag.

Js部分var AudioContext = window.AudioContext || window.webkitAudioContext; //Cross browser variant.

var canvas, ctx;

var audioContext;

var file;

var fileContent;

var audioBufferSourceNode;

var analyser;

var loadFile = function() {

var fileReader = new FileReader();

fileReader.onload = function(e) {

fileContent = e.target.result;

decodecFile();

}

fileReader.readAsArrayBuffer(file);

}

var decodecFile = function() {

audioContext.decodeAudioData(fileContent, function(buffer) {

start(buffer);

});

}

var start = function(buffer) {

if(audioBufferSourceNode) {

audioBufferSourceNode.stop();

}

audioBufferSourceNode = audioContext.createBufferSource();

audioBufferSourceNode.connect(analyser);

analyser.connect(audioContext.destination);

audioBufferSourceNode.buffer = buffer;

audioBufferSourceNode.start(0);

showTip(false);

window.requestAnimationFrame(render); //先判斷是否已經調用一次

}

var showTip = function(show) {

var tip = document.getElementById('tip');

if (show) {

tip.className = "show";

} else {

tip.className = "";

}

}

var render = function() {

ctx = canvas.getContext("2d");

ctx.strokeStyle = "#00d0ff";

ctx.lineWidth = 2;

ctx.clearRect(0, 0, canvas.width, canvas.height);

var dataArray = new Uint8Array(analyser.frequencyBinCount);

analyser.getByteFrequencyData(dataArray);

var step = Math.round(dataArray.length / 60);

for (var i = 0; i < 40; i++) {

var energy = (dataArray[step * i] / 256.0) * 50;

for (var j = 0; j < energy; j++) {

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200 + 4 * j);

ctx.lineTo(20 * (i + 1) - 2, 200 + 4 * j);

ctx.stroke();

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200 - 4 * j);

ctx.lineTo(20 * (i + 1) - 2, 200 - 4 * j);

ctx.stroke();

}

ctx.beginPath();

ctx.moveTo(20 * i + 2, 200);

ctx.lineTo(20 * (i + 1) - 2, 200);

ctx.stroke();

}

window.requestAnimationFrame(render);

}

window.onload = function() {

audioContext = new AudioContext();

analyser = audioContext.createAnalyser();

analyser.fftSize = 256;

var fileChooser = document.getElementById('fileChooser');

fileChooser.onchange = function() {

if (fileChooser.files[0]) {

file = fileChooser.files[0];

showTip(true);

loadFile();

}

}

canvas = document.getElementById('visualizer');

}

以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值