Files
vrsub/docs/adaptive_vad.md
T
cat-shark 2c3c356348 feat: whisper 节点自动接入自适应 VAD + 方案文档
- nodes/whisper.py:invoke 检测到 vad_filter=true 且未显式传 vad_parameters
  且 WOV_AUTO_VAD=1 时,自动调用 vad_profiler.vad_parameters_for_audio 按
  本音频信号动态确定 VAD 参数并传给 transcribe;分析失败回退默认不中断转写
  (防御性,不影响整段流程)。
- tests/test_nodes.py:新增自动 VAD 异常回退测试(patch profiler 抛错,
  验证 whisper 正常转写 + transcribe 收到 vad_parameters=None)。
- docs/adaptive_vad.md:完整方案文档(背景/可行性/三层架构/参数建议规则/
  评分器/实现落点/取舍/实现状态)。
2026-09-06 08:15:13 +08:00

6.2 KiB
Raw Blame History

每视频自适应 VAD 调参方案

背景:为什么必须每视频单独调 VAD

实测 CJOD-255 音频(2 小时成人视频)暴露了固定 VAD 配置的根本缺陷:

  • 全片 1s 能量 56% 在 RMS<900(音乐/低语)、仅 13% 是静音级(<300) 全程有 BGM几乎没有纯静音
  • 简单能量阈值(RMS>700)会把整片识别成 1 个 4120s 的巨型语音段—— BGM 让能量始终高于阈值,VAD 无法分离出真实说话。
  • 结果是 whisper 全段解码:80% 字幕碎片化(啊/嗯/はい)、32% 参考句漏识别、 敏感段(口交等)丢失。

不同视频的 BGM/静音/人声比例天差地别(测试片、音乐 MV、含 BGM 剧集、 纯语音播客),固定 VAD 参数必然在多数视频上偏差。因此需要对每个视频 先独立做信号分析,再动态决定 VAD 参数。

可行性

  1. VAD 是解码前的前置步骤:先用 ffmpeg 提 WAV(已有),可独立做信号分析, 不占用模型推理资源。
  2. faster-whisper 支持 vad_parameters 细参:可传 threshold(说话判定阈值)、min_silence_duration_ms(断句静音时长)、 speech_pad_ms(语音段首尾缓冲)。当前节点只传 vad_filter,细参入口未用。
  3. 求解代价可控:找最优参数在 1-2 分钟代表性片段上做网格搜索, 全片只跑一次确定后的参数。

方案架构(三层)

每个视频进入 whisper 节点时:
┌─────────────────────────────────────────────────────────┐
│ ① 信号分析 profile_audio(秒级,无模型)                │
│    - 1s 网格能量/RMS 分布 → 静音比例、BGM 底噪、语音疏密    │
│    - 推断:threshold 候选、min_silence 候选、speech_pad    │
│ ② 片段网格验证 pick_best_vad90s 代表片段,3~5 组参数)    │
│    - 若提供参考字幕 → 用"参考覆盖率+时间对齐误差"评分      │
│    - 无参考 → 用"碎片率/连贯性/置信度"启发式评分            │
│ ③ 全片用 ② 选出的 vad_parameters 跑一次                  │
└─────────────────────────────────────────────────────────┘

信号分析 → VAD 参数推荐规则

音频特征(从能量分布算出) VAD 建议
静音比例高(>30%)、BGM 低 threshold 高(0.6~0.7)、min_silence 2000,正常 VAD
BGM 覆盖广(如 CJOD 56%<900 threshold 低(0.30.4)、min_silence 300500、speech_pad 0——避免把音乐当语音整段拼接
纯语音(播客/采访) threshold 0.5、min_silence 1000,普通
长静音(停顿>2s 常见) speech_pad 减小(0~200)防时间轴压缩漂移

关键:BGM 掩盖音频的处理

对整片被 BGM 覆盖的视频,纯能量 VAD 天然失效。三种手段按优先级:

  1. 降低 VAD threshold0.5→0.3):让 silero 的语音概率模型在 BGM 中 更敏感地捕捉人声,避免把音乐段误判为静音而吞掉轻语。
  2. 降低 min_silence_duration_ms2000→300~500):避免把停顿超过 2s 的 短句硬并成一条,缓解"碎片化/漏句"。
  3. speech_pad_ms 减小400→0~200):缓冲越大,Whisper 对片段起始时间 估计越偏早,减小可提升时间对齐精度。

评分器(决定最优参数)

有参考字幕(OCR 硬字幕 = ground truth

参考的第 k 条 vs 转录的对齐:

  • 覆盖率 = 能在 1.5s 内找到语义对应日文的参考条数 / 总参考条数(越高越好)
  • 时间误差 = 配对条目 |转录start - 参考start| 的平均(越低越好)
  • 综合分 = 覆盖率 - 0.3×时间误差(权重可调)

无参考字幕(启发式)

用转录自身质量:

  • 碎声率 = 纯单字/语气词条目占比(越低越好,如 <20%)
  • 连贯性 = 每条平均字数(适中为好,不碎不并)
  • 置信度 = whisper segment 的 avg_logprob

实现落点

  • 新模块 nodes/vad_profiler.pyprofile_audio()(信号分析)、 pick_best_vad()(片段网格 + 评分)、vad_parameters_for_audio()(总入口)
  • nodes/whisper.py:在 invoke 里检测到 vad_filter=true 且未显式传 vad_parameters 时,调用 profiler 生成 per-video 参数传给 transcribe()
  • 参考字幕可选:若工作流/请求提供 reference_srt_uri,走"有参考评分"
  • 门控:WOV_AUTO_VAD=1(默认开),可关;显式传 vad_parameters 时跳过

取舍

  • 收益:每个视频用最贴合其声学的 VAD,显著降低碎片化、漏句、时间错位, 尤其对 BGM 音频(如成人视频、综艺)。
  • 成本:每视频额外 1030s 信号分析 + 片段上 35 次短转写(分钟级), 相比全片转写可接受。
  • 风险:片段网格选取的代表性片段若无说话/纯音乐,可能评为最差参数。 缓解:选 2 段(开头+中段)拼接,且阈值下限保护。

实现状态(2026-09 已落地)

  • nodes/vad_profiler.pyprofile_audio()1s 能量分析)、 suggest_vad_parameters()(信号→VAD 建议)、score_transcript()(幻觉词 扣分启发式)、vad_parameters_for_audio()(总入口)、_pick_representative_start()_grid_search_vad()(片段网格)。
  • nodes/whisper.pyinvoke 检测到 vad_filter=true 且未显式传 vad_parametersWOV_AUTO_VAD=1 时,自动调用 profiler 生成 per-video 参数 传给 transcribe();分析失败回退默认不中断转写。
  • 测试 tests/test_vad_profiler.py(19 个)覆盖:信号分析、四个建议分支、 幻觉词/碎片评分、网格选优、异常回退、空音频/低采样率、候选网格展开。
  • 门控:WOV_AUTO_VAD=0 关闭自动调参;显式传 vad_parameters 时跳过。
  • 说明:当前接入默认 whisper_invoke=None(仅信号分析,秒级、不额外跑模型); 若未来要启用片段网格验证,传入 whisper 回调并暴露配置即可,框架已就绪。