Skip to content

B.1 升采样:从低速到高速

一段语音用 8 kHz 采样保存,每秒 8000 个样本。播放设备要求 16 kHz,每秒要读 16000 个样本。语音内容没变,问题是样本点不够——设备一秒钟想要 16000 个数,文件里只有 8000 个。

最容易想到的办法,是在两个旧样本之间补一个新样本。真正的问题也在这里:补什么?

先试最省事的办法:补零。

假设原始序列只有 8 个样本:

x[n]=[1,2,3,4,5,6,7,8]x[n] = [1, 2, 3, 4, 5, 6, 7, 8]

做 2 倍升采样,在每两个样本之间插一个零:

xu[n]=[1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0]x_u[n] = [1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0]

这个操作叫插零(zero-stuffing)。序列长度变成原来的 2 倍,采样间隔从 TT 变成了 T/2T/2。如果用一般的 LL 倍升采样表示,就是

xu[n]={x[n/L],n=0,±L,±2L,0,其他位置x_u[n] = \begin{cases} x[n/L], & n = 0, \pm L, \pm 2L, \cdots \\ 0, & \text{其他位置} \end{cases}

L=2L=2,按 n=0,1,2,n=0,1,2,\cdots 记下标,原来的样本落在 0,2,4,0,2,4,\cdots 这些偶数位置,新插入的点在中间。MATLAB 下标从 1 开始,写程序时对应到第 1、3、5、… 个元素。

图B.1-1:插零前后的时域序列
图B.1-1:插零前后的时域序列

插零确实把采样率翻倍了,但结果还没法用。把插零后的序列当波形看,每个原始样本后面都突然掉到 0,再从 0 跳到下一个样本。放在语音里,多出来一些高频毛刺;放在图像里,类比放大后边缘很生硬。插零只是把新采样点的位置排出来了,新位置上应该是什么合理的数值,它还没给。

时域里的跳变,对应频域里的高频成分。把插零前后的序列做 DTFT,可以看到:原来的频谱被压到低频区域,同时高频区域出现了一份额外的复制品。

对 2 倍升采样,频谱关系是

Xu(ejω)=X(ej2ω)X_u(e^{j\omega}) = X(e^{j2\omega})

把插零定义代入 DTFT,2ω2\omega 来自下标换元:

Xu(ejω)=n=xu[n]ejωn=m=xu[2m]ej2ωm=m=x[m]ej2ωm=X(ej2ω)X_u(e^{j\omega}) = \sum_{n=-\infty}^{\infty} x_u[n]e^{-j\omega n} = \sum_{m=-\infty}^{\infty} x_u[2m]e^{-j2\omega m} = \sum_{m=-\infty}^{\infty} x[m]e^{-j2\omega m} = X(e^{j2\omega})

第二个等号只保留偶数位置。奇数位置上的 xu[n]x_u[n] 都是 0,对求和没有贡献;偶数位置写成 n=2mn=2m,并且 xu[2m]=x[m]x_u[2m]=x[m]。指数项随之变成 ejω(2m)=ej2ωme^{-j\omega(2m)}=e^{-j2\omega m},于是得到 X(ej2ω)X(e^{j2\omega})

关键在自变量 2ω2\omega。当 ω\omegaπ/2-\pi/2 走到 π/2\pi/2 时,2ω2\omega 正好走完整个 [π,π][-\pi, \pi],原来占满 [π,π][-\pi, \pi] 的频谱就被压缩到了 [π/2,π/2][-\pi/2, \pi/2]。离散时间频谱以 2π2\pi 为周期,压缩后相邻周期的副本也会落进新的 [π,π][-\pi, \pi] 范围内。这个副本就叫镜像频谱(image)。

图B.1-2:插零后的频谱镜像
图B.1-2:插零后的频谱镜像

还是拿 8 kHz 语音升到 16 kHz 来看。原信号在 8 kHz 采样率下无歧义频率范围是 [4kHz,4kHz][-4\text{kHz}, 4\text{kHz}],升到 16 kHz 后新的无歧义范围变成 [8kHz,8kHz][-8\text{kHz}, 8\text{kHz}]。原有内容仍然只占低频那一块;插零带来的镜像,会落到新的高频区域。

时域里的毛刺和频域里的高频镜像说的是同一件事。要让波形平滑,就要把这些镜像去掉。

去除镜像用低通滤波器。它保留低频里的原始信息,压掉高频里的镜像。

对 2 倍升采样,原始频谱被压缩到 [π/2,π/2][-\pi/2, \pi/2],镜像从 π/2\pi/2 附近开始出现。理想低通滤波器的截止频率取

ωc=π2\omega_c = \frac{\pi}{2}

LL 倍升采样时,原始频谱压缩到 [π/L,π/L][-\pi/L, \pi/L],截止频率对应变成

ωc=πL\omega_c = \frac{\pi}{L}
图B.1-3:升采样低通滤波器
图B.1-3:升采样低通滤波器

滤波器通带增益要取 LL。用常数序列就能看出来为什么。原来全是 1 的序列,2 倍插零后变成

[1,0,1,0,1,0,][1, 0, 1, 0, 1, 0, \cdots]

平均值变成了原来的一半。低通滤波把它平滑成常数时,如果通带增益还是 1,结果会接近 0.50.5;要恢复到原来的幅度,就要乘以 2。LL 倍升采样的道理一样,插值低通的通带增益取 LL

经过低通滤波后,插入位置就不再是零了,而是由邻近样本共同决定的平滑值。时域上是在做插值,频域上是在去镜像,两种说法指的是同一个操作。

图B.1-4:低通滤波前后
图B.1-4:低通滤波前后

表B.1-1:不同升采样倍数对应的滤波器参数

升采样倍数 LL截止频率 ωc\omega_c通带增益
2π/2\pi/22
3π/3\pi/33
4π/4\pi/44

截止频率不是越高越好,它要卡在原始频谱和镜像之间。放得太高,镜像会漏出来;放得太低,原始信号本来有用的高频内容也会被削掉。

把 8 kHz 语音升到 16 kHz,完整流程是

图B.1-5:升采样系统链路
图B.1-5:升采样系统链路

升采样倍数为

L=160008000=2L = \frac{16000}{8000} = 2

先在每两个样本之间插一个零:

原始插零后
[x[0], x[1], x[2], x[3], ...][x[0], 0, x[1], 0, x[2], 0, x[3], 0, ...]

再设计低通滤波器:截止频率 π/2\pi/2,通带增益 2。滤波后,新插入位置得到合理的中间值,输出序列的采样率就是 16 kHz。

下面是一个 2 倍升采样的 MATLAB 实现。fir1 里的截止频率按 Nyquist 频率归一化,cutoff = 1/L 对应数字频率 π/L\pi/L。函数假设输入是单通道序列;如果输入是列向量,输出也保持为列向量。

function y = upsample_2x(x)
L = 2;
wasColumn = iscolumn(x);
x = x(:).';
x_upsampled = zeros(1, L * length(x));
x_upsampled(1:L:end) = x;
numtaps = 51;
cutoff = 1 / L;
h = fir1(numtaps - 1, cutoff);
h = L * h;
y = conv(x_upsampled, h, 'same');
if wasColumn
y = y.';
end
end

两个步骤对应代码里的两行:x_upsampled(1:L:end) = x 完成插零,conv(x_upsampled, h, 'same') 完成低通滤波。取 51 点 FIR 是为了得到奇数长度的线性相位滤波器,群延迟是 (511)/2=25(51-1)/2=25 个样本。numtaps 越大,过渡带越窄,镜像压制越好,计算量和延迟也跟着涨。'same' 让输出长度保持为插零后的长度。实际处理音频时还要补偿群延迟,开头和结尾的边界样本也要留意。

工程里也可以用 interpresample。这里手写是为了把两个动作拆开看清楚:插零把采样位置扩展出来,低通滤波去掉镜像并补出中间值。频域上去掉的是镜像,时域上得到的是插值后的平滑波形。