Web Audio提供了一個強大的音頻處理系統,在我們現有的業務場景中,很少有使用到Web Audio,很多時候用到也僅限於播放一段音頻。
除此之外,還能實現豐富的功能,比如:可視化、音色合成器、動態混音、聲音特效、3D空間音頻、均衡器、環境混響等,可以應用在音樂播放器、電子音樂軟件、游戲音效、音樂游戲、直播互動等領域。
這篇文章是我在學習Web Audio的過程中寫的一些總結和Demo,簡單介紹一些API基礎用法。
文章中所有示例:https://web-audio.johnsonlee.site/[1]
1. AudioContext
AudioContext
為音頻處理提供一個上下文環境,相當於一個中央控制器,控制着音頻路由圖中的各個音頻模塊。
在開始音頻處理之前,都需要創建一個AudioContext實例,並且可以全局共享同一個。所有(相關)的音頻節點都需要包含在同一個AudioContext中,每個音頻節點,只能關聯一個AudioContext。
1.1 音頻節點
音頻節點即AudioNode
,它是一個基類,作為一個音頻路由圖[2]中的基本單位,它的工作依賴於AudioContext
。
音頻節點擁有自己的輸入/輸出,可以通過connect
方法將一個節點的輸出連接至另一個節點的輸入。比如我們可以將一個音頻節點連接至audioContext.destination
節點來進行音頻播放。
audioBufferSourceNode.connect(audioContext.destination)
上面的audioContext.destination
是音頻節點中的一種,音頻節點可以分為三類:
- Source Node:能產生音頻的節點,只有輸出,沒有輸入。
- Process Node:對音頻進行處理的節點,有輸入(可能有多個)和輸出。
- Destination Node:通常為音頻播放設備,如揚聲器。
有的音頻處理節點會有多個輸出,比如ChannelSplitterNode
,可以將音頻拆分為多個聲道,對應的,也有一個合並聲道的節點ChannelMergerNode
,有多個輸入和一個輸出。
1.2 路由圖
Web Audio 提供的是模塊化的API,在AudioContext中,各個音頻節點的連接,構建了一個路由圖,audioContext控制着整個路由圖的運轉。比如下面一個簡單的音頻播放示例
const ac = new AudioContext() const $audio = document.querySelector('#audio'); const sourceNode = ac.createMediaElementSource($audio); // 從audio標簽創建一個音頻源節點 const gainNode = ac.createGain(); // 創建一個增益節點 gainNode.gain.value = 0; // 將增益設置為0(相當於音量設置為0) $audio.addEventListener('play', () => { gainNode.gain.exponentialRampToValueAtTime(1, 1); // 在1秒的時間內指數增長到1,實現一個播放漸入效果 }); sourceNode.connect(gainNode); // 音頻源節點連接到增益節點 gainNode.connect(ac.destination); // 增益節點連接到destination進行播放
2. 音頻源
web中音頻源包括:
- audio節點
- 網絡加載的音頻文件
- 實時音頻流(webRTC、麥克風)
- 能產生音頻信號的音頻節點(如:OscillatorNode)
2.1 從<audio>
標簽創建音頻源
網絡加載的音頻文件,需要將其轉換成音頻源節點,才能連接到路由圖中,比如我們經常使用的<audio>
標簽,它是不能直接連接到其它音頻節點的
const ac = new AudioContext(); const $audio = document.querySelector('#audio'); // create a source node from an audio const sourceNode = ac.createMediaElementSource($audio); sourceNode.connect(/* other audio node */);
2.2 使用http請求加載音頻數據
我們也可以使用http請求,將二進制的音頻數據下載到本地后,再創建一個音頻源節點
const ac = new AudioContext(); const source = ac.createBufferSource(); source.connect(ac.destination); fetch('xxx.mp3').then(res => res.arrayBuffer()).then( async buffer => { source.buffer = await ac.decodeAudioData(buffer); source.start(); } );
上面代碼中,我們將解碼后的數據(AudioBuffer[3])加載到了source.buffer
屬性中,實際上,這里除了從網絡/本地加載音頻文件外,我們也可以自己創建一個buffer丟給source.buffer
來制造一些聲音,比如下面的這個示例,使用隨機函數生成一個白噪聲音頻
https://codepen.io/JohnsonLeee/pen/bGqLXmZ?editors=0010[4]
AudioBuffer
的設計是為了存儲短時間的音頻片段,不建議將特別長的音頻通過BufferSource方式進行播放,更推薦使用上一種方案。
2.3 使用內置振盪器產生聲音
OscillatorNode
是一個振盪器節點,它可以產生一個周期信號,可以指定type為square
, sine
, sawtooth
, triangle
, custom
, 默認為sine
。其次需要設置frequency
屬性來設定頻率,它是AudioParam
類型,不能直接賦值。
const ac = new AudioContext(); const oscillator = ac.createOscillator(); oscillator.type = 'square'; oscillator.frequency.value = 440; // 不能直接給frequency負值,可以設置其value // oscillator.frequency.exponentialRampToValueAtTime(440, 1) // 也可以通過它提供的方法來設置 oscillator.connect(ac.destination); oscillator.start();
https://codepen.io/JohnsonLeee/pen/MWpwvPZ[5]
注意到type還有一個值custom
,我們可以自定義波形(決定聲音的音色),Web Audio提供了一個接口PeriodicWave
來創建一個周期波形。需要注意的是不能直接設置type為custom,而是要通過setPeriodicWave
方法來設置自定義波形。
const real = [0, 0, 1, 0, 1]; // cosine terms const imag = [0, 0, 0, 0, 0]; // sine terms const periodicWave = ac.createPeriodicWave(real, imag); oscillator.setPeriodicWave(periodicWave);
2.4 音頻流
音頻流也可以用來創建一個音頻源節點,常見的音頻流由麥克風、WebRTC產生,通過audioContext.createMediaStreamSource()
方法從音頻流創建一個音頻源節點。
將將其連接到前面的示波器組件,便可以做一個簡單的錄音可視化效果,查看示例[6]。
3. 音頻處理
音頻分析處理相關的API,都可以歸類音頻處理節點,相關的API有很多,這里分別介紹幾個常用API。
3.1 Analyse
AnalyserNode
可以對音頻信號進行實時的快速傅立葉變換(FFT)得到音頻的時域/頻域數據,拿到時域/頻域的數據后,可以實現時域分析、頻域分析、音頻可視化動效等。
通過audioContext.createAnalyser()
方法來創建一個AnalyserNode
,創建之后可以設置一個fftSize屬性,該屬性指定要進行FFT的采樣數據的窗口大小,它的值必須是2的非零整數倍,默認為2048。
const analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteTimeDomainData(dataArray); // analyser.getByteFrequencyData(dataArray);
analyser.frequencyBinCount
是時域/頻域數據的長度,它是fftSize的一半(因為實時音頻信號的傅立葉變換具有對稱性,只需要獲取一半數據即可),通過analyser.getByteTimeDomainData(dataArray)
方法將數據填充到8位無符號整型數組中。
之后,我們就可以使用dataArray進行對應的操作。可以使用上面的示波器組件來繪制一首音樂的波形:
https://codepen.io/JohnsonLeee/pen/poeaWQr[7]
將上面的繪制頻譜圖代碼稍作修改,就可以做一個簡單的音樂播放動效。
https://codepen.io/JohnsonLeee/pen/mdWWgyP[8]
3.2 BiquadFilter
BiquadFilterNode
是個雙二階數字濾波器[9]。濾波器的作用是對特定頻率的輸入信號進行增強/減弱,通常用來控制音調、做均衡器。可以通過audioContext.createBiquadFilter()
方法創建一個雙二階濾波器,它有4個關鍵的參數:type
, frequency
, Q
, gain
。
const filter = ac.createBiquadFilter(); filter.type = 'peaking'; // 設置類型為峰值濾波器 filter.Q.value = 1; // 峰值濾波器的帶寬 filter.frequency.value = 1000; // 中心頻率 filter.gain.value = 5; // 增益,單位db // ... source.connect(filter); filter.connect(ac);
不同類型的濾波器在用法和效果上都有一定的差異,這里以低架濾波器、峰值濾波器、高架濾波器為例寫了一個均衡器Demo[10]。
下表列出了所有的濾波器類型及對應的參數說明:
類型 | 描述 | frequency | Q | gain |
---|---|---|---|---|
lowpass | 低通濾波器,只允許低於設定的值的頻率通過 | 截止頻率 | 值越小減弱越強 | 沒用 |
highpass | 高通濾波器,只允許高於設定的值的頻率通過 | 截止頻率 | 值越小減弱越強 | 沒用 |
bandpass | 帶通濾波器,允許指定范圍內的頻率通過 | 中心頻率 | 控制帶寬,值越小帶寬越大 | 沒用 |
lowshelf | 低架濾波器,對低於設定值的頻率進行增強/減弱,高於的部分不變 | 上限頻率 | 沒用 | 正值增強,負值減弱,單位db |
highshelf | 高架濾波器,對高於設定值的頻率進行增強/減弱,低於的部分不變 | 下限頻率 | 沒用 | 正值增強,負值減弱,單位db |
peaking | 峰值濾波器,在設定范圍內的頻率會被增強/減弱,其它部分不變 | 中心頻率 | 控制帶寬,值越小帶寬越大 | 正值增強,負值減弱,單位db |
notch | 陷波濾波器,也稱作帶阻濾波器,它的作用和帶通濾波器相反 | 中心頻率 | 控制帶寬,值越小帶寬越大 | 沒用 |
allpass | 全通濾波器,不會對輸入信號進行增強/減弱,但是可以改變輸入信號的相位,可以用來做延遲 | 群延遲[11]最大頻率,即發生相變的中心頻率。 | 控制中頻過渡的銳度,值越大過渡銳度越大 | 沒用 |
3.3 Panner
PannerNode
提供了3D空間音頻能力(前提是播放設備必須擁有2個或以上的聲道)。聲音源在人的不同方向,聽到的聲音感受都是不一樣的,甚至在聲音源移動的時候由於多普勒效應[12]會產生一種奇怪的聲音。
而通常使用電子設備播放音樂的時候僅僅是簡單的播放,PannerNode
便為我們提供了模擬現實場景的能力,能夠開發出具有真實聽覺體驗的VR、游戲等場景。
PannerNode工作在右手笛卡爾坐標系中,由位置坐標、朝向向量坐標確定其位置,聲音呈錐形向空間擴散。可以通過設置coneInnerAngle
屬性控制聲源擴散錐形的角度。
使用audioContext.createPanner()
可以創建一個panner節點,它有幾個關鍵的屬性:
- positionX/positionY/positionZ:聲源位置坐標
- orientationX/orientationY/orientationZ:聲源朝向向量
- coneInnerAngle:錐形角度
- rolloffFactor:聲音隨距離的衰減速度
- distanceModel:聲音衰減算法模型
音頻空間化還需要使用到一個AudioListener
,它表示空間中收聽音頻的人。在一個音頻上下文中,僅有一個AudioListener,並且它不是AudioNode的子類,通過audioContext.listener
獲取到。
AudioListener
也擁有幾個類似的屬性:
- forwardX/forwardY/forwardZ:收聽者面部朝向向量
- upX/upY/upZ:收聽者頭部向量
- positionX/positionY/positionZ:收聽者在空間中的坐標
了解了這些API,就可以開始寫一個音頻空間化效果了。
const panner = ac.createPanner(); panner.panningModel = 'HRTF'; // 音頻空間化算法模型 panner.distanceModel = 'inverse'; // 遠離時的音量衰減算法 panner.maxDistance = 500; // 最大距離 panner.refDistance = 50; // 開始衰減的參考距離 panner.rolloffFactor = 1; // 衰減速度 panner.coneInnerAngle = 360; // 聲音360度擴散 panner.orientationX.value = 0; // 聲源朝向x分量 panner.orientationY.value = 0; panner.orientationZ.value = 1; const listener = ac.listener; listener.forwardX.value = 0; // 收聽者面部朝向向量 listener.forwardY.value = 0; listener.forwardZ.value = -1; // 設置與panner朝向相反,即與panner面對面 listener.upX.value = 0; // 收聽着頭部朝向向量 listener.upY.value = 1; listener.upZ.value = 0; source.connect(panner); panner.connect(ac.destination);
播放過程中動態的設置panner和listener的position,即可實現空間化的效果,查看Demo[13]。
3.4 Convolver
ConvolverNode
可以對音頻信號進行線性卷積運算,將特定的脈沖信號與音頻進行卷積運算后,可以獲得一些畸變效果,可以用來做一些環境混響效果。
ConvolverNode
使用比較簡單,通過audioContext.createConvolver()
創建一個卷積器節點,它有兩個屬性:
- buffer:AudioBuffer類型,一個沖激信號(必須是.wav格式的文件)
- normalize:決定是否對buffer的沖激響應進行等功率的歸一化縮放
normalize可能不是很好理解,有興趣的同學可以深入學習,這里簡單寫個示例代碼
const impulse = ac.decodeAudioData(arrayBuffer); // 解碼加載好的沖激信號 const convolver = ac.createConvolver() convolver.buffer = impulse; source.connect(convolver); convolver.connect(ac.destination);
這個API在使用上很簡單,也不要求必須了解相關的理論基礎,只需要找到對應的沖激信號文件。當然,在進行卷積運算后輸出的音頻功率會有所變化,一般情況需要配合GainNode
來調節增益。
查看Demo[14]。
4. 兼容性
Web Audio目前兼容性還不夠完善,可以查看MDN兼容性表格[15],在復雜的應用場景中可能會遇到一些難以解決的問題。
5. 結束語
Web Audio覆蓋的內容很多,本文簡單介紹了一些特性。
因為還在學習中,本文會繼續更新完善。有興趣的同學可以一起交流。
參考資料
[1]
https://web-audio.johnsonlee.site/: https://web-audio.johnsonlee.site/
[2]
音頻路由圖: https://otrlvkfgf2.feishu.cn/docs/doccndPOo08N5Dmc53KJxjhcEsh#fcDQp2
[3]
AudioBuffer: https://developer.mozilla.org/zh-CN/docs/Web/API/AudioBuffer
[4]
https://codepen.io/JohnsonLeee/pen/bGqLXmZ?editors=0010: https://codepen.io/JohnsonLeee/pen/bGqLXmZ?editors=0010
[5]
https://codepen.io/JohnsonLeee/pen/MWpwvPZ: https://codepen.io/JohnsonLeee/pen/MWpwvPZ
[6]
查看示例: https://codepen.io/JohnsonLeee/pen/NWpXWbM
[7]
https://codepen.io/JohnsonLeee/pen/poeaWQr: https://codepen.io/JohnsonLeee/pen/poeaWQr
[8]
https://codepen.io/JohnsonLeee/pen/mdWWgyP: https://codepen.io/JohnsonLeee/pen/mdWWgyP
[9]
雙二階數字濾波器: https://zh.wikipedia.org/wiki/雙二階濾波器
[10]
均衡器Demo: https://web-audio.johnsonlee.site/biquad-filter
[11]
群延遲: https://en.wikipedia.org/wiki/Group_delay_and_phase_delay
[12]
多普勒效應: https://en.wikipedia.org/wiki/Doppler_effect
[13]
查看Demo: https://web-audio.johnsonlee.site/panner
[14]
查看Demo: https://web-audio.johnsonlee.site/convolver
[15]
MDN兼容性表格: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext#browser_compatibility
轉自https://mp.weixin.qq.com/s/8DCFok78lzqgCrp_w5rEmw