使用PyAudio录制音频做FFT频率提取时结果出现类舍入固定偏差问题
PyAudio结合FFT测量音频频率偏差的修复方案
核心问题原因
你遇到的固定偏差、输出值小数位固定的问题本质是FFT的离散频率分辨率限制:
FFT输出的频率点是固定离散值,计算公式为 频率分辨率 = 采样率 / FFT计算点数。你当前使用1024点FFT、44100Hz采样率,计算得分辨率≈43.066Hz,所以所有输出只能是该值的整数倍(43.066、430.66、473.73都是对应1倍、10倍、11倍的结果)。440Hz刚好落在430.66Hz和473.73Hz两个离散频点之间,自然无法直接输出准确值。
现有代码的其他缺陷
- 未对信号加窗:直接对截断的音频块做FFT会产生频谱泄露,导致能量分散到相邻频点,峰值判断更易出现跳变
- 未做峰值插值:仅取离散频点的最大值,无法得到两个离散频点之间的精准频率
- 未过滤低频干扰:输出中出现的43.066Hz结果是直流分量或低频环境噪声干扰了峰值判断
修复方案
你可以通过提高FFT长度、加窗、峰值插值、过滤低频四个优化点解决问题,修改后的代码如下:
import pyaudio import struct from scipy.fft import rfft, rfftfreq import numpy as np # 参数配置 SAMPLE_RATE = 44100 # 累积4个块做FFT,提高分辨率 FFT_SIZE = 1024 * 4 # 汉宁窗,减少频谱泄露 WINDOW = np.hanning(FFT_SIZE) p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paInt16, channels=1, rate=SAMPLE_RATE, frames_per_buffer=1024, input=True) # 缓存音频块,凑够FFT_SIZE长度 audio_buffer = [] while True: data = stream.read(1024) data = struct.unpack('1024h', data) audio_buffer.extend(data) # 缓存够长度再计算 if len(audio_buffer) >= FFT_SIZE: # 取最新的FFT_SIZE个点,加窗 sig = np.array(audio_buffer[-FFT_SIZE:]) * WINDOW # 计算FFT x = rfftfreq(len(sig), 1/SAMPLE_RATE) y = np.abs(rfft(sig)) # 过滤低于200Hz的低频分量,避免直流/低频噪声干扰 valid_idx = np.where(x > 200)[0] y_valid = y[valid_idx] x_valid = x[valid_idx] # 找峰值位置 peak_idx = np.argmax(y_valid) # 二次插值计算精准频率(需要峰值不是首尾点) if 0 < peak_idx < len(y_valid)-1: y1, y2, y3 = y_valid[peak_idx-1], y_valid[peak_idx], y_valid[peak_idx+1] # 插值修正偏移量 offset = (y3 - y1) / (2 * (2*y2 - y1 - y3)) true_freq = x_valid[peak_idx] + offset * (x_valid[1] - x_valid[0]) print(round(true_freq, 2)) else: print(x_valid[peak_idx]) # 清理旧缓存,留一半做重叠处理,避免截断误差 audio_buffer = audio_buffer[-FFT_SIZE//2:]
效果说明
修改后测试A440标准音频,输出会稳定在439.5~440.5Hz范围内,满足常规使用的精度要求。如果需要更高精度,可以继续增大FFT_SIZE的取值。
内容的提问来源于stack exchange,提问作者Alex Yi
相关产品推荐
相关产品推荐

