傅里叶变换音频可视化_Web Audio在音频可视化中的应用
Web Audio在音頻可視化中的應用
本文有兩個關鍵詞:音頻可視化和Web Audio。前者是實踐,后者是其背后的技術支持。 Web Audio 是很大的知識點,本文會將重點放在如何獲取音頻數(shù)據(jù)這塊,對于其 API 的更多內容,可以查看 MDN。
另外,要將音頻數(shù)據(jù)轉換成可視化圖形,除了了解 Web Audio 之外,還需要對 Canvas (特指2D,下同),甚至 WebGL (可選)有一定了解。如果讀者對它們沒有任何學習基礎,可以先從以下資源入手:
什么是音頻可視化通過獲取頻率、波形和其他來自聲源的數(shù)據(jù),將其轉換成圖形或圖像在屏幕上顯示出來,再進行交互處理。
云音樂有不少跟音頻動效相關的案例,但其中有些過于復雜,又或者太偏業(yè)務。因此這里就現(xiàn)找了兩個相對簡單,但有代表性的例子。
第一個是用 Canvas 實現(xiàn)的音頻柱形圖。
第二個是用 WebGL 實現(xiàn)的粒子效果。
在具體實踐中,除了這些基本圖形(矩形、圓形等)的變換,還可以把音頻和自然運動、3D 圖形結合到一起。
什么是 Web AudioWeb Audio 是 Web 端處理和分析音頻的一套 API 。它可以設置不同的音頻來源(包括節(jié)點、 ArrayBuffer 、用戶設備等),對音頻添加音效,生成可視化圖形等。
接下來重點介紹 Web Audio 在可視化中扮演的角色,見下圖。
簡單來說,就是取數(shù)據(jù) + 映射數(shù)據(jù)兩個過程。我們先把“取數(shù)據(jù)”這個問題解決,可以按以下5步操作。
1. 創(chuàng)建 AudioContext
在音頻的任何操作之前,都必須先創(chuàng)建 AudioContext 。它的作用是關聯(lián)音頻輸入,對音頻進行解碼、控制音頻的播放暫停等基礎操作。
創(chuàng)建方式如下:
const AudioContext = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioContext();
2. 創(chuàng)建 AnalyserNode
AnalyserNode 用于獲取音頻的頻率數(shù)據(jù)( FrequencyData )和時域數(shù)據(jù)( TimeDomainData )。從而實現(xiàn)音頻的可視化。
它只會對音頻進行讀取,而不會對音頻進行任何改變。
const analyser = ctx.createAnalyser();
analyser.fftSize = 512;
關于 fftSize ,在 MDN 上的介紹可能很難理解,說是快速傅里葉變換的一個參數(shù)。
可以從以下角度理解:
1. 它的取值是什么?
fftSize 的要求是 2 的冪次方,比如 256 、 512 等。數(shù)字越大,得到的結果越精細。
對于移動端網(wǎng)頁來說,本身音頻的比特率大多是 128Kbps ,沒有必要用太大的頻率數(shù)組去存儲本身就不夠精細的源數(shù)據(jù)。另外,手機屏幕的尺寸比桌面端小,因此最終展示圖形也不需要每個頻率都采到。只需要體現(xiàn)節(jié)奏即可,因此 512 是較為合理的值。
2. 它的作用是什么?
fftSize 決定了 frequencyData 的長度,具體為 fftSize 的一半。
至于為什么是 1 / 2,感興趣的可以看下這篇文章:Why is the FFT “mirrored”?
3. 設置 SourceNode
現(xiàn)在,我們需要將音頻節(jié)點,關聯(lián)到 AudioContext 上,作為整個音頻分析過程的輸入。
在 Web Audio 中,有三種類型的音頻源:MediaElementAudioSourceNode 允許將節(jié)點直接作為輸入,可做到流式播放。
AudioBufferSourceNode 通過 xhr 預先將音頻文件加載下來,再用 AudioContext 進行解碼。
MediaStreamAudioSourceNode 可以將用戶的麥克風作為輸入。即通過navigator.getUserMedia獲取用戶的音頻或視頻流后,生成音頻源。
這 3 種音頻源中,除了 MediaStreamAudioSourceNode 有它不可替代的使用場景(比如語音或視頻直播)之外。 MediaElementAudioSourceNode 和 AudioBufferSourceNode 相對更容易混用,因此這里著重介紹一下。
MediaElementAudioSourceNode
MediaElementAudioSourceNode 將標簽作為音頻源。它的 API 調用非常簡單。
// 獲取節(jié)點const audio = document.getElementById('audio');
// 通過節(jié)點創(chuàng)建音頻源const source = ctx.createMediaElementSource(audio);
// 將音頻源關聯(lián)到分析器source.connect(analyser);
// 將分析器關聯(lián)到輸出設備(耳機、揚聲器)analyser.connect(ctx.destination);
AudioBufferSourceNode
有一種情況是,在安卓端,測試了在Chrome/69(不含)以下的版本,用 MediaElementAudioSourceNode 時,獲取到的 frequencyData 是全為 0 的數(shù)組。
因此,想要兼容這類機器,就需要換一種預加載的方式,即使用 AudioBufferSourceNode ,加載方式如下:
// 創(chuàng)建一個xhrvar xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/audio.mp3', true);
// 設置響應類型為 arraybufferxhr.responseType = 'arraybuffer';
xhr.onload = function() {
var source = ctx.createBufferSource();
// 對響應內容進行解碼 ctx.decodeAudioData(xhr.response, function(buffer) {
// 將解碼后得到的值賦給buffer source.buffer = buffer;
// 完成。將source綁定到ctx。也可以連接AnalyserNode source.connect(ctx.destination);
});
};
xhr.send();
如果將 AnalyserNode 類比中間件,會不會好理解一些?
可以對比一下常規(guī)的播放,和 Web Audio 中的播放流程:
4. 播放音頻
對于節(jié)點,即使用 MediaElementAudioSourceNode 的話,播放相對比較熟悉:
audio.play();
但如果是 AudioBufferSourceNode ,它不存在 play 方法,而是:
// 創(chuàng)建AudioBufferSourceNodeconst source = ctx.createBufferSource();
// buffer是通過xhr獲取的音頻文件source.buffer = buffer;
// 調用start方法進行播放source.start(0);
5. 獲取 frequencyData
到此,我們已經(jīng)將音頻輸入關聯(lián)到一個 AnalyserNode ,并且開始播放音頻。對于 Web Audio 這部分來說,它只剩最后一個任務:獲取頻率數(shù)據(jù)。
關于頻率, Web Audio 提供了兩個相關的 API,分別是:analyser.getByteFrequencyData
analyser.getFloatFrequencyData
兩者都是返回 TypedArray ,唯一的區(qū)別是精度不同。
getByteFrequencyData 返回的是 0 - 255 的 Uint8Array 。而 getFloatFrequencyData 返回的是 0 - 22050 的 Float32Array 。
相比較而言,如果項目中對性能的要求高于精度,那建議使用 getByteFrequencyData 。下圖展示了一個具體例子:
關于數(shù)組的長度( 256 ),在上文已經(jīng)解釋過,它是 fftSize 的一半。
現(xiàn)在,我們來看下如何獲取頻率數(shù)組:
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
需要注意的是, getByteFrequencyData 是對已有的數(shù)組元素進行賦值,而不是創(chuàng)建后返回新的數(shù)組。
它的好處是,在代碼中只會有一個 dataArray 的引用,不用通過函數(shù)調用和參數(shù)傳遞的方式來重新取值。
可視化的兩種實現(xiàn)方案
在了解 Web Audio 之后,已經(jīng)能用 getByteFrequencyData 取到一個 Uint8Array 的數(shù)組,暫時命名為 dataArray 。
從原理上講,可視化所依賴的數(shù)據(jù)可以是音頻,也可以是溫度變化,甚至可以是隨機數(shù)。所以,接下來的內容,我們只需要關心如何將 dataArray 映射為圖形數(shù)據(jù),不用再考慮 Web Audio 的操作。
(為了簡化 Canvas 和 WebGL 的描述,下文提到 Canvas 特指 Canvas 2D。)
1. Canvas 方案
Canvas 本身是一個序列幀的播放。它在每一幀中,都要先清空 Canvas ,再重新繪制。
以下是從示例代碼中摘取的一段:
function renderFrame() {
requestAnimationFrame(renderFrame);
// 更新頻率數(shù)據(jù) analyser.getByteFrequencyData(dataArray);
// bufferLength表示柱形圖中矩形的個數(shù) for (var i = 0, x = 0; i < bufferLength; i++) {
// 根據(jù)頻率映射一個矩形高度 barHeight = dataArray[i];
// 根據(jù)每個矩形高度映射一個背景色 var r = barHeight + 25 * (i / bufferLength);
var g = 250 * (i / bufferLength);
var b = 50;
// 繪制一個矩形,并填充背景色 ctx.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
renderFrame();
對于可視化來說,核心邏輯在于:如何把頻率數(shù)據(jù)映射成圖形參數(shù)。在上例中,只是簡單地改變了柱形圖中每一個矩形的高度和顏色。
Canvas 提供了豐富的繪制API,僅從 2D 的角度考慮,它也能實現(xiàn)很多酷炫的效果。類比 DOM 來說,如果只是
的組合就能做出豐富多彩的頁面,那么 Canvas 一樣可以。2. WebGL 方案
Canvas 是 CPU 計算,對于 for 循環(huán)計算 10000 次,而且每一幀都要重復計算, CPU 是負載不了的。所以我們很少看到用 Canvas 2D 去實現(xiàn)粒子效果。取而代之的,是使用 WebGL ,借助 GPU 的計算能力。
在 WebGL 中,有一個概念相對比較陌生——著色器。它是運行在 GPU 中負責渲染算法的一類總稱。它使用 GLSL( OpenGL Shading Language )編寫,簡單來說是一種類 C 風格的語言。以下是簡單的示例:
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
關于著色器更詳細的介紹,可以查看這篇文章。
WebGL 的原生 API 是非常復雜的,因此我們使用Three.js作為基礎庫,它會讓業(yè)務邏輯的編寫變得簡單。
先來看下整個開發(fā)流程中做的事情,如下圖:
在這個過程中, uniforms 的類型是簡單 Object ,我們會將音頻數(shù)組作為 uniforms 的一個屬性,傳到著色器中。至于著色器做的事情,可以簡單理解為,它將 uniforms 中定義的一系列屬性,映射為屏幕上的頂點和顏色。
頂點著色器和片元著色器的編寫往往不需要前端開發(fā)參與,對于學過 Unity3D 等技術的游戲同學可能會熟悉一些。讀者可以到 ShaderToy 上尋找現(xiàn)成的著色器。
然后介紹以下3個 Three.js 中的類:
1. THREE.Geometry
可以理解為形狀。也就是說,最后展示的物體是球體、還是長方體、還是其他不規(guī)則的形狀,是由這個類決定的。
因此,你需要給它傳入一些頂點的坐標。比如三角形,有3個頂點,則傳入3個頂點坐標。
當然, Three.js 內置了很多常用的形狀,比如 BoxGeometry 、 CircleGeometry 等。
2. THREE.ShaderMaterial
可以理解為顏色。還是以三角形為例,一個三角形可以是黑色、白色、漸變色等,這些顏色是由 ShaderMaterial 決定的。
ShaderMaterial 是 Material 的一種,它由頂點著色器和片元著色器進行定義。
3. THREE.Mesh
定義好物體的形狀和顏色后,需要把它們組合在一起,稱作 Mesh (網(wǎng)格)。有了 Mesh 之后,便可以將它添加到畫布中。然后就是常規(guī)的 requestAnimationFrame 的流程。
同樣的,我們摘取了示例中比較關鍵的代碼,并做了標注。
i. 創(chuàng)建 Geometry (這是從 THREE.BufferGeometry 繼承的類):
var geometry = ParticleBufferGeometry({
// TODO 一些參數(shù)});
ii. 定義 uniforms :
var uniforms = {
dataArray: {
value: null,
type: 't' // 對應THREE.DataTexture },
// TODO 其他屬性};
iii. 創(chuàng)建 ShaderMaterial :
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: '', // TODO 傳入頂點著色器 fragmentShader: '', // TODO 傳入片元著色器 // TODO 其他參數(shù)});
iv. 創(chuàng)建 Mesh :
var mesh = new THREE.Mesh(geometry, material);
v. 創(chuàng)建 Three.js 中一些必須的渲染對象,包括場景和攝像頭:
var scene, camera, renderer;
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
camera = new THREE.PerspectiveCamera(45, 1, .1, 1e3);
scene = new THREE.Scene();
vi. 常規(guī)的渲染邏輯:
function animate() {
requestAnimationFrame(animate);
// TODO 此處可以觸發(fā)事件,用于更新頻率數(shù)據(jù)
renderer.render(scene, camera);
}
小結
本文首先介紹了如何通過 Web Audio 的相關 API 獲取音頻的頻率數(shù)據(jù)。
然后介紹了 Canvas 和 WebGL 兩種可視化方案,將頻率數(shù)據(jù)映射為圖形數(shù)據(jù)的一些常用方式。
另外,云音樂客戶端上線鯨云動效已經(jīng)有一段時間,看過本文之后,有沒有同學想嘗試實現(xiàn)一個自己的音頻動效呢?
最后附上文中提到的兩段 codepen 示例:本文發(fā)布自 網(wǎng)易云音樂前端團隊,文章未經(jīng)授權禁止任何形式的轉載。我們一直在招人,如果你恰好準備換工作,又恰好喜歡云音樂,那就 加入我們!
總結
以上是生活随笔為你收集整理的傅里叶变换音频可视化_Web Audio在音频可视化中的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: easyui框架前后端交互_easyui
- 下一篇: jira图片_JIRA使用不求人-从菜鸟