Python监听电脑输出音频:实现特定声音匹配检测需求
监听系统输出音频并检测特定声音的Python方案
嘿,我刚好折腾过类似的需求,给你拆解下可行的步骤,分两部分搞定:捕获系统输出音频和实时匹配检测目标声音。
一、先搞定系统音频的捕获
Python本身没法直接抓取系统的输出音频,得借助「虚拟音频设备」把系统声音路由成一个可读取的输入源,不同系统的设置略有不同:
1. 安装虚拟音频设备
- Windows:安装Virtual Audio Cable,安装后把系统默认输出设备改成「CABLE Output」,这样系统所有声音都会传到这个虚拟线缆里,之后Python读取「CABLE Input」设备就行。
- Mac:用BlackHole(替代旧的Soundflower),安装后在音频设置里把系统输出转到BlackHole的通道,再把BlackHole设为Python的输入设备。
- Linux:用PulseAudio的虚拟sink,执行命令创建:
之后在音频设置里把系统输出切换到VirtualSink,Python就能读取对应的monitor源。pactl load-module module-null-sink sink_name=VirtualSink pactl load-module module-loopback source=VirtualSink.monitor
2. Python实时读取系统音频流
用sounddevice库来处理实时音频,先安装依赖:
pip install sounddevice soundfile
先运行下面的代码列出所有音频设备,找到你的虚拟输入设备ID:
import sounddevice as sd print(sd.query_devices())
然后用InputStream来实时捕获音频流,这里以设备ID为2为例(替换成你自己的):
import sounddevice as sd import sys # 配置参数,要和目标WAV文件一致 SAMPLE_RATE = 44100 BLOCK_SIZE = 1024 DEVICE_ID = 2 # 替换成你的虚拟输入设备ID def audio_callback(indata, frames, time, status): # indata就是实时获取的音频数据,shape是(BLOCK_SIZE, 通道数) if status: print(status, file=sys.stderr) # 这里后续加匹配逻辑 # 启动监听流 with sd.InputStream(samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE, device=DEVICE_ID, callback=audio_callback): print("正在监听系统音频...") input("按回车停止监听\n")
二、实时匹配目标声音
接下来要把实时捕获的音频和你的目标WAV文件(比如Facebook提示音)对比,这里推荐用MFCC特征匹配或者互相关检测,前者对环境噪音更鲁棒。
1. 预处理目标音频
先安装librosa用于音频特征提取:
pip install librosa
加载目标WAV文件,提取关键特征(比如MFCC):
import librosa TARGET_WAV_PATH = "facebook_notification.wav" # 加载目标音频,统一采样率 target_audio, _ = librosa.load(TARGET_WAV_PATH, sr=SAMPLE_RATE) # 提取MFCC特征(13维) target_mfcc = librosa.feature.mfcc(y=target_audio, sr=SAMPLE_RATE, n_mfcc=13) # 转置成(帧数, 特征数)方便后续计算 target_mfcc = target_mfcc.T
2. 实时流中检测匹配
在之前的audio_callback里加入匹配逻辑,这里用简单的帧相似度计算(或者用动态时间规整DTW更准确,但计算量稍大):
import librosa import numpy as np # 预处理目标音频(放在回调外,只执行一次) TARGET_WAV_PATH = "facebook_notification.wav" target_audio, _ = librosa.load(TARGET_WAV_PATH, sr=SAMPLE_RATE) target_mfcc = librosa.feature.mfcc(y=target_audio, sr=SAMPLE_RATE, n_mfcc=13).T target_len = target_mfcc.shape[0] def audio_callback(indata, frames, time, status): if status: print(status, file=sys.stderr) # 把输入数据转成单通道(如果是立体声的话) audio_data = indata.mean(axis=1) # 提取当前块的MFCC current_mfcc = librosa.feature.mfcc(y=audio_data, sr=SAMPLE_RATE, n_mfcc=13).T current_len = current_mfcc.shape[0] # 只有当当前块长度足够时才对比 if current_len >= target_len: # 滑动窗口计算相似度(这里用欧氏距离的平均值) min_distance = float('inf') for i in range(current_len - target_len + 1): window = current_mfcc[i:i+target_len] distance = np.mean(np.linalg.norm(window - target_mfcc, axis=1)) if distance < min_distance: min_distance = distance # 设定阈值,小于这个值就认为匹配到了 THRESHOLD = 5 # 这个值需要你根据实际情况调整 if min_distance < THRESHOLD: print("Facebook message audio heard")
一些注意事项
- 参数统一:实时流的采样率、通道数必须和目标WAV文件一致,否则匹配会出错。
- 阈值调整:THRESHOLD需要你自己测试,比如先录一段目标声音,计算它的距离,再录背景噪音的距离,取中间值。
- 性能优化:如果实时处理卡顿,可以调大BLOCK_SIZE,或者用更轻量的特征(比如直接用波形的互相关)。
内容的提问来源于stack exchange,提问作者Markus Mikkonen




