You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python实现WAV音频拉伸/压缩失效,输出音频混乱求助

音频时间拉伸/压缩代码功能异常的修复方案

尝试将WAV文件拉伸至2倍(理论支持任意k值),但输出的output.wav未被拉伸且音频混乱;k<1的压缩场景同样失效。代码已能运行无报错,但功能不符合预期,作为音频处理新手不知如何解决。

原代码

import numpy as np
from scipy.io import wavfile

sampling_rate, signal = wavfile.read("test.wav")

NFFT = 1024
overlap_factor = 4
stretch_factor = 2

hop_size = int(NFFT / overlap_factor)
num_hops = int(np.ceil(float(len(signal)) / hop_size))
pad_size = num_hops * hop_size - len(signal)
z = np.zeros((pad_size,))
pad_signal = np.concatenate((signal, z))

frames = np.array_split(pad_signal, num_hops)

output = np.zeros((0,), dtype=np.float32)
previous_phase = np.zeros((NFFT,), dtype=np.float32)
summed_phase = np.zeros((NFFT,), dtype=np.float32)

for i, frame in enumerate(frames):
    spectrum = np.fft.fft(frame, n=NFFT)
    magnitude = np.abs(spectrum)
    phase = np.angle(spectrum)
    
    frequencies = np.fft.fftfreq(NFFT, d=1.0/sampling_rate)
    frequencies = np.repeat(frequencies, stretch_factor)
    phase_diff = (frequencies[1] - frequencies[0]) * hop_size / sampling_rate

    expected_phase = previous_phase + phase_diff * 2 * np.pi
    delta_phase = phase - expected_phase

    previous_phase = phase.copy()
    summed_phase += delta_phase
    phase_advances = hop_size * summed_phase / (2 * np.pi)
    phase_advances = np.round(phase_advances).astype(int)

    new_spectrum = np.zeros((NFFT,), dtype=np.complex64)
    for j in range(NFFT):
        j2 = j + phase_advances[j]
        if j2 >= 0 and j2 < NFFT:
            new_spectrum[j2] += spectrum[j]
    new_frame = np.fft.ifft(new_spectrum).real

    output = np.concatenate((output, new_frame[:hop_size]))

wavfile.write("output.wav", sampling_rate, output.astype(np.int16))

原代码核心问题

  • 时间轴调整逻辑缺失:未根据stretch_factor修改输出帧的数量或步长,导致总输出长度与输入一致,完全无法实现拉伸/压缩效果。
  • 相位计算逻辑错误:重复频率轴无实际意义,相位差计算未结合拉伸因子,导致相位累积混乱,还原音频时出现严重失真。
  • 频谱处理方式错误:通过移位频谱调整相位的做法不符合相位声码器标准流程,破坏了频率成分的对应关系,引发音频混乱。

修复后的代码

import numpy as np
from scipy.io import wavfile

# 读取音频并处理立体声
sampling_rate, signal = wavfile.read("test.wav")
if len(signal.shape) > 1:
    signal = signal.mean(axis=1)
signal = signal.astype(np.float32)

# 参数配置
NFFT = 1024
overlap_factor = 4
stretch_factor = 2  # >1为拉伸,<1为压缩

# 输入帧参数
hop_size_input = int(NFFT / overlap_factor)
# 输出帧参数:根据拉伸因子调整步长
hop_size_output = int(hop_size_input / stretch_factor)

# 补零使输入信号长度适配输入帧步长
pad_length = (len(signal) + hop_size_input - 1) // hop_size_input * hop_size_input - len(signal)
pad_signal = np.pad(signal, (0, pad_length), mode='constant')

# 生成带重叠的输入帧
frames = np.array([pad_signal[i:i+NFFT] for i in range(0, len(pad_signal)-NFFT+1, hop_size_input)])
num_frames = len(frames)

# 初始化相位相关变量(仅保留正频率,减少计算量)
prev_phase = np.zeros(NFFT // 2 + 1, dtype=np.float32)
cum_phase = np.zeros(NFFT // 2 + 1, dtype=np.float32)
freqs = np.fft.fftfreq(NFFT, d=1/sampling_rate)[:NFFT//2+1]

# 初始化输出信号与汉明窗(减少频谱泄漏和拼接失真)
window = np.hamming(NFFT)
output_length = int(len(pad_signal) * stretch_factor)
output = np.zeros(output_length, dtype=np.float32)
output_hop_count = 0

for i in range(num_frames):
    frame = frames[i] * window
    spectrum = np.fft.rfft(frame)  # 实信号FFT,仅返回正频率
    mag = np.abs(spectrum)
    phase = np.angle(spectrum)

    # 计算输入帧的预期相位变化
    phase_diff = freqs * hop_size_input / sampling_rate * 2 * np.pi
    expected_phase = prev_phase + phase_diff
    # 计算相位差并取模2π,避免累积溢出
    delta_phase = phase - expected_phase
    delta_phase = np.mod(delta_phase + np.pi, 2*np.pi) - np.pi

    # 更新累积相位与上一帧相位
    cum_phase += delta_phase + phase_diff
    prev_phase = phase.copy()

    # 根据拉伸因子生成对应数量的输出帧
    output_phase_inc = freqs * hop_size_output / sampling_rate * 2 * np.pi
    loop_count = int(stretch_factor) if stretch_factor >=1 else 1
    for _ in range(loop_count):
        # 构建输出频谱:幅度不变,相位基于累积值
        output_spectrum = mag * np.exp(1j * cum_phase)
        # 逆FFT生成输出帧并加窗
        output_frame = np.fft.irfft(output_spectrum) * window
        # 重叠相加到输出信号
        start = output_hop_count * hop_size_output
        end = start + NFFT
        if end <= len(output):
            output[start:end] += output_frame
        # 更新累积相位与输出步长计数
        cum_phase += output_phase_inc
        output_hop_count +=1
        # 压缩场景下提前终止,避免输出过长
        if stretch_factor <1 and output_hop_count * hop_size_output >= len(output):
            break

# 归一化输出到int16范围,避免削波失真
output = output / np.max(np.abs(output)) * 32767
wavfile.write("output.wav", sampling_rate, output.astype(np.int16))

关键修改说明

  • 立体声处理:自动将双声道音频转为单声道,避免维度不匹配问题。
  • 输出步长适配:根据stretch_factor动态调整输出帧步长,拉伸时生成更多帧,压缩时减少帧数量,确保输出长度符合预期。
  • 加窗与重叠相加:使用汉明窗减少频谱泄漏,重叠相加法保证帧拼接时音频平滑过渡。
  • 正确相位计算:基于正频率计算相位差与累积相位,结合拉伸因子调整输出相位增量,保证相位连续性,消除音频混乱。
  • 归一化处理:将输出信号映射到int16范围,避免音频削波失真。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 03:07:58