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