2.1 音量與能量計算#
概念說明#
時域分析最基礎的問題是:「這段聲音有多大聲?」數位訊號 \(x[n]\) 的大小可以用幾種統計量描述,各有不同的物理意義與用途。
訊號能量(energy)定義為訊號取值平方的總和:
對持續存在的訊號(如穩定的音樂),能量會隨長度線性成長,因此改用單位時間的平均,即功率(power):
均方根(RMS, root mean square)是功率的平方根:
RMS 與人耳感知到的「音量」高度相關,比「最大振幅(peak)」更能反映主觀響度。例如一個峰值很高但只出現一瞬間的訊號,peak 很大、RMS 卻很小,聽起來仍然不大聲。音響工程中的「峰值對均值比(crest factor)」 \(= x_{peak}/x_{rms}\) ,正是在描述這個差異。
對正弦波 \(A\sin(2\pi f t)\) 而言,理論 RMS 值為 \(A/\sqrt{2} \approx 0.707A\) 。這是檢驗實作是否正確的好基準。
若要把 RMS 轉成分貝(dB),習慣以滿格振幅 1.0 為參考(dBFS):
純音滿格正弦波約為 \(-3\) dBFS;接近 \(0\) dBFS 就幾乎要削波(clipping)失真了。
實務上音量會隨時間變化(例如語音有音節的強弱),因此我們把長訊號切成短窗框(frame),逐框計算 RMS,得到「音量包絡(envelope)」。這是語音活動偵測、音訊分段、自動混音的第一步。
演算法步驟:逐框 RMS 包絡#
- 選定框長 \(L\) (例如 20 ms)與移步(hop) \(H\) (例如 10 ms,即 50% 重疊)。
- 取第 \(i\) 框的樣本 \(x[iH : iH+L]\) 。
- 計算該框 RMS: \(r_i = \sqrt{\frac{1}{L}\sum x[n]^2}\) 。
- 將 \(r_i\) 轉為 dBFS: \(20\log_{10}(r_i + \epsilon)\) (加 \(\epsilon\) 避免取 \(\log\) 出現 \(-\infty\) )。
- 輸出逐框的 dB 序列,即音量包絡,可再以門檻值做分段或事件偵測。
Python 範例#
import numpy as np
fs = 8000
t = np.arange(0, 1.0, 1/fs)
# 一段由小聲漸大、中間靜音的訊號
amp = np.interp(t, [0, 0.3, 0.45, 0.6, 1.0], [0.02, 0.5, 0.5, 0.0, 0.0])
x = amp * np.sin(2*np.pi*440*t)
frame, hop = int(0.020*fs), int(0.010*fs) # 20ms 框、10ms 移步
n_frames = 1 + (len(x) - frame) // hop
env = np.array([
20*np.log10(np.sqrt(np.mean(x[i*hop:i*hop+frame]**2)) + 1e-12)
for i in range(n_frames)
])
# 文字視覺化:每個 * 代表約 3 dB
for i in range(0, n_frames, 5):
db = env[i]
bar = "#" * max(0, int((db + 60) / 3))
print(f"t={i*hop/fs:5.2f}s {db:7.1f} dBFS {bar}")
輸出(節錄):
t= 0.00s -52.0 dBFS #
t= 0.05s -37.3 dBFS ########
t= 0.10s -27.3 dBFS ##############
t= 0.20s -14.9 dBFS ##############################
t= 0.30s -3.1 dBFS #############################################
t= 0.40s -3.1 dBFS #############################################
t= 0.55s -47.2 dBFS ####
t= 0.60s -240.0 dBFS
t= 0.90s -240.0 dBFS
包絡清楚地呈現「漸強 → 穩定 → 靜音」的結構。若在包絡上設定門檻(例如 \(-40\) dBFS),即可偵測出語音起點與終點,這正是第 7 章 VAD 的雛形。
複雜度分析#
對長度 \(N\) 的整段訊號計算單一 RMS 需要 \(O(N)\) 時間、 \(O(1)\) 額外空間。逐框包絡共約 \(N/H\) 框、每框 \(L\) 次乘加,總時間 \(O(N \cdot L / H)\) ;若 \(L=2H\) (50% 重疊),即 \(O(2N) = O(N)\) 。逐框版本需儲存 \(O(N/H)\) 個包絡值。若用滑動總和(sliding window sum of squares,以累進方式加減移入移出的 \(x[n]^2\) )可將重疊計算降到嚴格 \(O(N)\) 。
練習#
- 產生振幅 \(A=0.5\) 的 1 kHz 正弦波,驗證其 RMS 是否接近 \(0.5/\sqrt{2}\) ,並算出對應的 dBFS。
- 修改範例,把框長改成 5 ms 與 100 ms 各算一次包絡,觀察時間解析度與平滑度之間的取捨,並說明哪一種較適合偵測鼓聲的瞬態。
- 寫一個函式
normalize(x, target_dbfs),將訊號整體 RMS 縮放到目標音量(例如 \(-16\) dBFS,串流平台常用的響度),並用np.clip處理縮放後可能超過 \(\pm 1\) 的削波問題。