VAD 技术实践:让 AI 知道什么时候该听,什么时候该回答
摘要: 在语音 AI 系统中,ASR 负责把声音转成文字,但它通常不会独立决定用户什么时候开始说话、什么时候真正说完。VAD(Voice Activity Detection,语音活动检测)就是解决这个问题的技术。本文介绍 VAD 的核心原理,并通过客服录音自动切分、实时语音助手两个 Python 案例,演示如何使用轻量级 Silero VAD 检测语音片段,优化语音识别流程。
一、VAD 是什么?有了 ASR 为什么还需要它?
假设我们正在开发一个 AI 语音客服。
用户说:
你好,我想查询一下……(停顿两秒)……昨天购买的商品什么时候发货。
这时系统面临一个问题:用户在中间停顿了两秒,究竟是已经说完,还是正在思考?
如果过早调用 ASR 和 LLM,AI 可能在用户还没说完时就开始回答。
但如果每次都等待很久,交互又会显得迟钝。
VAD 的核心任务,就是检测音频中什么时候存在人声,以及人声什么时候开始和结束。
需要注意,VAD 只能识别语音活动,不能真正理解用户是否表达完了一个完整的意思。
它与 ASR 的区别非常明确:
| 技术 | 解决的问题 | 典型输出 |
|---|---|---|
| VAD | 什么时候有人说话? | 语音起止时间 |
| ASR | 说了什么内容? | 转写文本 |
| Speaker Diarization | 分别是谁在说话? | 说话人标签与时间段 |
| LLM | 用户表达了什么意图? | 理解结果或回答 |
因此,VAD 往往被放在 ASR 之前,作为语音处理的第一道关卡。
1.1 VAD 的基本原理
最简单的 VAD 可以通过音频能量判断是否有人声。
但现实中,键盘敲击、空调、汽车噪声也可能有较大的音量,仅凭能量阈值很容易误判。
现代神经网络 VAD 会学习语音的时频特征,并对短音频片段输出语音概率。
麦克风音频
│
▼
音频分帧(例如32ms)
│
▼
VAD 神经网络
│
▼
语音概率 P(speech)
│
▼
阈值与连续时长判断
│
├── 检测到语音开始
├── 持续说话
└── 检测到语音结束
例如,一段音频的检测结果可能是:
0.0s - 1.2s 静音
1.2s - 4.5s 有人说话
4.5s - 5.3s 静音
5.3s - 8.1s 有人说话
这就能把一段连续录音拆成两个候选语音片段。
二、有哪些值得使用的 VAD 模型?
VAD 并不一定需要大语言模型。实际上,许多成熟的 VAD 都是非常轻量的专用模型。
| 模型 | 主要特点 | 适用场景 |
|---|---|---|
| WebRTC VAD | 传统信号处理与统计方法,极轻量 | 电话、实时通信 |
| Silero VAD | 神经网络模型,支持 PyTorch、ONNX | 录音切分、语音助手 |
| pyannote.audio Segmentation | 神经网络语音分割,可涉及说话人重叠 | 会议分析、说话人分离流水线 |
这里推荐 Silero VAD。
它的 JIT 模型文件约 2MB,支持 8kHz 和 16kHz 音频,可以直接在 CPU 上运行。
根据官方公布的测试,其处理一个 30 多毫秒音频块的计算耗时,在特定单线程 CPU 环境下可以低于 1ms。不过,实际端到端延迟还取决于音频缓冲、静音等待和系统调度。
官方项目:
下面通过两个案例了解它的使用方式。
三、实践案例一:自动切分客服录音
3.1 业务场景
假设我们有一段 10 分钟的客服录音,里面包含不少等待、静音和交谈间隙。
如果整段直接送入 ASR,可能增加计算和处理成本。
更合理的方法是:
完整客服录音
│
▼
Silero VAD
│
▼
识别有效语音时间段
│
▼
提取语音片段
│
▼
ASR
│
▼
拼接转写文本
3.2 环境准备
安装依赖:
pip install silero-vad soundfile torch
准备一段单声道、16kHz 的录音文件:
call.wav
如果原始录音格式不匹配,可以先使用 FFmpeg 转换:
ffmpeg -i original.mp3 -ar 16000 -ac 1 call.wav
3.3 Python 实现
import torch
import soundfile as sf
from pathlib import Path
from silero_vad import (
load_silero_vad,
get_speech_timestamps
)
# 加载VAD模型
model = load_silero_vad()
# 读取音频
audio, sr = sf.read(
"call.wav",
dtype="float32"
)
if sr != 16000 or audio.ndim != 1:
raise ValueError("需要16kHz单声道音频")
audio_tensor = torch.from_numpy(audio)
# 检测语音片段
segments = get_speech_timestamps(
audio_tensor,
model,
sampling_rate=sr,
threshold=0.5,
min_speech_duration_ms=250,
min_silence_duration_ms=500,
speech_pad_ms=200
)
# 保存每个语音片段
Path("segments").mkdir(exist_ok=True)
for i, seg in enumerate(segments):
start = seg["start"]
end = seg["end"]
clip = audio[start:end]
sf.write(
f"segments/speech_{i:03d}.wav",
clip,
sr
)
print(
f"片段{i}: "
f"{start/sr:.2f}s - "
f"{end/sr:.2f}s"
)
运行后,可能得到:
segments/
├── speech_000.wav
├── speech_001.wav
├── speech_002.wav
└── ...
这些就是检测出的语音片段,可以逐段交给 ASR 识别。
代码中有三个关键参数:
threshold=0.5:语音检测概率阈值。min_silence_duration_ms=500:持续静音约 500ms 才考虑结束片段。speech_pad_ms=200:在片段两端保留少量音频,降低切掉字头或字尾的风险。
注意,以上参数只是实验起点。对于客服通话,过短的静音阈值可能把一个完整句子切成多段。
另外,应当保留每段音频的原始时间戳。否则,后续 ASR 虽然能识别文字,却无法恢复完整录音的时间轴。
四、实践案例二:实时语音助手判断用户什么时候说完
4.1 业务场景
假设我们想开发一个可以直接对话的 AI 助手。
用户对着麦克风说话:
帮我查一下明天北京的天气。
系统应该自动完成:
- 检测用户开始说话。
- 持续接收语音。
- 检测用户停止说话。
- 将完整语音交给 ASR。
- 调用 LLM 生成回复。
这里使用 Silero 官方提供的 VADIterator,它专门用于连续音频流的检测。
4.2 Python 实现
安装麦克风采集依赖:
pip install sounddevice
然后运行:
import torch
import numpy as np
import soundfile as sf
import sounddevice as sd
from silero_vad import (
load_silero_vad,
VADIterator
)
SR = 16000
CHUNK = 512 # 32ms音频
model = load_silero_vad()
vad = VADIterator(
model,
sampling_rate=SR,
threshold=0.5,
min_silence_duration_ms=600
)
audio_chunks = []
speech_start = None
print("开始监听麦克风...")
with sd.InputStream(
samplerate=SR,
channels=1,
dtype="float32",
blocksize=CHUNK
) as stream:
# 最多监听约30秒,演示一次完整发言
for _ in range(30 * SR // CHUNK):
data, overflow = stream.read(CHUNK)
chunk = data[:, 0].copy()
audio_chunks.append(chunk)
# 实时VAD判断
event = vad(torch.from_numpy(chunk))
if event is None:
continue
if "start" in event:
speech_start = event["start"]
print("检测到用户开始说话")
if "end" in event and speech_start is not None:
speech_end = event["end"]
print("检测到用户停止说话")
# 取得完整语音片段
audio = np.concatenate(audio_chunks)
speech = audio[speech_start:speech_end]
# 保存后可送入ASR
sf.write(
"user_speech.wav",
speech,
SR
)
print("已保存:user_speech.wav")
break
这个例子运行后,会监听麦克风,在检测到一次完整的语音起止事件后生成 user_speech.wav。
随后可以调用此前介绍过的 Qwen3-ASR:
user_speech.wav
│
▼
Qwen3-ASR
│
▼
用户说话文本
│
▼
LLM
│
▼
生成回答
│
▼
Qwen3-TTS
│
▼
播放回答
这样,一个最基础的实时语音助手架构就搭建出来了。
当然,这只是单轮演示,正式系统还需要处理连续多轮对话、麦克风异常、缓冲区管理,以及用户说话中途被打断的情况。
五、VAD 落地时需要注意什么?
第一,静音不一定意味着用户说完了。
用户思考时可能停顿一两秒。如果系统把短暂停顿当成结束,就容易频繁打断用户。
更好的方案是将 VAD 与 ASR 的增量识别结果结合,通过语义判断用户是否真正表达完毕。
第二,VAD 无法区分不同说话人。
录音中只要有人说话,VAD 就可能检测到语音。它无法判断声音来自用户、旁边的同事,还是电视机。
如果要区分不同说话人,需要额外使用说话人识别或分离技术。
第三,阈值需要根据场景调整。
| 参数 | 过小可能导致 | 过大可能导致 |
|---|---|---|
threshold |
把噪声识别为语音 | 漏掉轻声讲话 |
min_silence_duration_ms |
语句被频繁切断 | 语音结束响应缓慢 |
speech_pad_ms |
切掉语音首尾 | 保留过多静音 |
其中,VAD 对音频结束的判断需要等待一定的连续静音时间。因此,即使模型本身推理耗时不到 1ms,也不意味着系统能够在用户说完后的 1ms 内开始回答。
六、总结
VAD 并不是一个特别复杂的 AI 模型,但它在语音系统中的位置非常重要。
本文介绍的两个实践案例分别是:
- 客服录音切分: 自动找到有效语音片段,减少不必要的 ASR 处理。
- 实时语音助手: 判断用户什么时候开始说话、什么时候停止说话,控制语音交互流程。
结合前面介绍的模型,可以得到一套完整的语音 AI 架构:
麦克风音频
│
▼
VAD 检测语音起止
│
▼
ASR 语音转文字
│
▼
LLM 理解与回答
│
▼
TTS 语音合成
│
▼
扬声器播放
当然,VAD 并不能完全解决人机对话的轮次切换问题。用户什么时候真正说完,还可能需要结合语义和上下文进行判断。
最后用一句话概括:
ASR 负责听懂用户说了什么,VAD 则负责判断什么时候应该开始听、什么时候可以尝试停止听。
在实时语音 AI 中,VAD 虽然不起眼,却直接影响着对话是否自然、流畅,以及 AI 会不会动不动就抢话。
- 点赞
- 收藏
- 关注作者
评论(0)