Skip to content

Commit b9bcf9d

Browse files
author
xiaoyunchong.xyc
committed
feat: add dynamic silence threshold to fsmn-vad model (default on)
- Modify FsmnVADStreaming.inference() to support dynamic_silence=True (default) - Non-streaming schedule: gradually tighten threshold as speech accumulates - Streaming uses STREAMING_SILENCE_SCHEDULE via DynamicStreamingVAD wrapper - Can disable with dynamic_silence=False or customize via silence_schedule param - Tracks VAD state to only accumulate during active speech
1 parent 7be505f commit b9bcf9d

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

  • funasr/models/fsmn_vad_streaming

funasr/models/fsmn_vad_streaming/model.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@
1818
from funasr.utils.datadir_writer import DatadirWriter
1919
from funasr.utils.load_utils import load_audio_text_image_video, extract_fbank
2020

21+
# Dynamic silence threshold schedule: (accumulated_speech_ms, silence_threshold_ms)
22+
# Streaming mode: longer initial patience (waiting for user to continue)
23+
STREAMING_SILENCE_SCHEDULE = [
24+
(5000, 2000),
25+
(10000, 1500),
26+
(15000, 1000),
27+
(30000, 800),
28+
(45000, 400),
29+
(float("inf"), 100),
30+
]
31+
32+
# Non-streaming mode: shorter patience (audio already complete, cut by natural pauses)
33+
DEFAULT_SILENCE_SCHEDULE = [
34+
(5000, 800),
35+
(10000, 600),
36+
(20000, 500),
37+
(30000, 400),
38+
(float("inf"), 300),
39+
]
40+
2141

2242
class VadStateMachine(Enum):
2343
kVadInStateStartPointNotDetected = 1
@@ -914,10 +934,32 @@ def inference(
914934
n = int(len(audio_sample) // chunk_stride_samples + int(_is_final))
915935
m = int(len(audio_sample) % chunk_stride_samples * (1 - int(_is_final)))
916936
segments = []
937+
# Dynamic silence threshold
938+
dynamic_silence = kwargs.get("dynamic_silence", True)
939+
silence_schedule = kwargs.get("silence_schedule", DEFAULT_SILENCE_SCHEDULE)
940+
speech_to_sil_ms = self.vad_opts.speech_to_sil_time_thres
941+
accumulated_ms = cache.get("_dynamic_accumulated_ms", 0)
942+
in_speech = cache.get("_dynamic_in_speech", False)
943+
917944
for i in range(n):
918945
kwargs["is_final"] = _is_final and i == n - 1
919946
audio_sample_i = audio_sample[i * chunk_stride_samples : (i + 1) * chunk_stride_samples]
920947

948+
# Apply dynamic silence threshold (only accumulate while in speech)
949+
if dynamic_silence and "stats" in cache:
950+
vad_state = cache["stats"].vad_state_machine
951+
# VadStateMachine: 2=kVadInStateStartPointNotDetected, 3=kVadInStateInSpeechSegment
952+
if vad_state.value == 2 or in_speech: # kVadInStateInSpeechSegment
953+
accumulated_ms += chunk_size
954+
in_speech = True
955+
for limit_ms, silence_ms in silence_schedule:
956+
if accumulated_ms <= limit_ms:
957+
cache["stats"].max_end_sil_frame_cnt_thresh = max(silence_ms - speech_to_sil_ms, 0)
958+
cache["stats"].speech_noise_thres = 0.5
959+
break
960+
cache["_dynamic_accumulated_ms"] = accumulated_ms
961+
cache["_dynamic_in_speech"] = in_speech
962+
921963
# extract fbank feats
922964
speech, speech_lengths = extract_fbank(
923965
[audio_sample_i],
@@ -944,6 +986,11 @@ def inference(
944986
segments_i = self.forward(**batch)
945987
if len(segments_i) > 0:
946988
segments.extend(*segments_i)
989+
if dynamic_silence:
990+
accumulated_ms = 0
991+
in_speech = False
992+
cache["_dynamic_accumulated_ms"] = 0
993+
cache["_dynamic_in_speech"] = False
947994

948995
cache["prev_samples"] = audio_sample[-m:] if m > 0 else torch.empty(0)
949996
if _is_final:

0 commit comments

Comments
 (0)