3. 分帧与加窗

任务目标

对一段语音信号执行分帧(帧长25ms、帧移10ms),并对每帧应用汉明窗,对比加窗前后单帧波形,验证分帧参数与窗函数效果。

环境依赖

需要 numpy、matplotlib。若需读取真实语音文件,另需 scipy.io.wavfile(本案例使用合成信号,不依赖外部文件)。

核心实现

import numpy as np
import matplotlib.pyplot as plt

# ---------- 参数设置 ----------
sr = 16000                # 采样率 16kHz
frame_len_ms = 25         # 帧长 25ms
frame_shift_ms = 10       # 帧移 10ms
frame_len = int(sr * frame_len_ms / 1000)    # 400 点
frame_shift = int(sr * frame_shift_ms / 1000) # 160 点

# ---------- 生成测试信号(含两个频率分量,便于观察加窗效果) ----------
t = np.arange(sr * 0.1) / sr          # 100ms 信号
signal = 0.6 * np.sin(2 * np.pi * 440 * t) + 0.4 * np.sin(2 * np.pi * 1800 * t)

# ---------- 分帧 ----------
n_frames = 1 + (len(signal) - frame_len) // frame_shift
frames = np.stack([signal[i * frame_shift : i * frame_shift + frame_len]
                   for i in range(n_frames)])

# ---------- 汉明窗 ----------
hamming_win = np.hamming(frame_len)
frames_win = frames * hamming_win

# ---------- 取第 3 帧(索引2)对比加窗前后 ----------
idx = 2
frame_raw = frames[idx]
frame_win = frames_win[idx]

# ---------- 可视化 ----------
time_axis = np.arange(frame_len) / sr * 1000   # 毫秒

plt.figure(figsize=(10, 6))

plt.subplot(2, 1, 1)
plt.plot(time_axis, frame_raw, color='#4C72B0')
plt.title(f'Frame {idx} — Before Windowing')
plt.xlabel('Time (ms)')
plt.ylabel('Amplitude')
plt.grid(alpha=0.3)

plt.subplot(2, 1, 2)
plt.plot(time_axis, frame_win, color='#C44E52')
plt.title(f'Frame {idx} — After Hamming Window')
plt.xlabel('Time (ms)')
plt.ylabel('Amplitude')
plt.grid(alpha=0.3)

plt.tight_layout()
plt.savefig('frame_windowing.png', dpi=120)
plt.show()

# ---------- 验证输出 ----------
print(f'采样率: {sr} Hz')
print(f'帧长: {frame_len} 点 ({frame_len_ms}ms)')
print(f'帧移: {frame_shift} 点 ({frame_shift_ms}ms)')
print(f'信号总长度: {len(signal)} 点 ({len(signal)/sr*1000:.1f}ms)')
print(f'总帧数: {n_frames}')
print(f'第{idx}帧加窗前能量: {np.sum(frame_raw**2):.4f}')
print(f'第{idx}帧加窗后能量: {np.sum(frame_win**2):.4f}')

运行方式

直接运行脚本即可。程序会生成 frame_windowing.png 图像,并在控制台打印分帧参数与能量统计。

验证方法

  • 分帧正确性:控制台输出帧长为400点、帧移160点、总帧数符合公式 1 + (len(signal)-frame_len)//frame_shift。信号长度1600点,计算得总帧数 = 1 + (1600-400)//160 = 8,输出应为8。
  • 加窗效果:观察图像——加窗前帧两端幅值不为零,加窗后帧两端平滑衰减至接近0,中间部分保持原波形形状。第3帧(索引2)起始采样点索引为 2×160=320,该帧原始信号两端有明显非零值,加窗后两端被抑制。
  • 能量变化:汉明窗系数平方和约为0.397(对400点),因此加窗后能量约为加窗前的0.397倍。控制台打印的两帧能量比值应接近此值(因信号非平稳,存在小幅偏差)。

常见问题

  • 帧数计算:当信号长度不是帧移整数倍时,末尾不足一帧的余量被丢弃,这是标准做法。
  • 窗函数选择:汉明窗主瓣较宽但旁瓣衰减大,适合语音分析;若需更高频率分辨率可换用矩形窗,但频谱泄漏更明显。
  • 信号边界:若使用真实语音文件,需先读取并转为单声道float数组,再替换 signal 变量即可。