You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python监听电脑输出音频:实现特定声音匹配检测需求

监听系统输出音频并检测特定声音的Python方案

嘿,我刚好折腾过类似的需求,给你拆解下可行的步骤,分两部分搞定:捕获系统输出音频实时匹配检测目标声音

一、先搞定系统音频的捕获

Python本身没法直接抓取系统的输出音频,得借助「虚拟音频设备」把系统声音路由成一个可读取的输入源,不同系统的设置略有不同:

1. 安装虚拟音频设备

  • Windows:安装Virtual Audio Cable,安装后把系统默认输出设备改成「CABLE Output」,这样系统所有声音都会传到这个虚拟线缆里,之后Python读取「CABLE Input」设备就行。
  • Mac:用BlackHole(替代旧的Soundflower),安装后在音频设置里把系统输出转到BlackHole的通道,再把BlackHole设为Python的输入设备。
  • Linux:用PulseAudio的虚拟sink,执行命令创建:
    pactl load-module module-null-sink sink_name=VirtualSink
    pactl load-module module-loopback source=VirtualSink.monitor
    
    之后在音频设置里把系统输出切换到VirtualSink,Python就能读取对应的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

火山引擎 最新活动