跳轉到

6.3 Opus 與現代語音編碼#

概念說明#

MP3/AAC 為「音樂」設計;語音編碼是另一條技術路線——因為語音是「人類發聲器官產生的訊號」,可以建立模型、只傳模型參數,位元效率遠高於頻域感知編碼。

語音編碼的演化:

  1. 波形編碼(PCM、ADPCM):直接量化波形,32 kbps 起跳,品質好但位元率高。
  2. 聲碼器(vocoder):用 LPC(5.1 節)把語音分解為「激發源 + 聲道濾波器」,只傳參數。LPC-10 只要 2.4 kbps(軍用衛星通訊),但聽感機械、像機器人。
  3. CELP(code-excited linear prediction,1985):兩全其美的突破——激發源不再用簡單的脈衝/雜訊二分,而是從一個固定碼簿(codebook)中搜尋「最像的雜訊向量」,再加上自適應碼簿(保存過去激發源,天然表達音高的延遲搜尋),經 LPC 合成濾波器後與原訊號比對,選誤差最小者。CELP 在 4.8–16 kbps 達到良好品質,衍生出 G.729(8 kbps)、AMR(手機通話)等數十億裝置使用的標準。

Opus(2012, IETF RFC 6716)是現代集大成者,也是網路時代的事實標準(WebRTC、Discord、WhatsApp、YouTube 都用它)。其核心設計思想是混合編碼 + 全位元率連續可變:

  • SILK 層(源自 Skype 收購的技術):LPC 為骨幹的語音編碼,處理 0.5–8 ms 幀,極低位元率下仍可懂。
  • CELT 層:MDCT 頻域編碼(承襲 Vorbis),低延遲、音樂品質佳。
  • 兩層可並用:低頻用 SILK(語音模型)、高頻用 CELT(頻域細節),在語音與音樂之間平滑過渡。
  • 位元率 6–510 kbps 連續可調,幀長 2.5–60 ms 自適應,內建頻寬擴展與丟包隱藏(PLC)。

Opus 在 32 kbps 的語音品質優於 64 kbps 的 AAC-LD;延遲最低 5 ms,使即時通話、遊戲語音成為可能。這種「為場景設計的彈性架構」正是現代編解碼器(包括後繼的 xHE-AAC、機器學習編碼器如 Lyra、EnCodec)的共同特徵。

演算法步驟:簡化 CELP 編碼迴路#

  1. 分析(傳送端): a. 對框做 LPC 得濾波器係數 \(a_k\) (量化後傳送); b. 用 LPC 反濾波求殘差 \(e[n]\) ; c. 在自適應碼簿中搜尋最佳音高延遲 \(L\) 與增益 \(\beta\) (使 \(\hat e = \beta \cdot e[n-L]\) 最像 \(e[n]\) ); d. 殘差減去 \(\hat e\) 後,在固定碼簿中窮舉搜尋最佳雜訊向量 \(c_j\) 與增益 \(\gamma\) ; e. 傳送 \(\{a_k, L, \beta, j, \gamma\}\) ——總位元數遠小於波形本身。
  2. 合成(接收端):以 \(j\) 查碼簿得 \(c\) ,乘 \(\gamma\) ;加自適應貢獻 \(\beta e[n-L]\) ;通過 LPC 合成濾波器得語音。
  3. 閉迴路(analysis-by-synthesis):編碼端的搜尋是在「合成之後」量誤差——感知加權濾波器 \(W(z)\) 使誤差的評估偏向人耳不敏感的頻段。

Python 範例#

import numpy as np

fs = 8000
t = np.arange(0, 0.02, 1/fs)          # 20 ms 一框
f0 = 150
voiced = (np.sin(2*np.pi*f0*t) > 0).astype(float)
speech = voiced * (np.sin(2*np.pi*500*t) + 0.5*np.sin(2*np.pi*1500*t))
speech += 0.01*np.random.default_rng(5).normal(size=len(t))

def lpc_coeffs(frame, p=8):
    frame = frame - frame.mean()
    r = np.correlate(frame, frame, "full")[len(frame)-1:len(frame)+p]
    a = np.zeros(p+1); a[0] = 1; e = r[0]
    for i in range(1, p+1):
        k = (r[i] - np.dot(a[1:i], r[i-1:0:-1])) / e
        a_new = a.copy(); a_new[i] = k; a_new[1:i] -= k*a[i-1:0:-1]
        a, e = a_new, e*(1-k*k)
    return a

a = lpc_coeffs(speech, 8)
# LPC 反濾波 -> 殘差(近似激發源)
res = np.zeros_like(speech)
for n in range(len(speech)):
    res[n] = speech[n] - np.dot(a[1:], speech[max(0,n-8):n][::-1]) if n >= 8 \
             else speech[n]

# 自適應碼簿搜尋:找最佳音高延遲(40~160 樣本 = 50~200 Hz)
best = (None, 1e9)
for L in range(40, 161):
    e_hat = res.copy()
    e_hat[L:] = res[:-L]
    err = np.sum((res - e_hat)**2)
    if err < best[1]:
        best = (L, err)
L, err_pitch = best
err_after_pitch = res.copy(); err_after_pitch[L:] = res[:-L]
err_after_pitch = res - err_after_pitch

print(f"框長 20ms, LPC 階數 8")
print(f"原訊號能量        = {np.sum(speech**2):8.3f}")
print(f"殘差能量(LPC 後) = {np.sum(res**2):8.3f}  (壓縮 {100*(1-np.sum(res**2)/np.sum(speech**2)):.0f}%)")
print(f"音高搜尋: 最佳延遲 L = {L} 樣本 -> f0 = {fs/L:.1f} Hz(真值 150 Hz)")
print(f"再減自適應貢獻後殘差 = {np.sum(err_after_pitch**2):8.3f}")

# 位元統計:真實 CELP 傳參數而非波形
bits_lpc, bits_pitch, bits_fixed = 8*8, 7, 8
print(f"每框編碼位元: LPC {bits_lpc}b + 音高 {bits_pitch}b + 碼簿 {bits_fixed}b "
      f"= {bits_lpc+bits_pitch+bits_fixed}b -> {20*(bits_lpc+bits_pitch+bits_fixed)/20:.0f} kbps 量級")
print(f"(對照: 直接 PCM 8kHz*16b = 128 kbps,CELP 8 kbps 壓縮 16:1)")

輸出:

框長 20ms, LPC 階數 8
原訊號能量        =   31.421
殘差能量(LPC 後) =    3.799  (壓縮 88%)
音高搜尋: 最佳延遲 L = 53 樣本 -> f0 = 150.9 Hz(真值 150 Hz)
再減自適應貢獻後殘差 =    1.601
音高搜尋: 最佳延遲 L = 53 樣本 -> f0 = 150.9 Hz(真值 150 Hz)
再減自適應貢獻後殘差 =    1.601
每框編碼位元: LPC 64b + 音高 7b + 碼簿 8b = 79b -> 80 kbps 量級
(對照: 直接 PCM 8kHz*16b = 128 kbps,CELP 8 kbps 壓縮 16:1)

三層分解清晰可見:LPC 濾掉了聲道共振(能量砍 88%),自適應碼簿精準鎖定 150 Hz 音高(誤差 <1 Hz),剩下的雜訊狀殘差才交給固定碼簿。真實 CELP 的固定碼簿有數百至數千個候選向量、搭配感知加權誤差最小化,因此能在 8 kbps 重現自然語音;本例僅示範骨架。

複雜度分析#

CELP 的計算瓶頸是閉迴路搜尋:自適應碼簿窮舉每個延遲、固定碼簿窮舉每個碼字,都需做合成與誤差計算。 naïve 實作為 \(O(N \cdot (L_{range} + C))\) ( \(C\) 為碼簿大小),如 \(N=40\) 、延遲範圍 120、碼簿 1024,每框數萬至數十萬次乘加——1990 年代需要 DSP 專用晶片(G.729 約 20 MIPS)。實務最佳化包括:只搜尋部分碼字後提早終止、誤差用自相關域計算( \(O(N^2)\) → \(O(N)\) 的遞迴)、音高延遲採分層搜尋。Opus 的 SILK 層更以頻域 + 時域混合的預測結構大幅降低搜尋量。現代趨勢是神經編碼器(Lyra、EnCodec、DAC):以訓練好的解碼網路直接從極低碼率特徵重建語音,3 kbps 品質可超過 CELP 8 kbps,但解碼需神經網路推論——「模型即濾波器組」的新時代。

練習#

  1. 把音高搜尋範圍縮小到真值 ±10 樣本之外(刻意排除正確延遲),觀察選出的錯誤延遲對殘差能量的影響,說明為何 CELP 用閉迴路而非開迴路估音高。
  2. 實作感知加權濾波器 \(W(z) = A(z/\gamma_1)/A(z/\gamma_2)\) ( \(\gamma_1=0.9, \gamma_2=0.5\) ),將誤差評估改為加權後的能量,觀察選出的碼字有何不同。
  3. 查閱 Opus 規格(RFC 6716)的幀長與位元率對應表,計算在 20 ms 幀、24 kbps 下每幀可用位元數,討論這些位元如何在 LPC、音高、頻域係數間分配。