11/**
22 * VAD 音频标记器 — Path B 语音段检测与分段
33 *
4- * @ai -context
5- * Path B 通过 RMS 能量检测将连续语音切段,
6- * 每段完成后立即触发 onSegmentReady 回调,支持流式 ASR 转写。
7- * @ai -context: 校准仅对 microphone 源生效;loopback(网课系统环回)为数字
8- * 信号无环境底噪,构造时直接标记已校准并使用预设阈值,不进入校准期
9- * (避免 UI 出现无意义的“正在校准音频阈值”提示)。
10- *
11- * @ai -context: 背景噪声校准为现场课程麦克风输入场景设计——麦克风存在真实
12- * 环境底噪(空调/键盘/人声嘴杂),以 sourceType: 'microphone' 构造时
13- * 启用前 N 块自适应校准。后续可考虑将校准结果持久化供下次会话作为初始
14- * 阈值,避免每次启动等待采样期。
4+ * @ai -context: RMS 能量检测将连续语音切段,每段完成后触发 onSegmentReady
5+ * 回调供流式 ASR 转写。校准仅对 microphone 源生效(前 N 块自适应阈值);
6+ * loopback(网课系统环回)为数字信号无环境底噪,构造即已校准用预设阈值。
7+ * P0-2 起叠加 Silero 精判:噪声抑制(暂存-确认两段式)+ 静音复核(低能量
8+ * 语音尾推迟分段,上限保护),概率源不可用时回退纯 RMS。
9+ * English: RMS-based speech segmentation for Path B; microphone sources get
10+ * adaptive calibration, loopback uses preset thresholds. Silero refinement
11+ * (P0-2) adds noise suppression and silence re-check with RMS fallback.
1512 */
1613
1714import type { AudioChunkData , AudioSegment , TimelineEntry } from './captureTypes' ;
1815import { encodeWavBase64 } from './wavEncoder' ;
19- import type { SileroProbSource } from './sileroVad' ;
20-
21- // ================================================================
22- // Silero 精判参数(P0-2)
23- // ================================================================
24-
25- /** RMS 超阈但 Silero 概率低于此值 → 候选噪声(连续 2 块确认后抑制) */
26- const SILERO_NOISE_MAX_PROB = 0.12 ;
27- /** 静音复核:该时间窗内 Silero 平均概率高于此值 → 视为语音仍在(推迟分段) */
28- const SILERO_SPEECH_MIN_PROB = 0.5 ;
29- /** 静音复核时间窗(ms) */
30- const SILERO_RECHECK_WINDOW_MS = 400 ;
31- /** 静音复核推迟上限(ms):超限强制分段,防止 Silero 误判长噪声为语音导致段无限拉长 */
32- const SILERO_MAX_SILENCE_EXTENSION_MS = 3000 ;
16+ import {
17+ classifySileroProb ,
18+ SILERO_RECHECK_WINDOW_MS ,
19+ SILERO_SPEECH_MIN_PROB ,
20+ SILERO_MAX_SILENCE_EXTENSION_MS ,
21+ type SileroProbSource ,
22+ } from './sileroVad' ;
3323
3424// ================================================================
3525// 配置类型
@@ -42,15 +32,9 @@ export interface VADMarkerConfig {
4232 silenceDurationMs : number ;
4333 /** 最短语音时长(ms),低于则丢弃,默认 300 */
4434 minSpeechDurationMs : number ;
45- /**
46- * 最长语音段时长(ms),达到即强制分段,默认 28000——
47- * 保证段长兼容 GLM-ASR 备选(≤30s 硬限制),也避免长段拉高转写延迟
48- */
35+ /** 最长语音段时长(ms),达到即强制分段,默认 28000(兼容 GLM-ASR ≤30s 硬限制) */
4936 maxSpeechDurationMs : number ;
50- /**
51- * 音频源类型:loopback(系统环回,网课默认)跳过背景噪声校准;
52- * microphone(现场课程麦克风)启用前 N 块自适应校准
53- */
37+ /** 音频源类型:loopback 跳过背景噪声校准;microphone 启用前 N 块自适应校准 */
5438 sourceType : 'loopback' | 'microphone' ;
5539}
5640
@@ -280,18 +264,15 @@ export class VADMarker {
280264 }
281265
282266 /**
283- * Silero 三态分类(P0-2):
284- * - 'noise':最近概率与窗口均值都明确低于阈值(键盘/空调/音乐等持续噪声)
285- * - 'speech':概率明确高于噪声阈值(真实语音)
286- * - 'unknown':概率源不可用或暂无结果(不干预,走纯 RMS 路径)
267+ * Silero 三态分类(P0-2,纯函数委托见 sileroVad.classifySileroProb):
268+ * 'noise'=持续噪声 / 'speech'=真实语音 / 'unknown'=概率源不可用(不干预)
287269 */
288270 private classifySilero ( ) : 'noise' | 'speech' | 'unknown' {
289271 if ( ! this . silero ) return 'unknown' ;
290- const latest = this . silero . latestProb ( ) ;
291- const recent = this . silero . recentProb ( SILERO_RECHECK_WINDOW_MS ) ;
292- if ( latest === null || recent === null ) return 'unknown' ;
293- if ( latest < SILERO_NOISE_MAX_PROB && recent < SILERO_NOISE_MAX_PROB ) return 'noise' ;
294- return 'speech' ;
272+ return classifySileroProb (
273+ this . silero . latestProb ( ) ,
274+ this . silero . recentProb ( SILERO_RECHECK_WINDOW_MS ) ,
275+ ) ;
295276 }
296277
297278 // ================================================================
0 commit comments