You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何PCM音频分块乘窗函数后输出出现机器人化失真?

音频加窗后出现机器人失真的原因及问题排查

首先得说,你遇到的机器人化失真,核心问题出在窗函数的计算错误,再加上分块拼接的方式不对,双重作用下就导致了信号严重畸变。咱们一步步拆解:

1. 窗函数完全写错了!

你手动写的这个公式:

y.append(1 / (15.96 - 14.56 * math.cos((2 * math.pi * x) / (1024 - 1))))

完全不是汉宁(Hanning)或汉明(Hamming)窗的正确计算方式。

标准的汉明窗公式是:
w(n) = 0.54 - 0.46 * cos(2πn/(N-1))
汉宁窗是:
w(n) = 0.5 * (1 - cos(2πn/(N-1)))

而你写的公式,相当于构造了一个反向的衰减曲线:当n在块中间时(cos项接近1),分母是15.96-14.56=1.4,1/1.4≈0.714,信号会被衰减;当n在块边缘时(cos项接近-1),分母是15.96+14.56=30.52,1/30.52≈0.0327,信号被大幅衰减——这样整个块的信号都被压得极低,而且中间比边缘衰减少,把原本连续的语音信号,每个块都变成“中间强、两边弱”的奇怪形状,拼接起来自然就像机器人说话一样。

正确的做法是直接用NumPy内置的窗函数:np.hamming(1024)或者np.hanning(1024),既准确又省事,根本不需要手动推导公式。

2. 分块拼接的方式有问题

就算窗函数写对了,你现在的分块逻辑也会导致失真:

  • 你在音频开头插入了256个0的chunk,这会让音频开头多一段静音,而且打乱了原信号的分块起始位置。
  • 你用的是无重叠分块+直接拼接,而加窗处理的正确姿势是用重叠分块+重叠相加法(Overlap-Add)。因为汉宁/汉明窗的两端振幅接近0,如果无重叠直接拼接,块与块之间会出现明显的信号断层,听起来就会断断续续像机器人。用50%左右的重叠,再把每个加窗后的块重叠部分相加,就能抵消窗函数带来的边缘衰减,恢复连续的语音信号。

3. 其他小细节

  • 分块数计算时,len(samples)/1024得到的是浮点数,直接用来循环会出问题,应该用int(np.ceil(len(samples)/1024))来确保覆盖所有样本。
  • 处理时最好先把int16的样本转成float32,避免乘法运算时的溢出问题,最后再转回int16。

修正后的代码示例

我给你改了一份正确的汉明窗处理代码,你可以试试:

import numpy as np
from scipy.io import wavfile
import matplotlib.pyplot as plt

# 读取音频,转成float避免溢出
sample_rate, samples = wavfile.read('nearendimpulse.wav')
samples = samples.astype(np.float32)

# 设置分块参数:1024块大小,50%重叠
block_size = 1024
overlap = block_size // 2
num_blocks = int(np.ceil((len(samples) - overlap) / (block_size - overlap)))

# 创建标准汉明窗
window = np.hamming(block_size)

final_data = []
for i in range(num_blocks):
    start = i * (block_size - overlap)
    end = start + block_size
    # 提取当前块,不足的补0
    block = samples[start:end]
    if len(block) < block_size:
        block = np.pad(block, (0, block_size - len(block)), mode='constant')
    # 加窗处理
    windowed_block = block * window
    final_data.append(windowed_block)

# 用重叠相加法拼接信号
final_voice = np.zeros(len(samples) + block_size)
for i in range(num_blocks):
    start = i * (block_size - overlap)
    final_voice[start:start+block_size] += final_data[i]

# 截断到原音频长度,转回int16
final_voice = final_voice[:len(samples)].astype(np.int16)

# 保存处理后的音频
wavfile.write('output_final_fixed.wav', sample_rate, final_voice)

# 绘图对比
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 8))
ax1.plot(samples)
ax1.set_title('Original Audio Signal')
ax2.plot(window)
ax2.set_title('Standard Hamming Window')
ax3.plot(final_voice)
ax3.set_title('Processed Audio Signal')
plt.tight_layout()
plt.show()

总结一下失真原因

  1. 最核心的错误:手动编写的窗函数公式完全不符合汉宁/汉明窗的定义,构造了一个反向衰减的曲线,严重扭曲了每个音频块的幅度分布。
  2. 分块拼接逻辑错误:无重叠直接拼接加窗后的块,导致信号出现断层,加剧了失真效果。

内容的提问来源于stack exchange,提问作者Khubaib Ahmad

火山引擎 最新活动