1use std::os::raw::c_void;
9use std::sync::atomic::AtomicBool;
10use std::sync::Arc;
11
12use transcribe_cpp_sys as sys;
13
14use crate::cancel::{abort_trampoline, CancelToken};
15use crate::error::{check, Result};
16use crate::model::{Model, ModelInner};
17use crate::result::Timings;
18use crate::session::clamp_len;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[non_exhaustive]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct VadInfo {
25 pub sample_rate: i32,
27 pub frame_samples: i32,
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
33#[cfg_attr(
34 feature = "serde",
35 derive(serde::Serialize, serde::Deserialize),
36 serde(default)
37)]
38pub struct VadSessionOptions {
39 pub n_threads: i32,
41}
42
43#[derive(Debug, Clone, PartialEq)]
46#[cfg_attr(
47 feature = "serde",
48 derive(serde::Serialize, serde::Deserialize),
49 serde(default)
50)]
51pub struct VadOptions {
52 pub threshold: f64,
54 pub neg_threshold: f64,
58 pub min_speech_ms: i32,
61 pub min_silence_ms: i32,
63 pub speech_pad_ms: i32,
65 pub max_speech_ms: i32,
67}
68
69impl Default for VadOptions {
70 fn default() -> Self {
71 VadOptions {
72 threshold: 0.5,
73 neg_threshold: -1.0,
74 min_speech_ms: 250,
75 min_silence_ms: 100,
76 speech_pad_ms: 30,
77 max_speech_ms: 0,
78 }
79 }
80}
81
82#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
84#[non_exhaustive]
85#[cfg_attr(
86 feature = "serde",
87 derive(serde::Serialize, serde::Deserialize),
88 serde(default)
89)]
90pub struct VadSegment {
91 pub start_sample: i64,
93 pub end_sample: i64,
95}
96
97#[derive(Debug, Clone, Default, PartialEq)]
100#[non_exhaustive]
101#[cfg_attr(
102 feature = "serde",
103 derive(serde::Serialize, serde::Deserialize),
104 serde(default)
105)]
106pub struct VadResult {
107 pub probs: Vec<f32>,
109 pub first_frame: i64,
112 pub segments: Vec<VadSegment>,
114}
115
116pub struct VadSession {
118 ptr: *mut sys::transcribe_vad_session,
119 model: Arc<ModelInner>,
121 cancel: Option<Arc<AtomicBool>>,
123}
124
125impl std::fmt::Debug for VadSession {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.debug_struct("VadSession").finish_non_exhaustive()
128 }
129}
130
131unsafe impl Send for VadSession {}
134
135impl Drop for VadSession {
136 fn drop(&mut self) {
137 unsafe { sys::transcribe_vad_session_free(self.ptr) };
138 }
139}
140
141const BUSY_RUN: &str = "a stream is active on this model; finish or drop it before vad run()";
142const BUSY_FEED: &str =
143 "a stream is active on this model; finish or drop it before vad stream_feed()";
144const BUSY_FLUSH: &str =
145 "a stream is active on this model; finish or drop it before vad stream_flush()";
146
147fn copy_result(ptr: *mut sys::transcribe_vad_session) -> Result<VadResult> {
149 let mut res: sys::transcribe_vad_result = unsafe { std::mem::zeroed() };
150 unsafe { sys::transcribe_vad_result_init(&mut res) };
151 check(
152 unsafe { sys::transcribe_vad_get_result(ptr, &mut res) },
153 "vad result",
154 )?;
155 let p = unsafe { sys::transcribe_vad_probs(ptr) };
156 let probs = if p.is_null() || res.n_probs <= 0 {
157 Vec::new()
158 } else {
159 unsafe { std::slice::from_raw_parts(p, res.n_probs as usize) }.to_vec()
160 };
161 let segments = (0..res.n_segments)
162 .map(|i| {
163 let mut raw: sys::transcribe_vad_segment = unsafe { std::mem::zeroed() };
164 unsafe { sys::transcribe_vad_segment_init(&mut raw) };
165 let _ = unsafe { sys::transcribe_vad_get_segment(ptr, i, &mut raw) };
166 VadSegment {
167 start_sample: raw.start_sample,
168 end_sample: raw.end_sample,
169 }
170 })
171 .collect();
172 Ok(VadResult {
173 probs,
174 first_frame: res.first_frame,
175 segments,
176 })
177}
178
179impl VadSession {
180 pub(crate) fn new(model: &Model, options: &VadSessionOptions) -> Result<VadSession> {
181 let mut params: sys::transcribe_vad_session_params = unsafe { std::mem::zeroed() };
182 unsafe { sys::transcribe_vad_session_params_init(&mut params) };
183 params.n_threads = options.n_threads;
184
185 let mut out: *mut sys::transcribe_vad_session = std::ptr::null_mut();
186 let status =
187 unsafe { sys::transcribe_vad_session_init(model.inner.ptr, ¶ms, &mut out) };
188 check(status, "vad session init")?;
189 debug_assert!(!out.is_null());
190
191 Ok(VadSession {
192 ptr: out,
193 model: Arc::clone(&model.inner),
194 cancel: None,
195 })
196 }
197
198 pub fn set_cancel_token(&mut self, token: &CancelToken) {
202 let flag = Arc::clone(&token.flag);
203 let userdata = Arc::as_ptr(&flag) as *mut c_void;
204 unsafe {
205 sys::transcribe_vad_set_abort_callback(self.ptr, Some(abort_trampoline), userdata)
206 };
207 self.cancel = Some(flag);
208 }
209
210 pub fn clear_cancel_token(&mut self) {
212 unsafe { sys::transcribe_vad_set_abort_callback(self.ptr, None, std::ptr::null_mut()) };
213 self.cancel = None;
214 }
215
216 pub fn run(&mut self, pcm: &[f32], options: &VadOptions) -> Result<VadResult> {
219 let n = clamp_len(pcm.len())?;
220 let mut params: sys::transcribe_vad_params = unsafe { std::mem::zeroed() };
221 unsafe { sys::transcribe_vad_params_init(&mut params) };
222 params.threshold = options.threshold;
223 params.neg_threshold = options.neg_threshold;
224 params.min_speech_ms = options.min_speech_ms;
225 params.min_silence_ms = options.min_silence_ms;
226 params.speech_pad_ms = options.speech_pad_ms;
227 params.max_speech_ms = options.max_speech_ms;
228
229 let ptr = self.ptr;
232 self.model
233 .with_compute(Some(BUSY_RUN), |_| -> Result<VadResult> {
234 check(
235 unsafe { sys::transcribe_vad_run(ptr, pcm.as_ptr(), n, ¶ms) },
236 "vad run",
237 )?;
238 copy_result(ptr)
239 })?
240 }
241
242 pub fn stream_feed(&mut self, pcm: &[f32]) -> Result<VadResult> {
248 let n = clamp_len(pcm.len())?;
249 let ptr = self.ptr;
250 self.model
251 .with_compute(Some(BUSY_FEED), |_| -> Result<VadResult> {
252 check(
253 unsafe { sys::transcribe_vad_stream_feed(ptr, pcm.as_ptr(), n) },
254 "vad stream feed",
255 )?;
256 copy_result(ptr)
257 })?
258 }
259
260 pub fn stream_flush(&mut self) -> Result<VadResult> {
264 let ptr = self.ptr;
265 self.model
266 .with_compute(Some(BUSY_FLUSH), |_| -> Result<VadResult> {
267 check(
268 unsafe { sys::transcribe_vad_stream_flush(ptr) },
269 "vad stream flush",
270 )?;
271 copy_result(ptr)
272 })?
273 }
274
275 pub fn stream_reset(&mut self) {
277 let ptr = self.ptr;
278 let _ = self
279 .model
280 .with_compute(None, |_| unsafe { sys::transcribe_vad_stream_reset(ptr) });
281 }
282
283 pub fn timings(&self) -> Timings {
286 let mut raw: sys::transcribe_timings = unsafe { std::mem::zeroed() };
287 unsafe { sys::transcribe_timings_init(&mut raw) };
288 let _ = unsafe { sys::transcribe_vad_get_timings(self.ptr, &mut raw) };
289 Timings::from_raw(&raw)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::Error;
297 use std::sync::Mutex;
298
299 #[test]
300 fn calls_are_busy_while_a_stream_holds_the_lease() {
301 let mut session = VadSession {
304 ptr: std::ptr::null_mut(),
305 model: Arc::new(ModelInner {
306 ptr: std::ptr::null_mut(),
307 compute_lock: Mutex::new(true),
308 }),
309 cancel: None,
310 };
311 for (r, msg) in [
312 (session.run(&[0.0; 160], &VadOptions::default()), BUSY_RUN),
313 (session.stream_feed(&[0.0; 160]), BUSY_FEED),
314 (session.stream_flush(), BUSY_FLUSH),
315 ] {
316 assert!(matches!(r, Err(Error::Busy(ref m)) if m == msg), "{r:?}");
317 }
318 }
319
320 #[test]
321 fn defaults_match_the_c_init() {
322 let mut p: sys::transcribe_vad_params = unsafe { std::mem::zeroed() };
323 unsafe { sys::transcribe_vad_params_init(&mut p) };
324 let d = VadOptions::default();
325 assert_eq!(
326 (
327 d.threshold,
328 d.neg_threshold,
329 d.min_speech_ms,
330 d.min_silence_ms,
331 d.speech_pad_ms,
332 d.max_speech_ms
333 ),
334 (
335 p.threshold,
336 p.neg_threshold,
337 p.min_speech_ms,
338 p.min_silence_ms,
339 p.speech_pad_ms,
340 p.max_speech_ms
341 )
342 );
343 }
344}