Skip to content

VAD 详细参数说明

VAD(Voice Activity Detection)用于一句话识别(dictation)模式下的语音起止检测。当前实现基于 Silero VAD ONNX 模型,同时叠加一层动态能量阈值,用来过滤低能量底噪。

设计理念

可以把 VAD 理解成 EasyMrcp 里的“听筒开关”。电话里一直都有音频流进来,但一句话识别不适合把整通电话都持续送给 ASR,它更适合在用户真正开始说话时打开识别,在用户说完后及时收尾。

VAD 做的事情就是判断三件事:

  1. 用户是不是开始说话了。
  2. 用户是不是还在继续说话。
  3. 用户是不是已经停顿足够久,可以认为这一句话结束了。

为了避免误判,当前实现不会只看“声音大不大”,也不会只看模型判断“像不像人声”,而是两个条件一起看:

  • Silero VAD 模型判断这段音频像人声;
  • 当前音频能量明显高于背景底噪。

这样做的好处是,空调声、电流声、轻微杂音这类“有声音但不像人在说话”的内容不容易触发识别;而真正的人声出现后,系统会回退一小段缓存音频再发给 ASR,尽量避免把句首截掉。当检测到用户停顿超过配置时间后,VAD 会通知 ASR 这一句话可以结束输入。

本文档基于当前源码整理,主要对应以下类:

  • com.cfsl.easymrcp.vad.VadHandle
  • com.cfsl.easymrcp.vad.SlieroVadDetector
  • com.cfsl.easymrcp.vad.SlieroVadOnnxModel
  • com.cfsl.easymrcp.rtp.NettyAsrRtpProcessor
  • com.cfsl.easymrcp.asr.AsrHandler

1. 生效范围

VAD 只在 ASR 识别模式为 dictation 时启用:

java
ASRConstant.IDENTIFY_PATTERNS_DICTATION

实时转写(transliterate)模式不会走 VAD 分段逻辑,而是直接把 RTP 解码后的 PCM 音频发送给 ASR。

2. 基础参数

2.1 模型文件

参数当前值说明
MODEL_PATHsilero_vad.onnxSilero VAD ONNX 模型文件名。

模型路径解析规则:

  1. 优先查找当前工作目录下的 src/main/resources/silero_vad.onnx
  2. 如果不存在,则查找当前工作目录下的 silero_vad.onnx

2.2 采样率

参数当前逻辑说明
sampleRate800016000VAD 检测器只接受 8k 或 16k 采样率。

采样率由 ASR 配置中的 reSample 决定:

  • reSample = upsample8kTo16k 时,VAD 使用 16000
  • 其他情况默认使用 8000

2.3 VAD 聚合帧大小

场景vadFrameSizeBytes说明
8k PCM2048 字节每次聚合 2048 字节后做一次 VAD 检测。
16k PCM4096 字节8k 上采样到 16k 后,聚合帧字节数同步翻倍。

VAD 缓冲区容量为 vadFrameSizeBytes * 3,用于容纳 2 到 3 个 VAD 帧,避免数据积压。

3. 模型概率阈值

参数当前值说明
START_THRESHOLD0.4f当 Silero 模型输出的人声概率大于等于该值时,模型侧认为可能开始说话。
END_THRESHOLD0.8f已经进入说话状态后,当人声概率低于该值时,进入可能结束判断。

语音开始不是只看 START_THRESHOLD。当前实现还要求当前帧 RMS 能量大于等于动态能量阈值。

语音结束判断流程:

  1. 已处于说话状态。
  2. 当前帧 speechProb < END_THRESHOLD
  3. 静音持续时间达到 MIN_SILENCE_DURATION_MS
  4. 确认语音结束。

4. 动态能量阈值

当前实现已经不再使用“全量历史平均能量 × 倍数”的方式计算阈值,而是使用背景底噪估计:

text
energyThreshold = max(MIN_ENERGY_THRESHOLD_FLOOR, noiseFloorEnergy * ENERGY_THRESHOLD_MULTIPLIER)
参数当前值说明
MIN_ENERGY_THRESHOLD_FLOOR0.01f能量阈值最小下限,避免极安静环境下阈值过低。
ENERGY_THRESHOLD_MULTIPLIER1.4f动态阈值倍数。
noiseFloorAlpha0.01f背景底噪指数滑动平均系数。
noiseFloorEnergy初始为 0.01f估计出来的背景底噪能量。

底噪更新逻辑:

text
if (!triggered) {
  noiseFloorEnergy = noiseFloorAlpha * rmsEnergy + (1 - noiseFloorAlpha) * noiseFloorEnergy
}
energyThreshold = max(MIN_ENERGY_THRESHOLD_FLOOR, noiseFloorEnergy * ENERGY_THRESHOLD_MULTIPLIER)

说明:

  • 只有未进入说话状态时,当前帧 RMS 能量才会参与背景底噪估计。
  • 进入说话状态后,不再用人声能量抬高底噪估计。
  • 语音开始要求同时满足:
    • speechProb >= START_THRESHOLD
    • rmsEnergy >= energyThreshold

5. 时间控制参数

参数当前值说明
MIN_SILENCE_DURATION_MS默认 300 ms判定语音结束所需的最小静音持续时间。
SPEECH_PAD_MS500 ms语音起止点前后的补偿时间。

MIN_SILENCE_DURATION_MS 可以通过 DetectSpeech 事件里的 SpeechCompleteTimeout 下发:

json
{
  "StartInputTimers": true,
  "NoInputTimeout": 60000,
  "SpeechCompleteTimeout": 800,
  "AutomaticInterruption": true
}

注意:

  • 当前代码只在 StartInputTimers=true 的分支里读取 SpeechCompleteTimeout
  • SpeechCompleteTimeout 需要在 VAD 初始化前设置才会稳定生效。
  • VAD 初始化后调用 setSpeechCompleteTimeout 不会动态修改已创建检测器,只会记录告警日志。

6. 工作流程

6.1 RTP 到 VAD

  1. NettyAsrRtpProcessor 接收 RTP 包。
  2. 根据协商编码解码成 16 位单声道 PCM。
  3. 如果配置了 upsample8kTo16k,先上采样到 16k。
  4. dictation 模式下写入主音频缓冲区和 VAD 环形缓冲区。
  5. VAD 缓冲区达到 vadFrameSizeBytes 后,取出一帧送入 VadHandle.receivePcm()

6.2 VAD 检测

  1. 将 16 位小端 PCM 转为 float 采样值。
  2. 计算当前帧 RMS 能量 rmsEnergy
  3. 调用 Silero ONNX 模型得到人声概率 speechProb
  4. 在非说话状态下更新 noiseFloorEnergyenergyThreshold
  5. 根据模型概率和能量阈值判断语音开始。
  6. 根据结束阈值和静音持续时间判断语音结束。

6.3 语音开始后的处理

检测到语音开始时:

  • 主音频缓冲区读指针回退 500ms
  • 异步重新连接一句话 ASR
  • 将缓冲区中的音频发送给 ASR,避免丢掉句首

6.4 语音结束后的处理

检测到语音结束时:

  • 发送主音频缓冲区里的剩余音频
  • 调用 sendEof()
  • 由具体 ASR 实现返回最终识别结果

7. 实时日志

当前 VAD 每 200ms 最多打印一次实时统计日志,字段包括:

  • vadProb
  • startThreshold
  • endThreshold
  • rmsEnergy
  • energyThreshold
  • noiseFloorEnergy
  • triggered

这些字段可用于判断误触发或漏判原因。

8. 参数调优建议

当前默认参数:

text
START_THRESHOLD = 0.4
END_THRESHOLD = 0.8
MIN_SILENCE_DURATION_MS = 300
SPEECH_PAD_MS = 500
MIN_ENERGY_THRESHOLD_FLOOR = 0.01
ENERGY_THRESHOLD_MULTIPLIER = 1.4
noiseFloorAlpha = 0.01

安静环境

  • 保持 MIN_ENERGY_THRESHOLD_FLOOR = 0.01
  • 如仍有漏判,可适当降低 ENERGY_THRESHOLD_MULTIPLIER
  • 不建议轻易降低 START_THRESHOLD,否则更容易误触发

噪音环境

  • 可适当提高 ENERGY_THRESHOLD_MULTIPLIER
  • 可适当增加 MIN_SILENCE_DURATION_MS
  • 通过实时日志观察 rmsEnergyenergyThreshold 的差距,再决定调参

更快断句

  • 降低 SpeechCompleteTimeout
  • 注意过低会把长句中的自然停顿切成多句

更稳断句

  • 提高 SpeechCompleteTimeout
  • 适合用户说话停顿较多、但希望尽量保持整句识别的场景

9. 对接注意事项

  • DetectSpeech 建议始终显式传 StartInputTimers
  • 如果需要配置 SpeechCompleteTimeout,应与 StartInputTimers=true 一起传。
  • VAD 只控制一句话识别的音频分段,不直接生成 RecognitionComplete
  • RecognitionComplete 仍由具体 ASR 引擎返回最终结果后触发。
  • 如果日志中 vadProb 已经超过 START_THRESHOLD,但没有进入说话状态,优先检查 rmsEnergy 是否低于 energyThreshold