共计 2617 个字符,预计需要花费 7 分钟才能阅读完成。
背景:为什么需要非线性量化?
当我们想把麦克风采集的模拟声音信号转换成数字信号时,直接使用均匀量化会遇到大问题:
- 小声细节会被淹没在量化噪声中(比如轻声说话时的气音)
- 大声部分又浪费了太多量化精度(爆炸声和正常说话用同样的量化间隔)
这就好比用固定大小的格子装东西——装大象时格子太小装不下,装蚂蚁时格子太大浪费空间。A 律压缩就是解决这个动态范围问题的 ” 智能格子 ” 方案。
13 折线:A 律的工程简化版本
数学上 A 律公式长这样:
F(x) = sign(x) * A|x|/(1+lnA) 当 |x| < 1/A
F(x) = sign(x) * (1+ln(A|x|))/(1+lnA) 当 1/A <= |x| <= 1
但硬件实现时常用 13 段折线来近似这个曲线(正负区域各 6 段直线 + 1 段过零点):
- 靠近零点的 4 段:斜率相同(相当于均匀量化)
- 中间到外围的 3 段:斜率逐段减半
- 正负对称共形成 13 个转折点

(示意图:横轴输入幅度,纵轴量化后值,显示各段斜率变化)
Python 手把手实现
核心量化函数
import numpy as np
def a_law_quantize(signal, bits=8):
"""
参数:signal: 归一化到 [-1,1] 的输入信号
bits: 量化位数(常用 8 位)返回:量化后的整数值(0-255)"""
signal = np.clip(signal, -1, 1) # 安全截断
# 13 折线参数
segments = [0, 1/128, 1/64, 1/32, 1/16, 1/8, 1/4, 1/2, 1] # 正半轴分段点
slopes = [16, 16, 8, 4, 2, 1, 1/2, 1/4] # 各段斜率
quantized = np.zeros_like(signal)
for i in range(len(signal)):
x = abs(signal[i])
sign = 1 if signal[i] >=0 else -1
# 确定所在分段
seg = 0
while seg < len(segments)-1 and x > segments[seg+1]:
seg += 1
# 计算段内量化值
delta = (segments[seg+1] - segments[seg]) / (2**(bits-1)/8)
code = int((x - segments[seg]) / delta)
# 合成最终编码(最高位表示符号)quantized[i] = sign * (seg * (2**(bits-1)//8) + code)
return np.clip(quantized + 128, 0, 255).astype(np.uint8) # 转为无符号 8 位
反量化函数
def a_law_dequantize(quantized):
"""将量化值恢复为模拟信号"""
quantized = quantized.astype(np.int16) - 128 # 转回有符号
segments = [0, 1/128, 1/64, 1/32, 1/16, 1/8, 1/4, 1/2, 1]
slopes = [16, 16, 8, 4, 2, 1, 1/2, 1/4]
reconstructed = np.zeros_like(quantized, dtype=np.float32)
for i in range(len(quantized)):
code = abs(quantized[i])
sign = 1 if quantized[i] >=0 else -1
seg = code // 16 # 每段 16 个量化间隔
if seg >= len(slopes): seg = len(slopes)-1
delta = (segments[seg+1] - segments[seg]) / 16
x = segments[seg] + (code % 16) * delta
reconstructed[i] = sign * x
return reconstructed
测试案例:正弦波量化
# 生成测试信号
fs = 8000 # 采样率
t = np.arange(0, 1, 1/fs)
signal = 0.3 * np.sin(2*np.pi*440*t) + 0.1 * np.sin(2*np.pi*3000*t)
# 量化与恢复
quantized = a_law_quantize(signal)
reconstructed = a_law_dequantize(quantized)
# 绘制对比
import matplotlib.pyplot as plt
plt.figure(figsize=(10,4))
plt.plot(t[:100], signal[:100], label='原始信号')
plt.plot(t[:100], reconstructed[:100], '--', label='恢复信号')
plt.legend()
plt.title('A 律 13 折线量化效果对比')
plt.show()
工程调参经验
- 分段阈值优化:
- 语音信号重点优化小幅度区域(前 4 段)
-
音乐信号可能需要调整第 5 - 6 段转折点
-
位数选择:
- 8 位:电话级语音(SNR 约 38dB)
- 12 位:广播级音频
-
16 位:CD 音质(实际采用 μ 律)
-
定点数加速技巧:
// 嵌入式系统中可以用查表法 const uint8_t segment_map[256] = {0,0,0,0,...,1,1,1,...}; uint8_t quantize(int16_t sample) {uint8_t seg = segment_map[abs(sample)]; return (seg << 4) | ((abs(sample) >> (seg+3)) & 0x0F); }
进阶:量化噪声分析
用 FFT 观察噪声分布:
from scipy.fft import fft
error = reconstructed - signal
freq = np.linspace(0, fs, len(error))
plt.semilogy(freq[:fs//2], abs(fft(error))[:fs//2])
plt.xlabel('Frequency (Hz)')
plt.ylabel('Noise Power')
plt.title('量化噪声频谱')
你会发现噪声能量主要集中在高频段——这正是非线性量化的聪明之处:把噪声推到人耳不敏感的区域。
动手挑战
尝试用这个算法处理真实 WAV 文件:
1. 用 scipy.io.wavfile.read 加载音频
2. 观察不同音乐类型的量化误差
3. 比较 A 律与直接 8 位量化的音质差异
你会发现:钢琴的高频泛音量化误差会比人声更大,这是因为 13 折线对快速变化的信号压缩更剧烈。在实际电话系统中,会配合 300-3400Hz 的带通滤波器来优化这个问题。
正文完
