跳轉到

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),強迫誤差去相關,聽感上更自然。這是「故意加雜訊反而更好」的反直覺案例。

演算法步驟:量化一段訊號#

  1. 給定 \(B\) ,計算階層數 \(L = 2^B\) 。
  2. 以 \(\hat{x} = \mathrm{round}(x \cdot (L/2)) / (L/2)\) 進行均匀捨入量化。
  3. 計算量化誤差 \(e = x - \hat{x}\) 。
  4. 量測實際 SNR,與理論公式 \(1.76+6.02B\) 比較。
  5. 觀察低振幅訊號在 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)\) 產生。
  • 事後無法消除已產生的量化雜訊——因為它已把資訊「燒入」訊號中(只有部分進階的雜訊整形可改變能量分布)。

練習#

  1. 將程式的正弦振幅降到 0.04(約 −28 dBFS),重新量測 SNR。你會發現低階訊號在低位元深度下 SNR 遠比理論差,請解釋原因(相關誤差、蟲刻狀失真)。
  2. 把 quantize 改為「捨尾」(用 np.floor)而非捨入,重測 SNR,比較並解釋直流偏差(DC offset)的現象。
  3. (思考題)32-bit float 內部處理是「無損」的嗎?請從動態範圍(float 約 1500 dB)與 24-bit 錄音 144 dB 的關係推論:為什麼專業 DAW 採用 float 內部運算是合理的?