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

如何用Python检测特定频率组合并响应?sounddevice代码求助

问题与需求

正在使用sounddevice开发类老式调制解调器的Python脚本,通过声音实现脚本实例间通信。已完成DTMF生成器、二进制转换器等收发功能,但无法检测440Hz+350Hz的拨号音,无法实时监听DTMF、数据等声音并响应。

刚接触sounddevice和numpy,仅会生成并播放指定时长的正弦波,接收部分依赖ChatGPT生成的代码(见下方),但该代码要么无响应要么持续报错。尝试自行开发但难以理解官方文档,需要:

  1. 代码调试帮助
  2. 解释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处理逻辑和阈值设置不合理:

  1. 固定阈值不可靠:abs(fft_data)的绝对值与输入音量、缓冲区大小直接相关,固定值10000无法适配不同场景
  2. FFT结果未做对称处理:FFT输出是对称的,后半部分对应负频率,无需处理
  3. 频率分辨率不稳定:未指定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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 03:35:27