如何用FFT确定频率相关振幅?回环测试振幅波动问题
音频设备回环扫频测量振幅波动异常问题
我尝试通过生成输入信号并扫频测量输出的方式计算音频设备的频率响应,实现思路伪代码如下:
for each frequency f in (10-20k): generate reference signal s with frequency f async play s and record result r determine amplitude a of r using FFT add tuple (f,a) to result
对应的Python实现代码:
import numpy as np from matplotlib import pyplot as plt import sounddevice as sd import wave from math import log10, ceil from scipy.fft import fft, rfft, rfftfreq, fftfreq SAMPLE_RATE = 44100 # Hertz DURATION = 3 # Seconds def generate_sine_wave(freq, sample_rate, duration): x = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) frequencies = x * freq y = np.sin((2 * np.pi) * frequencies) return x, y def main(): # info about our device print(sd.query_devices(device="Studio 24c")) # default device settings sd.default.device = 'Studio 24c' sd.default.samplerate = SAMPLE_RATE f_start = 10 f_end = 20000 samples_per_decade = 10 ndecades = ceil(log10(f_end) - log10(f_start)) npoints = ndecades * samples_per_decade freqs = np.logspace(log10(f_start), log10(f_end), num=npoints, endpoint=True, base=10) measure_duration = 0.25 # seconds peaks = [] for f in freqs: _, y = generate_sine_wave(f, SAMPLE_RATE, measure_duration) rec = sd.playrec(y, SAMPLE_RATE, input_mapping=[2], output_mapping=[2]) sd.wait() yf = np.fft.rfft(rec, axis=0) yf_abs = 1 / rec.size * np.abs(yf) xf = np.fft.rfftfreq(rec.size, d=1./SAMPLE_RATE) peaks.append(np.max(yf_abs)) plt.xscale("log") plt.scatter(freqs,peaks) plt.grid() plt.show() if __name__ == "__main__": main()
在测量实际设备前,我将音频接口的输出直接连接到输入做回环校准,预期各频率下振幅一致,但结果波动很大(见附图)。扫频过程中未更改音频接口设置,请问这是什么原因?
问题原因及修正方案
- FFT频率分辨率不匹配:0.25秒的测量时长对应的FFT频率分辨率是4Hz,而对数分布的扫频频率多数无法刚好落在FFT的频率bin中心,导致测得的峰值不是真实振幅。可通过以下方式修正:
- 调整扫频频率,让每个频率都是
SAMPLE_RATE / N的整数倍(N为采样点数,即SAMPLE_RATE * measure_duration) - 延长测量时长(比如1秒)提高分辨率
- 用插值法在FFT结果中估算真实峰值
- 调整扫频频率,让每个频率都是
- 频谱泄漏:未对录制信号加窗,FFT时会出现频谱泄漏,导致能量分散到相邻bin,峰值测量不准。在FFT前给
rec加汉宁窗:window = np.hanning(len(rec)) rec_windowed = rec * window yf = np.fft.rfft(rec_windowed, axis=0) - 振幅归一化错误:实信号的rfft结果中,除DC和Nyquist频率外,其余频率的振幅需要乘以2才能得到正确值,当前归一化系数错误。修正为:
yf_abs = 2 / rec.size * np.abs(yf) # 处理DC分量(索引0) yf_abs[0] /= 2 - 信号同步与瞬态干扰:播放和录制的切换可能存在瞬态冲击,或信号开头/结尾的截断影响结果。给生成的正弦波添加渐入渐出的静音过渡:
def generate_sine_wave(freq, sample_rate, duration, fade=0.1): x = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) frequencies = x * freq y = np.sin((2 * np.pi) * frequencies) # 添加渐入渐出 fade_samples = int(fade * sample_rate) y[:fade_samples] *= np.linspace(0, 1, fade_samples) y[-fade_samples:] *= np.linspace(1, 0, fade_samples) return x, y - 设备自动增益(AGC)干扰:部分音频接口默认开启AGC,会自动调整输入增益导致振幅波动。进入音频接口控制软件,手动关闭自动增益功能,固定输入输出增益。
内容的提问来源于stack exchange,提问作者mefiX
相关产品推荐
相关产品推荐

