本文深入剖析 LHDC V5 编解码所涉及的核心算法原理。
一、变换编码算法:LD-MDCT
1.1 MDCT 的数学定义
MDCT(Modified Discrete Cosine Transform,改进离散余弦变换)是一种线性正交变换,定义为:
Xk=∑n=0N−1xncosπN(n+N4+12)(k+12),k=0,1,...,N2−1 X_k = \sum_{n=0}^{N-1} x_n \cos\left\\frac{\\pi}{N}\\left(n + \\frac{N}{4} + \\frac{1}{2}\\right)\\left(k + \\frac{1}{2}\\right)\\right, \quad k = 0, 1, \ldots, \frac{N}{2}-1 Xk=n=0∑N−1xncosNπ(n+4N+21)(k+21),k=0,1,...,2N−1
其中:
- NNN = MDCT 尺寸(LHDC V5 中为 480 / 960 / 1920)
- xnx_nxn = 输入时域采样(加窗后)
- XkX_kXk = 输出频域系数(共 N/2N/2N/2 个,临界采样)
关键性质 :MDCT 是临界采样变换(N 点输入 → N/2 点输出),看似有信息损失,但通过 50% 重叠 + 加窗 可以实现完全重构(Perfect Reconstruction),这就是 Princen-Bradley 条件。
1.2 为什么用 MDCT 而不是 DFT/FFT?
| 特性 | DFT/FFT | MDCT |
|---|---|---|
| 输入/输出比 | N→N(冗余) | N→N/2(临界采样) |
| 块效应 | 严重(不重叠时) | 通过重叠相加消除 |
| 频率分辨率 | N 个频点 | N/2 个频点(足够) |
| 编码效率 | 低(冗余系数) | 高(无冗余) |
| 完全重构 | 需要冗余 | 50%重叠+窗即可 |
MDCT 是几乎所有现代音频编码标准(MP3、AAC、Vorbis、Opus、LDAC、LHDC)的核心变换,因为它在频率分辨率和编码效率之间取得了最佳平衡。
1.3 LD-MDCT(低延迟 MDCT)
LHDC V5 使用的 LD-MDCT(Low-Delay MDCT)与标准 MDCT 的区别:
- 标准 MDCT:帧长 = N/2 采样,但分析窗覆盖 N 采样(前后各 N/4 重叠),算法延迟 = N/2 采样
- LD-MDCT:通过特殊的窗函数设计(非对称窗或缩短的前向重叠),可以在保持完全重构的同时降低算法延迟
- LHDC V5 的 5ms 帧长 + LD-MDCT 使其端到端编码延迟远低于传统 AAC/MP3
1.4 快速 MDCT 算法
直接计算 MDCT 的复杂度为 O(N²),实际中通过 FFT 加速到 O(N log N):
N 点 MDCT → N/4 点复数 FFT 的分解步骤:
- 预旋转(Pre-twiddle):将 N 点实输入重新排列并乘以复旋转因子,得到 N/4 点复数序列
- N/4 点 FFT:对复数序列执行快速傅里叶变换
- 后旋转(Post-twiddle):乘以复旋转因子得到 MDCT 系数
- 对称展开:利用 MDCT 的对称性生成完整的 N/2 点输出
LHDC V5 解码器的具体实现 (lhdc_imdct.c):
- N = 480 → N/4 = 120 点复数 FFT
- 120 = 8 × 15 → 基 2 第一级(8 点)+ 15 点第二级
- 15 点 DFT 通过 3×5 Cooley-Tukey 分解实现
- N = 960 → 240 = 16 × 15
- N = 1920 → 480 = 32 × 15
这种混合基分解避免了大素数因子导致的低效 DFT,是实际工程中常用的优化技巧。
1.5 加窗与完全重构
分析窗(编码器) 和 合成窗(解码器) 必须满足 Princen-Bradley 条件:
wa2(n)+ws2(n+N/2)=1,∀n w_a^2(n) + w_s^2(n + N/2) = 1, \quad \forall n wa2(n)+ws2(n+N/2)=1,∀n
当分析窗 = 合成窗时,条件简化为 w2(n)+w2(n+N/2)=1w^2(n) + w^2(n + N/2) = 1w2(n)+w2(n+N/2)=1。
LHDC V5 使用 KBD 窗(Kaiser-Bessel Derived Window):
- 由 Kaiser-Bessel 窗推导而来,具有良好的频域能量集中性
- 主瓣窄、旁瓣衰减快,减少频谱泄漏
- 480 点 KBD 窗预计算为常量表存储在 flash 中,零 RAM 占用
二、感知编码算法:SNS(Spectral Noise Shaping)
2.1 心理声学基础
SNS 的理论基础是人耳的频域掩蔽效应(Frequency-domain Masking):
- 同时掩蔽(Simultaneous Masking):一个强音(掩蔽音)可以使同时存在的弱音(被掩蔽音)变得听不见
- 掩蔽阈值(Masking Threshold):在强音频率附近,听阈会被抬高,低于该阈值的声音不可闻
- 临界频带(Critical Band):人耳的频率分析可以建模为约 24~40 个重叠的带通滤波器(Bark 尺度),每个频带内的掩蔽效应最强
感知编码的核心思想:将量化噪声控制在掩蔽阈值以下,这样噪声虽然客观存在,但主观上不可闻。
2.2 SNS 的算法原理
LHDC V5 的 SNS 不直接计算心理声学模型(如 AAC 中的 PNS/Perceptual Noise Substitution),而是采用一种更简洁的频谱包络整形方法:
步骤 1:子带能量计算
将频谱划分为 MMM 个子带(24 或 32 段,子带边界由 band_off[] 表定义,近似模拟 Bark 尺度),计算每个子带的能量:
Es=∑k∈band s∣Xk∣2×scales E_s = \sum_{k \in \text{band } s} |X_k|^2 \times \text{scale}_s Es=k∈band s∑∣Xk∣2×scales
步骤 2:能量包络平滑
对子带能量进行加权移动平均,得到平滑的频谱包络 PsP_sPs。这模拟了人耳掩蔽效应的扩散性(一个频带的能量会影响相邻频带的掩蔽阈值)。
步骤 3:自适应步长 DPCM 编码(核心)
这是 SNS 最精巧的部分。用一个 ADPCM(Adaptive Differential PCM) 编码器编码频谱包络:
- 状态变量:
state(当前步长索引,0~63),run(连续同方向计数) - 步长表:
JUMP_TABLE[64],从 6 到 4096 指数增长(近似对数等比) - 对每个子带 sss (从第 2 个开始):
- 计算差分: ds=Ps−P^s−1d_s = P_s - \hat{P}_{s-1}ds=Ps−P^s−1 (实际包络与预测值的差)
- 方向位: bs=(ds<0)?1:0b_s = (d_s < 0) ? 1 : 0bs=(ds<0)?1:0 (1 bit,编码方向)
- 状态更新:
- 如果 bs==bs−1b_s == b_{s-1}bs==bs−1 (同方向):
run++,state = min(state + run, 63)(步长增大,加速跟踪) - 如果 bs≠bs−1b_s \neq b_{s-1}bs=bs−1 (方向翻转):
state = (3*state + 2) >> 2,run = 1(步长减小,精细调整)
- 如果 bs==bs−1b_s == b_{s-1}bs==bs−1 (同方向):
- 预测更新: P^s=P^s−1+(−1)bs×JUMP_TABLEstate\hat{P}s = \hat{P}{s-1} + (-1)^{b_s} \times \text{JUMP\_TABLEstate}P^s=P^s−1+(−1)bs×JUMP_TABLEstate
只传输 1 bit/子带的方向信息,解码器用完全相同的 ADPCM 状态机重建频谱包络。
步骤 4:后处理
- 均值移除:消除 DC 偏移
- 非对称移动平均平滑
- 钳位到 -2560, 1024,取反
步骤 5:增益应用
将每个子带的缩放因子 sfssf_ssfs 转换为增益 gs=2sfs/16g_s = 2^{sf_s / 16}gs=2sfs/16 (通过查找表),对该子带内所有频谱系数乘以 gsg_sgs 。
2.3 SNS 的本质
SNS 本质上是一种频域预测 + 自适应量化的组合:
- 用 ADPCM 编码频谱包络(1 bit/子带,极低成本)
- 用包络信息对频谱进行预加重/去加重(pre-emphasis/de-emphasis)
- 强能量子带被衰减(降低该子带的信号幅度,从而降低量化噪声的绝对水平,但噪声/信号比不变------因为信号也被衰减了,逆量化后恢复)
- 弱能量子带被提升到噪声底以上(避免"音洞")
与 AAC 的对比:
- AAC 使用完整的心理声学模型(计算扩散函数、绝对听阈、掩蔽阈值),然后根据阈值分配每个频带的量化步长
- LHDC V5 的 SNS 更简洁,用 ADPCM 包络编码替代了完整的心理声学模型,计算量更小,适合蓝牙低延迟场景
- 两者的目标一致:将量化噪声控制在听阈以下
三、量化与码率控制算法
3.1 均匀标量量化
LHDC V5 使用均匀标量量化(Uniform Scalar Quantization):
qk=round(Xk×L) q_k = \text{round}(X_k \times L) qk=round(Xk×L)
其中 LLL 是全局量化步长(由 global_gain 索引通过分段线性函数 e(g)e(g)e(g) 转换为 L=2e(g)L = 2^{e(g)}L=2e(g) )。
为什么用均匀量化而不是非均匀量化(如 μ-law / A-law)?
- 均匀量化在频域系数上更有效:经过 SNS 整形后,频谱系数的动态范围已经被压缩,均匀量化足够
- 均匀量化的逆量化简单( Xk=qk/LX_k = q_k / LXk=qk/L ),计算量小
- 非均匀量化更适合时域信号(如电话语音的 μ-law),因为时域信号的动态范围大且小信号出现概率高
3.2 全局增益的分段线性映射
global_gain(9 bit,0~511)到量化步长指数 eee 的映射是分段线性函数:
e(g)={g,g≤splitsplit+(g−split)×slope,split<g≤503e(503)+(g−504),g>503 e(g) = \begin{cases} g, & g \leq \text{split} \\ \text{split} + (g - \text{split}) \times \text{slope}, & \text{split} < g \leq 503 \\ e(503) + (g - 504), & g > 503 \end{cases} e(g)=⎩ ⎨ ⎧g,split+(g−split)×slope,e(503)+(g−504),g≤splitsplit<g≤503g>503
split和slope由帧长、位深、采样率决定- 低索引区(g ≤ split):每档 1 bit(精细调节,用于低码率)
- 中索引区:斜率 < 1(粗调,用于中高码率,大步进覆盖大范围)
- 高索引区(g > 503):每档 1 bit,钳位到 30
这种分段设计在有限的 9 bit 索引范围内实现了大范围的步长调节,同时在关键区域保持精细分辨率。
3.3 码率控制循环(Rate Loop)
编码器的核心挑战:找到最优的全局量化步长,使得编码后的比特数恰好等于目标比特数。
LHDC V5 使用迭代搜索 + 牛顿法加速的码率控制:
初始化 offset_size(从上一帧预测)
循环:
1. 用当前 offset_size 对应的步长量化频谱
2. 统计非零系数个数 nzc
3. 估算编码这些系数需要的比特数(算术编码估算 + 尾数估算)
4. 比较估算值与目标范围:
- 超出 target_range → 增大 offset_size(减小步长,减少比特)
- 低于 target_range_bottom → 减小 offset_size(增大步长,增加比特)
- 在范围内 → 收敛,退出
5. 用牛顿法更新 offset_size:
offset = fast_calc_step(...) # 估算步长-比特曲线的斜率
offset_size += (estimated_bits - target_range) / offset
fast_calc_step 利用前两次迭代的结果估算 d(bits)/d(offset)d(\text{bits})/d(\text{offset})d(bits)/d(offset),实现牛顿法快速收敛(通常 3~5 次迭代即可收敛)。
3.4 系数比特数预测(coef3 / coef7)
为了精确估算编码比特数,需要预测每个非零系数的尾数位数。使用两种一阶 IIR 预测器:
coef3:pi=3⋅pi−1+5⋅bi8 \text{coef3}: \quad p_i = \frac{3 \cdot p_{i-1} + 5 \cdot b_i}{8} coef3:pi=83⋅pi−1+5⋅bi
coef7:pi=7⋅pi−1+bi8 \text{coef7}: \quad p_i = \frac{7 \cdot p_{i-1} + b_i}{8} coef7:pi=87⋅pi−1+bi
其中 bi=log2(∣qi∣)≪7b_i = \log_2(|q_i|) \ll 7bi=log2(∣qi∣)≪7(Q7 定点的系数幅值比特数)。
- coef3:快响应(5/8 权重给当前值),跟踪快速变化
- coef7:慢响应(7/8 权重给历史值),平滑稳定
- 编码器选择累计绝对预测误差更小的预测器,用 1 bit
symbol_flag通知解码器
这是一种自适应预测编码(Adaptive Predictive Coding)的思想,与语音编码中的 ADPCM(如 G.726)同源。
四、熵编码算法:FAC 算术编码 + Rice 编码
4.1 算术编码基础
算术编码(Arithmetic Coding)是一种熵编码方法,与霍夫曼编码相比,能更接近信源的熵极限(特别是当符号概率不是 2 的负整数次幂时)。
基本原理:
- 将输入符号序列映射到实数区间 [0, 1) 中的一个子区间
- 每个符号有一个概率区间 [low, high)
- 编码时不断缩小区间:
[low, high) = [low + range*cum_low, low + range*cum_high) - 最终输出区间内任意一个数的二进制表示
自适应算术编码 :概率模型不是固定的,而是根据已编码符号的统计动态更新。LHDC V5 使用滑动窗口自适应模型(Sliding Window Adaptive Model):
- 维护最近
window个符号的频率统计 - 新符号加入窗口,最老的符号移出
- 频率归一化到 2^15 = 32768
4.2 FAC(Fast Adaptive arithmetic Coding)
LHDC V5 的 FAC 编码器是一种三符号(ternary)自适应算术编码器,专门优化用于三进制符号流 {0, 1, 2}:
两个独立模型:
-
模型 1(Stream 1 / Marker Plane):
- 初始频率:{54, 12, 1}(符号 0 概率最高,约 80%)
- 滑动窗口:
ma_win1(48k 时约 57) - 编码前
nzc个符号(marker plane,标记哪些系数需要 escape 编码)
-
模型 2(Stream 2 / Quotient):
- 初始频率:{90, 16, 1}(符号 0 概率更高,约 84%)
- 滑动窗口:
ma_win2 = ma_win1 × 8(约 456,更长的记忆) - 编码 Rice 商值的三进制展开
范围解码的 Xtensa 优化 :
标准范围解码需要计算 target = code / (range >> 15),但 Xtensa LX6 没有硬件除法指令(软件除法约 30+ 周期)。解码器使用恒等式优化:
⌊coder⌋≥c ⟺ code≥r×c \left\lfloor \frac{\text{code}}{r} \right\rfloor \geq c \iff \text{code} \geq r \times c ⌊rcode⌋≥c⟺code≥r×c
用乘法替代除法,每次符号解码最多 2 次乘法(三符号),比特精确一致。
4.3 Rice 编码
Rice 编码是一种针对拉普拉斯分布(Laplacian Distribution)的前缀码,特别适合编码预测残差和量化系数。
基本 Rice 编码:
- 将非负整数 xxx 编码为:q=⌊x/2k⌋q = \lfloor x / 2^k \rfloorq=⌊x/2k⌋ 个 1 + 1 个 0 + kkk bit 余数
- kkk 是 Rice 参数,需根据信号分布选择
LHDC V5 的 Rice 商编码 :
LHDC V5 不是直接使用标准 Rice 编码,而是将 Rice 商值展开为三进制符号流,然后用 FAC 算术编码。具体展开规则:
- 商值 q=0q = 0q=0 → 符号 0
- 商值 q=1q = 1q=1 → 符号 1
- 商值 q=2q = 2q=2 → 符号 1, 1
- 商值 q=3q = 3q=3 → 符号 1, 1, 1
- 商值 q≥4q \geq 4q≥4 → 符号 2(escape)+ 2 bit 低位(循环)+ ... + 连续 1 的个数 + 0 结束
这种三进制展开 + 算术编码的组合比标准 Rice 编码的压缩效率更高,因为算术编码可以精确匹配符号概率,而 Rice 编码只能匹配几何分布。
4.4 尾数位平面编码(Mantissa Bit-Plane)
算术编码只编码了系数的高位(Rice 商),低位(尾数)使用因果预测位平面编码:
shiftk=IIR(calc_bits(∣Mk−1∣)) \text{shift}_k = \text{IIR}(\text{calc\bits}(|M{k-1}|)) shiftk=IIR(calc_bits(∣Mk−1∣))
Mk=(qk≪shiftk)∣mantissa_bits M_k = (q_k \ll \text{shift}_k) | \text{mantissa\_bits} Mk=(qk≪shiftk)∣mantissa_bits
shift_k:当前系数的尾数位数,由前一系数的幅值通过 IIR 预测器预测- 预测器模式(flag2 选择):
- 快模式:
pred = (5*x + 3*pred) >> 3 - 慢模式:
pred = (7*pred + x) >> 3
- 快模式:
- 每个非零系数后跟 1 bit 符号位
这是一种上下文相关的位平面编码(Context-dependent Bit-Plane Coding),利用了频谱系数幅值的相关性(相邻系数幅值通常相近),用前一系数预测当前系数的尾数位数,减少需要传输的位数。
五、帧结构与加扰算法
5.1 帧结构
┌─────────────┬─────────────────────────┬─────────────────────────┐
│ 帧头 (2B) │ 声道 0 编码数据 (N B) │ 声道 1 编码数据 (N B) │
│ u16 LE │ │ │
└─────────────┴─────────────────────────┴─────────────────────────┘
帧头:低 10 位 = payload_len / 2,高 6 位 = 标志位。
每个声道数据内部结构:
┌──────────────────────────────────────────────────────────────┐
│ 前导信息 (Leading Section) │
│ flag(1b) + global_gain(9b) + sns_mode(4b) + 方向位 + │
│ nzc(7~9b) + symbol_flag(1b) + resid(0/4b) │
├──────────────────────────────────────────────────────────────┤
│ FAC 算术编码流(三进制符号流 → 字节流) │
├──────────────────────────────────────────────────────────────┤
│ 尾数 + 符号位平面 │
├──────────────────────────────────────────────────────────────┤
│ 填充(0 填充到固定帧长) │
└──────────────────────────────────────────────────────────────┘
5.2 加扰(Scrambling)
每个声道前 8 字节加扰:
- 异或固定掩码:
byte[i] ^= XOR_MASK[i],XOR_MASK = {0xFF, 0xE7, 0x7A, 0xB3, 0xDA, 0xE5, 0xCD, 0x73} - 字节重排:按
SCRAMBLE_ORDER[sel][]排列,sel = byte[8] & 1(两种排列)
加扰的目的(不是加密):
- 避免编码后的帧头出现固定模式(如全 0 或固定同步字),便于蓝牙接收器的同步和误码检测
- 破坏数据中的长 0/长 1 序列,有利于基带的白化和时钟恢复
- 两种排列由数据本身选择,提供一定的鲁棒性
六、算法复杂度与数学基础总结
| 算法模块 | 数学基础 | 复杂度 | 研究领域 |
|---|---|---|---|
| LD-MDCT | 线性代数、傅里叶分析、滤波器组理论 | O(N log N) | 数字信号处理、多速率信号处理 |
| 快速 IMDCT | FFT 算法、Cooley-Tukey 分解、数论 | O(N log N) | 算法设计、计算数学 |
| SNS | 心理声学、ADPCM、预测编码、自适应滤波 | O(M)(M=子带数) | 音频信号处理、感知编码 |
| 均匀量化 | 量化理论、率失真理论(Rate-Distortion) | O(N/2) | 信息论、信源编码 |
| 码率控制 | 优化理论、牛顿法、二分搜索 | O(K × N/2)(K=迭代次数) | 优化算法、控制理论 |
| 系数预测 | 线性预测、IIR 滤波器、自适应滤波 | O(N/2) | 数字信号处理、预测编码 |
| FAC 算术编码 | 信息论、熵编码、概率模型 | O(S)(S=符号数) | 信息论、数据压缩 |
| Rice 编码 | 前缀码、拉普拉斯分布、Golomb 编码 | O(S) | 信息论、数据压缩 |
| 尾数位平面 | 上下文建模、位平面编码、因果预测 | O(N/2) | 图像/视频编码、嵌入式编码 |
| 加窗重叠相加 | 滤波器组理论、完全重构条件 | O(N) | 数字信号处理、多速率系统 |
总的来说,LHDC V5 编解码算法是数字信号处理 + 信息论 + 心理声学的交叉产物,核心算法包括:
- LD-MDCT:时频变换(傅里叶分析 + 滤波器组理论)
- SNS:感知频谱整形(心理声学 + ADPCM 自适应预测)
- 均匀量化 + 码率控制:率失真优化(信息论 + 数值优化)
- FAC 算术编码 + Rice 编码:熵编码(信息论 + 数据压缩)
- 尾数位平面编码:上下文相关位平面编码(预测编码)
音频编码是一个既有深厚理论基础、又有广泛工程应用的领域,随着无线音频、空间音频、AI 语音的发展,该方向的人才需求将持续增长。