flexaudio_vad/
segmenter.rs1use crate::config::VadConfig;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Segment {
15 pub start_sample: u64,
17 pub end_sample: u64,
19}
20
21impl Segment {
22 pub fn start_ms(&self, sample_rate: u32) -> u64 {
24 (self.start_sample * 1000) / u64::from(sample_rate.max(1))
25 }
26
27 pub fn end_ms(&self, sample_rate: u32) -> u64 {
29 (self.end_sample * 1000) / u64::from(sample_rate.max(1))
30 }
31
32 pub fn len_samples(&self) -> u64 {
34 self.end_sample.saturating_sub(self.start_sample)
35 }
36}
37
38#[derive(Debug, Clone)]
43pub struct Segmenter {
44 threshold: f32,
45 neg_threshold: f32,
46 min_speech_samples: u64,
47 min_silence_samples: u64,
48 speech_pad_samples: u64,
49 max_speech_samples: u64, frame_size: u64,
51
52 triggered: bool,
54 current_start: u64,
56 temp_end: u64,
58 next_pos: u64,
60 prev_end: u64,
62}
63
64impl Segmenter {
65 pub fn new(config: &VadConfig) -> Self {
67 Segmenter {
68 threshold: config.threshold,
69 neg_threshold: config.resolved_neg_threshold(),
70 min_speech_samples: config.ms_to_samples(config.min_speech_ms),
71 min_silence_samples: config.ms_to_samples(config.min_silence_ms),
72 speech_pad_samples: config.ms_to_samples(config.speech_pad_ms),
73 max_speech_samples: config.ms_to_samples(config.max_speech_ms),
74 frame_size: config.frame_size() as u64,
75 triggered: false,
76 current_start: 0,
77 temp_end: 0,
78 next_pos: 0,
79 prev_end: 0,
80 }
81 }
82
83 pub fn reset(&mut self) {
85 self.triggered = false;
86 self.current_start = 0;
87 self.temp_end = 0;
88 self.next_pos = 0;
89 self.prev_end = 0;
90 }
91
92 pub fn feed(&mut self, prob: f32, out: &mut Vec<Segment>) {
97 let frame_start = self.next_pos;
99 let frame_end = frame_start + self.frame_size;
100 self.next_pos = frame_end;
101
102 if prob >= self.threshold {
104 if self.temp_end != 0 {
106 self.temp_end = 0;
107 }
108 if !self.triggered {
109 self.triggered = true;
110 self.current_start = frame_start;
112 }
113 self.check_max_speech(frame_end, out);
115 return;
116 }
117
118 if prob < self.neg_threshold && self.triggered {
120 if self.temp_end == 0 {
121 self.temp_end = frame_start;
122 }
123 if frame_start.saturating_sub(self.temp_end) >= self.min_silence_samples {
125 self.finalize_segment(self.current_start, self.temp_end, out);
126 self.triggered = false;
127 self.temp_end = 0;
128 return;
129 }
130 }
132 self.check_max_speech(frame_end, out);
137 }
138
139 fn check_max_speech(&mut self, frame_end: u64, out: &mut Vec<Segment>) {
143 if !self.triggered || self.max_speech_samples == 0 {
144 return;
145 }
146 let speech_len = frame_end.saturating_sub(self.current_start);
147 if speech_len <= self.max_speech_samples {
148 return;
149 }
150 if self.temp_end != 0 {
151 let split = self.temp_end;
153 self.finalize_segment(self.current_start, split, out);
154 self.current_start = split;
155 self.temp_end = 0;
156 } else {
157 self.finalize_segment(self.current_start, frame_end, out);
159 self.current_start = frame_end;
160 }
161 }
162
163 pub fn flush(&mut self, out: &mut Vec<Segment>) {
166 if self.triggered {
167 self.finalize_segment(self.current_start, self.next_pos, out);
168 self.triggered = false;
169 self.temp_end = 0;
170 }
171 }
172
173 fn finalize_segment(&mut self, raw_start: u64, raw_end: u64, out: &mut Vec<Segment>) {
175 if raw_end.saturating_sub(raw_start) < self.min_speech_samples {
177 return;
178 }
179
180 let mut start = raw_start.saturating_sub(self.speech_pad_samples);
182 let end = raw_end + self.speech_pad_samples;
183
184 if self.prev_end != 0 && start < self.prev_end {
188 start = self.prev_end;
189 }
190
191 self.prev_end = end;
192 out.push(Segment {
193 start_sample: start,
194 end_sample: end,
195 });
196 }
197}