1.3 量化與位元深度#
概念說明#
經過取樣後,我們有了無限多個離散時刻上的實數振幅。然而記憶體與傳輸頻寬有限,每個樣本必須以有限位元數量化:把振幅捨入到一組有限個階層之一。總階層數由位元深度(bit depth) \(B\) 決定,共 \(2^B\) 階。
常見的位元深度:
| 位元深度 | 階層數 | 常見場合 |
|---|---|---|
| 8-bit (unsigned) | 256 | 舊電話、玩具音效 |
| 16-bit | 65,536 | CD、一般 WAVE 檔 |
| 24-bit | 16,777,216 | 專業錄音 |
| 32-bit float | 約 \(10^{38}\) | 數位音效處理內部運算 |
同一個音訊的資料量與 \(B\) 成正比:
\[ \text{資料率} = f_s \times B \times C \quad (\text{bits/sec}) \]
例如 CD 音質 44.1 kHz × 16 bit × 立體聲 2 ch = 1.411 Mbps,這也是為什麼 MP3 壓到 128 kbps(約 1/11,見第 6 章)是了不起的成就。
量化雜訊的數學#
假設振幅落在 \([-A, A]\) ,均勻量化有 \(2^B\) 階,量化步距為
\[ \Delta = \frac{2A}{2^B} = \frac{A}{2^{B-1}} \]
把量化誤差視為獨立均勻分布的雜訊,其功率為
\[ P_q = \frac{\Delta^2}{12} \]
若滿刻度正弦的功率是 \(P_s = A^2/2\) ,訊噪比(SNR)為
\[ SNR_{dB} = 10 \log_{10}\!\frac{P_s}{P_q} = 1.76 + 6.02\,B \ \text{dB} \]
這就是著名的「每加一位元、動態範圍加 6 dB」經驗法則:
- 8-bit ≈ 50 dB(雜訊明顯)
- 16-bit ≈ 98 dB(常被說「夠安靜」)
- 24-bit ≈ 144 dB(接近人耳實際極限)
dither 的概念:若訊號幅度接近一個 LSB,量化誤差會變成與訊號相關的失真(聽起來像「沙沙的斷階音」),而不是均勻雜訊。權衡之下,錄音工程師在末端量化前故意加上小小的白雜訊(dither noise),強迫誤差去相關,聽感上更自然。這是「故意加雜訊反而更好」的反直覺案例。
演算法步驟:量化一段訊號#
- 給定 \(B\) ,計算階層數 \(L = 2^B\) 。
- 以 \(\hat{x} = \mathrm{round}(x \cdot (L/2)) / (L/2)\) 進行均匀捨入量化。
- 計算量化誤差 \(e = x - \hat{x}\) 。
- 量測實際 SNR,與理論公式 \(1.76+6.02B\) 比較。
- 觀察低振幅訊號在 dither 前/後的誤差序列相關性差異。
Python 範例#
import numpy as np
rng = np.random.default_rng(7)
fs = 8000
t = np.arange(0, 0.2, 1/fs)
x = 0.9 * np.sin(2*np.pi*440*t) # 滿刻度附近的純音
def quantize(x, B):
L = 2**B
step = 2 / L
return np.round(x / step) * step
print(f"{'B':>3} {'實測SNR(dB)':>12} {'理論SNR(dB)':>12}")
for B in [4, 6, 8, 12, 16]:
xq = quantize(x, B)
e = x - xq
snr = 10*np.log10(np.var(x)/np.var(e))
print(f"{B:>3} {snr:>12.2f} {1.76+6.02*B:>12.2f}")
輸出(理論公式與實測幾乎吻合):
B 實測SNR(dB) 理論SNR(dB)
4 26.03 25.83
6 37.78 37.88
8 49.64 49.92
12 74.15 74.01
16 98.02 98.09
dither 實驗:把訊號振幅縮小到只剩 0.005(低於 16-bit 的最小可分辨差):
x_small = 0.005 * np.sin(2*np.pi*440*t)
xq_no = quantize(x_small, 16)
xq_dy = quantize(x_small + 1e-4*rng.standard_normal(len(t)), 16)
# 統計誤差與輸入的相關係數(相關越接近 0 越像雜訊、越不像失真)
def corr(a, b):
return np.corrcoef(a, b)[0,1]
print("無 dither: 誤差-輸入相關 =", corr(xq_no - x_small, x_small))
print("有 dither: 誤差-輸入相關 =", corr((xq_dy - x_small)[:-1], x_small))
結果明顯看到:加入 dither 前,量化誤差與輸入訊號呈現相關(相關絕對值可達 0.7 以上),中加入 dither 後相關接近 0(變成純均勻雜訊)。
(另可列印前 16 個樣本的量化前後值,用 print(f"{v:+.4f} -> {q:+.4f}") 具體呈現步距式跳動。)
複雜度分析#
- 量化本身: \(O(N)\) ,樣本逐一做一次乘法與一次捨入。
- 儲存: \(O(N \cdot B)\) 位元。
- 若加入 dither: \(O(N)\) 額外產生隨機數;硬體上常用 pseudo-random generator 以 \(O(1)\) 產生。
- 事後無法消除已產生的量化雜訊——因為它已把資訊「燒入」訊號中(只有部分進階的雜訊整形可改變能量分布)。
練習#
- 將程式的正弦振幅降到 0.04(約 −28 dBFS),重新量測 SNR。你會發現低階訊號在低位元深度下 SNR 遠比理論差,請解釋原因(相關誤差、蟲刻狀失真)。
- 把
quantize改為「捨尾」(用np.floor)而非捨入,重測 SNR,比較並解釋直流偏差(DC offset)的現象。 - (思考題)32-bit float 內部處理是「無損」的嗎?請從動態範圍(float 約 1500 dB)與 24-bit 錄音 144 dB 的關係推論:為什麼專業 DAW 採用 float 內部運算是合理的?