语音活动检测与轮次切换:Silero、Cobra 与刷新技巧 本节摘要:每一个语音 agent 都生死系于两个判断:用户现在在说话吗?他说完了吗?VAD 回答第一个,轮次检测(VAD + 静音悬挂 + 语义端点模型)回答第二个。任一判断出错,你的助手要么打断用户,要么永远闭不上嘴。本节聚焦语音 agent 在每个 20 毫秒块上要做的三个截然不同的判断:这帧是语音吗(VAD)、用户是否开始新话语(起止检测)、用户是否说完了(端点检测)。朴素答案(能量阈值)在任何噪声下都失败——交通、键盘、人群嘈杂;2026 年的答案是 Silero VAD(开源、深度学习)+ 轮次检测模型(语义端点)+ VAD 校准的静音悬挂。
本节摘要:每一个语音 agent 都生死系于两个判断:用户现在在说话吗?他说完了吗?VAD 回答第一个,轮次检测(VAD + 静音悬挂 + 语义端点模型)回答第二个。任一判断出错,你的助手要么打断用户,要么永远闭不上嘴。本节聚焦语音 agent 在每个 20 毫秒块上要做的三个截然不同的判断:这帧是语音吗(VAD)、用户是否开始新话语(起止检测)、用户是否说完了(端点检测)。朴素答案(能量阈值)在任何噪声下都失败——交通、键盘、人群嘈杂;2026 年的答案是 Silero VAD(开源、深度学习)+ 轮次检测模型(语义端点)+ VAD 校准的静音悬挂。本节会讲透三级 VAD 级联(能量门 → Silero → 语义轮次检测器)、四个关键参数(阈值、最短语音时长、静音悬挂、预滚动缓冲)的默认值与权衡,以及 Kyutai 2025 的「刷新技巧」——VAD 触发话尾时给流式 STT 发 flush 信号,把 500 毫秒的前瞻延迟压到 125 毫秒。读完本节,你能为任意工作负载选对 VAD 模型与轮次检测策略,并避开固定阈值、悬挂过短/过长、无预滚动、忽视语义端点四类坑。
阅读完本节,你应当能够:
语音 agent 在每个 20 毫秒块上要做三个截然不同的判断:
朴素答案(能量阈值)在任何噪声下都失败——交通、键盘、人群嘈杂。2026 年的答案:Silero VAD(开源、深度学习)+ 轮次检测模型(语义端点)+ VAD 校准的静音悬挂。
第一级:能量门。最便宜。RMS 阈值 -40 dBFS。滤掉明显的静音,但任何高于阈值的噪声都会触发。
第二级:Silero VAD(2020-2026,MIT)。100 万参数,在 6000+ 语种上训练。单 CPU 线程每 30 毫秒块约 1 毫秒。5% FPR 下 87.7% TPR。开源默认。
第三级:语义轮次检测器。LiveKit 的 turn-detection 模型(2024-2026)或你自己的小分类器。区分「句中停顿」与「说完了」,使用语言上下文(语调 + 近期词汇),而非仅看静音。
流式 STT 模型有前瞻延迟(Kyutai STT-1B 是 500 毫秒,STT-2.6B 是 2.5 秒)。正常你要在话尾后等这么久才拿到转录。刷新技巧:VAD 触发话尾时,给 STT 发一个 flush 信号,强制立即输出。STT 以约 4 倍实时处理,所以 500 毫秒缓冲约 125 毫秒跑完。端到端:125 毫秒 VAD + flush STT = 对话级延迟。
| VAD | TPR @ 5% FPR | 延迟 | 许可 |
|---|---|---|---|
| WebRTC VAD(Google, 2013) | 50.0% | 30 ms | BSD |
| Silero VAD(2020-2026) | 87.7% | ~1 ms | MIT |
| Cobra VAD(Picovoice) | 98.9% | ~1 ms | 商业 |
| pyannote segmentation | 95% | ~10 ms | MIT 系 |
Silero 是正确的默认选择。Cobra 是合规/精度升级。纯能量 VAD 在 2026 年生产中没有位置。
💡 静音悬挂的 500800 毫秒不是玄学,而是对话物理的产物:人类在句中停顿(思考、换气)的中位数约 200400 毫秒,而在话说完后的停顿通常 >600 毫秒。所以把悬挂设在 500~800 毫秒,正好卡在「句中停顿」与「话尾」的分布之间。但不同场景(沉思的思考者 vs 快节奏问答)分布不同,必须在目标用户上 A/B 测。
def energy_vad(chunk, threshold_dbfs=-40.0): rms = (sum(x * x for x in chunk) / len(chunk)) ** 0.5 dbfs = 20.0 * math.log10(max(rms, 1e-10)) return dbfs > threshold_dbfs
from silero_vad import load_silero_vad, get_speech_timestamps vad = load_silero_vad() audio = torch.tensor(waveform_16k, dtype=torch.float32) segments = get_speech_timestamps( audio, vad, sampling_rate=16000, threshold=0.5, min_speech_duration_ms=250, min_silence_duration_ms=500, speech_pad_ms=300, ) for s in segments: print(f"{s['start']/16000:.2f}s - {s['end']/16000:.2f}s")
class TurnDetector: def __init__(self, silence_hangover_ms=500, min_speech_ms=250): self.state = "idle" self.speech_ms = 0 self.silence_ms = 0 self.silence_hangover_ms = silence_hangover_ms self.min_speech_ms = min_speech_ms def update(self, is_speech, chunk_ms=20): if is_speech: self.speech_ms += chunk_ms self.silence_ms = 0 if self.state == "idle" and self.speech_ms >= self.min_speech_ms: self.state = "speaking" return "START" else: self.silence_ms += chunk_ms if self.state == "speaking" and self.silence_ms >= self.silence_hangover_ms: self.state = "idle" self.speech_ms = 0 return "END" return None
设计要点:这个状态机的两个计数器(
speech_ms与silence_ms)对应两个去抖动需求——min_speech_ms防止一次咳嗽被当成话语开始,silence_hangover_ms防止句中停顿被当成话尾。两者都靠「累积时长超过阈值才翻转状态」来实现,是控制论里经典的施密特触发器思想:状态翻转需要足够的证据,避免在边界附近抖动。
def flush_on_end(stt_client, audio_buffer): stt_client.send_audio(audio_buffer) stt_client.send_flush() return stt_client.recv_transcript(timeout_ms=150)
STT(Kyutai、Deepgram、AssemblyAI)必须支持 flush 这个才能工作。Whisper streaming 不支持——它是基于块的,永远等块。
| 场景 | VAD 选择 |
|---|---|
| 开源、快、通用 | Silero VAD |
| 商业呼叫中心 | Cobra VAD |
| 端上(手机) | Silero VAD ONNX |
| 研究 / 分离 | pyannote segmentation |
| 零依赖回退 | WebRTC VAD(遗留) |
| 需要话尾质量 | Silero + LiveKit turn-detector 分层 |
经验法则:除非真的没别的选择,否则别上线纯能量 VAD。
⚠️ 五类坑:① 固定阈值——安静环境好用,噪声环境失效,要么端上调,要么换 Silero;② 静音悬挂太短——agent 在句中打断用户,500800 毫秒是对话甜点;③ 悬挂太长——感觉迟钝,要在目标用户上 A/B 测;④ 无预滚动缓冲——用户音频前 200300 毫秒丢失,始终保留滚动预滚动;⑤ 忽视语义端点——「嗯,让我想想……」含长停顿,用户讨厌思考被打断,要用 LiveKit turn-detector 或类似方案。
本节产出技能文档(原课程 outputs/skill-vad-tuner.md),为工作负载选择 VAD 模型、阈值、悬挂、预滚动、轮次检测策略。调谐模板:
| 参数 | 默认 | 调谐方向 |
|---|---|---|
| 阈值 | 0.5 | 首字被切 → 0.3;误报多 → 0.6 |
| 最短语音时长 | 250 ms | 咳嗽误触发 → 调高 |
| 静音悬挂 | 500~800 ms | 打断用户 → 调高;迟钝 → 调低 |
| 预滚动缓冲 | 300~500 ms | 首字被切 → 调高 |
| 轮次检测 | Silero + LiveKit turn-detector | 长停顿场景必备 |
基础:运行 code/main.py。它模拟「语音 + 静音 + 语音 + 咳嗽」序列,测试三级 VAD。
进阶:装 silero-vad,处理一段 5 分钟录音,调阈值最小化首字被切与误触发,汇报精度/召回。
挑战:搭一个迷你轮次检测器:Silero VAD + 最近 10 个词嵌入上的 3 层 MLP(用 sentence-transformers),在手工标注的话尾数据集上训练,在 F1 上比 Silero-only 高 10%。
下一节,我们用这些组件搭出本章最前沿的架构——流式语音到语音的 Moshi 与 Hibiki,理解全双工对话模型如何绕开级联流水线,做到 200 毫秒端到端。