如何用Python检测特定频率组合并响应?sounddevice代码求助
问题与需求
正在使用sounddevice开发类老式调制解调器的Python脚本,通过声音实现脚本实例间通信。已完成DTMF生成器、二进制转换器等收发功能,但无法检测440Hz+350Hz的拨号音,无法实时监听DTMF、数据等声音并响应。
刚接触sounddevice和numpy,仅会生成并播放指定时长的正弦波,接收部分依赖ChatGPT生成的代码(见下方),但该代码要么无响应要么持续报错。尝试自行开发但难以理解官方文档,需要:
- 代码调试帮助
- 解释InputStream的工作原理及声音检测方法
import sounddevice as sd import numpy as np # Parameters target_frequencies = [440, 350] # Frequencies to detect (440Hz and 350Hz) duration = 15 # Duration in seconds sample_rate = 44100 # Sample rate # Callback function for audio input def audio_callback(indata, frames, time, status): # Convert audio data to mono mono_data = np.mean(indata, axis=1) # Compute the Fast Fourier Transform (FFT) fft_data = np.fft.fft(mono_data) freqs = np.fft.fftfreq(len(fft_data), 1 / sample_rate) # Find the indices of the target frequencies target_indices = [np.argmin(np.abs(freqs - freq)) for freq in target_frequencies] # Check if the target frequencies are present if all(abs(fft_data[index]) > 10000 for index in target_indices): print("yo yo yo") # Start recording with sd.InputStream(callback=audio_callback, channels=2, samplerate=sample_rate): print("Listening for tones...") sd.sleep(int(duration * 1000)) # Record for the desired duration print("Recording finished")
解答
一、InputStream工作原理
sd.InputStream是sounddevice实现实时音频捕获的核心组件,工作流程如下:
- 初始化时指定采样率、通道数、回调函数等参数,启动后会持续从音频输入设备捕获数据
- 系统会按固定的缓冲区大小(可通过
blocksize参数指定)积累音频帧,每攒够一帧数据就调用一次回调函数 - 回调函数的核心参数说明:
indata:捕获到的原始音频数据,是形状为(frames, channels)的numpy数组frames:当前回调处理的音频帧数量status:捕获状态标识,可用于排查音频溢出、设备异常等问题
二、原代码问题分析与修复
原代码无法正常工作的核心原因是FFT处理逻辑和阈值设置不合理:
- 固定阈值不可靠:
abs(fft_data)的绝对值与输入音量、缓冲区大小直接相关,固定值10000无法适配不同场景 - FFT结果未做对称处理:FFT输出是对称的,后半部分对应负频率,无需处理
- 频率分辨率不稳定:未指定
blocksize,缓冲区大小由系统动态分配,导致频率定位误差
修复后的代码
import sounddevice as sd import numpy as np import sys # 参数配置 target_frequencies = [440, 350] sample_rate = 44100 # 固定缓冲区大小,平衡响应速度与频率分辨率(2048帧对应~21.5Hz分辨率) blocksize = 2048 # 相对阈值:目标频率幅值需达到当前块最大幅值的30%以上 threshold_ratio = 0.3 def audio_callback(indata, frames, time, status): # 捕获异常状态输出 if status: print(status, file=sys.stderr) # 转换为单声道数据 mono_data = np.mean(indata, axis=1) # 计算FFT并仅保留正频率部分 fft_result = np.fft.fft(mono_data)[:frames//2] freq_axis = np.fft.fftfreq(frames, 1/sample_rate)[:frames//2] # 计算幅值谱并归一化 amp_spectrum = np.abs(fft_result) max_amp = np.max(amp_spectrum) normalized_amp = amp_spectrum / max_amp if max_amp > 0 else np.zeros_like(amp_spectrum) # 检测每个目标频率 detected_count = 0 for target_freq in target_frequencies: # 定位最接近目标频率的索引 closest_idx = np.argmin(np.abs(freq_axis - target_freq)) if normalized_amp[closest_idx] > threshold_ratio: detected_count += 1 # 当两个频率都被检测到输出提示 if detected_count == len(target_frequencies): print("检测到拨号音:440Hz + 350Hz") # 启动音频输入流 with sd.InputStream( callback=audio_callback, channels=2, samplerate=sample_rate, blocksize=blocksize ): print("正在监听拨号音...") input("按回车停止监听\n") print("监听结束")
关键改进点
- 固定缓冲区大小:保证频率分辨率稳定,避免动态缓冲区导致的频率定位偏差
- 归一化幅值:基于当前音频块的最大幅值做归一化,消除音量变化对阈值的影响
- FFT对称裁剪:只处理正频率部分,减少计算量
- 状态异常检测:添加
status输出,便于排查音频捕获故障
三、适合调制解调器的声音检测方法
针对这类特定频率检测场景,除了FFT,还有更高效的方案:
- Goertzel算法:专门用于检测少量特定频率,计算量远小于FFT,实时性更强,适合嵌入式或低延迟场景
- 带通滤波:为每个目标频率设计带通滤波器,提取对应频率成分后检测能量,抗干扰能力更强
- 能量阈值判断:结合滤波后的信号能量,设定合理阈值判断目标频率是否存在
内容的提问来源于stack exchange,提问作者rver
相关产品推荐
相关产品推荐

