聲明: 原創文章,未經允許不得轉載。
音頻可視化是一個“聽”起來非常“美”好的話題,其複雜程度很大程度上依賴視覺方案(一些例子),不同的視覺方案決定了你的技術方案選型,比如three.js,pixi.js等引擎。
不管你選用什麼渲染方案,處理音頻信号部分是相通的,本文會圍繞音頻信号的處理進行闡述,期望能夠給大家普及一下音頻相關的基礎知識(由于能力所限難免疏錯,歡迎指出)。
前五部分主要是一些理論性的基礎概念,如果你不敢興趣可以直接跳過。
三個示例(音頻放在git上加載較慢,需要等久一點):
一、什麼是聲音?
聲音來源于 振動,通過聲波傳播,人耳中無數 毛細胞 會将振動信号轉換成電信号并通過聽覺神經傳遞給大腦,形成人主觀意識上的“聲音”。聲波進入人耳後,因為耳蝸的特殊構造,不同部位對聲音的敏感程度是不一樣的:
高頻聲音會被耳蝸近根部位置所感覺,低頻聲音在近端部位置被感覺,是以人對不同頻率聲的感受是非線性的,這是後續聲學計權的基礎。
二、聲學計權
聲學計權常見的有頻率計權和時間計權,其作用在于模拟人耳對不同頻率聲音的非線性感受:
對低頻部分聲音不敏感;
最靈敏的區域在1~5K Hz之間;
上限在15~20K Hz之間;
人耳聽覺範圍如圖所示:
2.1 頻率計權
頻率計權是作用在音頻信号的頻譜上的,常用的有:A、B、C、D四種:
其中 A計權 是最接近人主觀感受的,它會削弱低頻和高頻部分中人耳不敏感的部分,是以音頻可視化裡要選擇A計權方式,詳細說明可閱讀wiki。
2.2 時間計權
現實裡聲音一般是連續的,人對聲音的主管感受也是聲音累加的結果(想象一下,第一波聲波引起耳膜振動,振動還沒停止,第二波聲音就來了,是以實際耳膜的振動是聲波在時間上累加的結果),時間計權就是就連續時間内聲音的平均值。對于變化較快的信号,我們可以使用125ms的區間來求平均,對于變化緩慢的可以采用1000ms的區間。
三、聲音測量
聲音測量最常用的實體量是聲壓,描述聲壓的大小通常用聲壓級(Sound Pressure Level,SPL)。人耳可聽的聲壓範圍為2×10-5Pa~20Pa,對應的聲壓級範圍為0~120dB。
常見聲音的聲壓
聲壓常常用分貝來度量,這裡要說明一點,分貝本身隻是一種度量方式,代表測量值和參考值的對數比率:
聲壓級的定義:
其中P是測量幅值,P ref代表人耳能聽見1000 Hz的最小聲壓:20 uP。
四、倍頻程
首先,連續的信号包含了大量的資料,我們沒有必要全部處理,是以我們一般會進行采樣,将連續的頻率劃分成一個一個區間來分析,頻程就代表一段頻率區間,倍頻程代表頻率劃分的一種方案。具體來說倍頻程中一段區間的上限頻率與下限頻率之比是常數:
具體可以看這篇文章《什麼是倍頻程》
當N等于1,就是1倍頻程,簡稱倍頻程,如果N等于2,則為1/2倍頻程。頻程劃分好之後,将分布于頻程内的頻譜求均方值得到的就是倍頻程功率譜:
五、webaudio對音頻的處理
在web端做音頻可視化離不開webaudio的API,其中最重要的就是getByteFrequencyData(文檔),這個方法能擷取時域信号轉換之後的頻域信号,詳細過程如下:
擷取原始的時域信号;
對其應用Blackman window(布萊克曼窗函數),其作用是補償DFT造成的信号畸變和能量洩漏;
快速傅裡葉變換,将時域變成頻域;
Smooth over time,這一步是在時間次元對信号進行權重平均(webaudio隻采用了2幀);
按照上文的聲壓公式轉換為dB;
歸一化,webaudio采用的歸一化方式如下:
六、音頻可視化中的信号處理方案
結合上述内容,我們覺得比較合理的處理方式如下:
6.1 濾波
有人會問,getByteFrequencyData内部不是已經應用了窗函數濾波嗎,為什麼還要再濾波?
因為webaudio内部的窗函數主要是用于補償信号畸變和能量洩漏,其參數都是固定的。而在音頻可視化的場景下,往往視覺感受要優先于資料精确性,是以我們加了一個高斯濾波來濾除突刺和平滑信号,“平滑”的程度是可以通過參數任意控制的。
6.2 計權
視覺呈現應該要和人的主觀聽覺關聯,是以計權是必要的,JavaScript的計權實作audiojs/a-weighting。另外我們也提供了額外的時間計權,内部會統計5個曆史資料進行平均。
6.3 頻程劃分
我們會根據傳入的上下限頻率區間和置頂的輸出頻帶數自動進行頻程劃分,核心代碼:
// 根據起止頻譜、頻帶數量确定倍頻數: N
// fu = 2^(1/N)*fl => n = 1/N = log2(fu/fl) / bandsQty
let n = Math.log2(endFrequency / startFrequency) / outBandsQty;
n = Math.pow(2, n); // n = 2^(1/N)
const nextBand = {
lowerFrequency: Math.max(startFrequency, 0),
upperFrequency: 0
};
for (let i = 0; i < outBandsQty; i++) {
// 頻帶的上頻點是下頻點的2^n倍
const upperFrequency = nextBand.lowerFrequency * n;
nextBand.upperFrequency = Math.min(upperFrequency, endFrequency);
bands.push({
lowerFrequency: nextBand.lowerFrequency,
upperFrequency: nextBand.upperFrequency
});
nextBand.lowerFrequency = upperFrequency;
}
七、sound-processor
sound-processor 是一個極小(gzip < 3KB)的處理音頻信号的庫,作為音頻可視化的底層部分,使用相對科學的方法處理原始音頻信号并輸出符合人類主觀聽覺的信号,内部的處理流程如下:
7.1 安裝
npm install sound-processor
7.2 使用
import { SoundProcessor } from "sound-processor";
const processor = new SoundProcessor(options);
// in means original signal
// analyser is the AnalyserNode
const in = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(in);
const out = processor.process(in);
7.3 options
filterParams: 濾波參數,對象,預設undefined,表示不濾波:
sigma:高斯分布的sigma參數,預設為1,表示标準正态分布,sigma越大平滑效果越明顯,一般取0.1~250之間;
radius:濾波半徑,預設為2;
sampleRate:采樣率,可以從webaudio的context中取(audioContext.sampleRate),一般是48000;
fftSize:傅裡葉變換參數,預設為1024;
startFrequency:起始頻率,預設為0;
endFrequency:截止頻率,預設10000,配合startFrequency可以選取任意頻段的信号;
outBandsQty:輸出頻帶數,對應可視化目标的數量,預設為fftSize的一半;
tWeight:是否開啟時間計權,預設為false;
aWeight:是否開啟A計權,預設為true;
7.4 頻率截取
一般音樂的頻率範圍在50~10000 Hz之間,實際中可以取的小一些,比如100~7000 Hz,對于不同風格以及不同樂器的聲音很難取到一個統一的完美區間,另外不同的視覺風格可能也會影響頻率區間。
參考材料