1+ //! Streaming keyword spotting.
2+ //!
3+ //! This module detects predefined or per-stream override keywords from an
4+ //! online ASR model. See
5+ //! [`rust-api-examples/examples/keyword_spotter.rs`](https://github.com/k2-fsa/sherpa-onnx/blob/master/rust-api-examples/examples/keyword_spotter.rs)
6+ //! for a complete example.
7+ //!
8+ //! # Example
9+ //!
10+ //! ```no_run
11+ //! use sherpa_onnx::{KeywordSpotter, KeywordSpotterConfig, Wave};
12+ //!
13+ //! let wave = Wave::read("./test.wav").expect("read wave");
14+ //! let mut config = KeywordSpotterConfig::default();
15+ //! config.model_config.transducer.encoder = Some("./kws/encoder.onnx".into());
16+ //! config.model_config.transducer.decoder = Some("./kws/decoder.onnx".into());
17+ //! config.model_config.transducer.joiner = Some("./kws/joiner.onnx".into());
18+ //! config.model_config.tokens = Some("./kws/tokens.txt".into());
19+ //! config.keywords_file = Some("./keywords.txt".into());
20+ //!
21+ //! let kws = KeywordSpotter::create(&config).expect("create keyword spotter");
22+ //! let stream = kws.create_stream();
23+ //! stream.accept_waveform(wave.sample_rate(), wave.samples());
24+ //! stream.input_finished();
25+ //!
26+ //! while kws.is_ready(&stream) {
27+ //! kws.decode(&stream);
28+ //! }
29+ //!
30+ //! if let Some(result) = kws.get_result(&stream) {
31+ //! println!("{}", result.keyword);
32+ //! }
33+ //! ```
34+
135use crate :: online_asr:: { OnlineModelConfig , OnlineStream } ;
236use crate :: utils:: to_c_ptr;
337use sherpa_onnx_sys as sys;
@@ -8,11 +42,16 @@ fn c_ptr_to_string(ptr: *const c_char) -> String {
842 if ptr. is_null ( ) {
943 String :: new ( )
1044 } else {
11- unsafe { CStr :: from_ptr ( ptr) . to_string_lossy ( ) . into_owned ( ) }
45+ unsafe {
46+ CStr :: from_ptr ( ptr)
47+ . to_string_lossy ( )
48+ . into_owned ( )
49+ }
1250 }
1351}
1452
1553#[ derive( Clone , Debug ) ]
54+ /// Configuration for [`KeywordSpotter`].
1655pub struct KeywordSpotterConfig {
1756 pub feat_config : sys:: FeatureConfig ,
1857 pub model_config : OnlineModelConfig ,
@@ -46,19 +85,25 @@ impl KeywordSpotterConfig {
4685 fn to_sys ( & self , cstrings : & mut Vec < CString > ) -> sys:: KeywordSpotterConfig {
4786 sys:: KeywordSpotterConfig {
4887 feat_config : self . feat_config ,
49- model_config : self . model_config . to_sys ( cstrings) ,
88+ model_config : self
89+ . model_config
90+ . to_sys ( cstrings) ,
5091 max_active_paths : self . max_active_paths ,
5192 num_trailing_blanks : self . num_trailing_blanks ,
5293 keywords_score : self . keywords_score ,
5394 keywords_threshold : self . keywords_threshold ,
5495 keywords_file : to_c_ptr ( & self . keywords_file , cstrings) ,
5596 keywords_buf : to_c_ptr ( & self . keywords_buf , cstrings) ,
56- keywords_buf_size : self . keywords_buf . as_ref ( ) . map_or ( 0 , |s| s. len ( ) as i32 ) ,
97+ keywords_buf_size : self
98+ . keywords_buf
99+ . as_ref ( )
100+ . map_or ( 0 , |s| s. len ( ) as i32 ) ,
57101 }
58102 }
59103}
60104
61105#[ derive( Clone , Debug ) ]
106+ /// Decoded keyword spotting result for one stream.
62107pub struct KeywordResult {
63108 pub keyword : String ,
64109 pub tokens : String ,
@@ -68,13 +113,15 @@ pub struct KeywordResult {
68113 pub json : String ,
69114}
70115
116+ /// Streaming keyword spotter.
71117pub struct KeywordSpotter {
72118 ptr : * const sys:: KeywordSpotter ,
73119}
74120
75121unsafe impl Send for KeywordSpotter { }
76122
77123impl KeywordSpotter {
124+ /// Create a keyword spotter from [`KeywordSpotterConfig`].
78125 pub fn create ( config : & KeywordSpotterConfig ) -> Option < Self > {
79126 let mut cstrings = Vec :: new ( ) ;
80127 let sys_config = config. to_sys ( & mut cstrings) ;
@@ -86,38 +133,47 @@ impl KeywordSpotter {
86133 }
87134 }
88135
136+ /// Create a stream that uses the keywords configured in [`KeywordSpotterConfig`].
89137 pub fn create_stream ( & self ) -> OnlineStream {
90138 let ptr = unsafe { sys:: SherpaOnnxCreateKeywordStream ( self . ptr ) } ;
91139 OnlineStream { ptr }
92140 }
93141
142+ /// Create a stream that uses `keywords` instead of the configured keyword list.
94143 pub fn create_stream_with_keywords ( & self , keywords : & str ) -> OnlineStream {
95144 let keywords = CString :: new ( keywords) . unwrap ( ) ;
96- let ptr = unsafe {
97- sys:: SherpaOnnxCreateKeywordStreamWithKeywords ( self . ptr , keywords. as_ptr ( ) )
98- } ;
145+ let ptr =
146+ unsafe { sys:: SherpaOnnxCreateKeywordStreamWithKeywords ( self . ptr , keywords. as_ptr ( ) ) } ;
99147 OnlineStream { ptr }
100148 }
101149
150+ /// Return `true` if `stream` has enough audio for another decode step.
102151 pub fn is_ready ( & self , stream : & OnlineStream ) -> bool {
103152 unsafe { sys:: SherpaOnnxIsKeywordStreamReady ( self . ptr , stream. ptr ) != 0 }
104153 }
105154
155+ /// Decode one incremental step for `stream`.
106156 pub fn decode ( & self , stream : & OnlineStream ) {
107157 unsafe { sys:: SherpaOnnxDecodeKeywordStream ( self . ptr , stream. ptr ) }
108158 }
109159
160+ /// Decode multiple streams in one batch.
110161 pub fn decode_multiple_streams ( & self , streams : & [ & OnlineStream ] ) {
111- let ptrs: Vec < * const sys:: OnlineStream > = streams. iter ( ) . map ( |s| s. ptr ) . collect ( ) ;
162+ let ptrs: Vec < * const sys:: OnlineStream > = streams
163+ . iter ( )
164+ . map ( |s| s. ptr )
165+ . collect ( ) ;
112166 unsafe {
113167 sys:: SherpaOnnxDecodeMultipleKeywordStreams ( self . ptr , ptrs. as_ptr ( ) , ptrs. len ( ) as i32 )
114168 }
115169 }
116170
171+ /// Reset the detector state for `stream`.
117172 pub fn reset ( & self , stream : & OnlineStream ) {
118173 unsafe { sys:: SherpaOnnxResetKeywordStream ( self . ptr , stream. ptr ) }
119174 }
120175
176+ /// Get the structured keyword spotting result for `stream`.
121177 pub fn get_result ( & self , stream : & OnlineStream ) -> Option < KeywordResult > {
122178 unsafe {
123179 let p = sys:: SherpaOnnxGetKeywordResult ( self . ptr , stream. ptr ) ;
@@ -126,7 +182,11 @@ impl KeywordSpotter {
126182 }
127183
128184 let result = & * p;
129- let tokens_arr = if result. tokens_arr . is_null ( ) || result. count <= 0 {
185+ let tokens_arr = if result
186+ . tokens_arr
187+ . is_null ( )
188+ || result. count <= 0
189+ {
130190 Vec :: new ( )
131191 } else {
132192 slice:: from_raw_parts ( result. tokens_arr , result. count as usize )
@@ -135,7 +195,11 @@ impl KeywordSpotter {
135195 . collect ( )
136196 } ;
137197
138- let timestamps = if result. timestamps . is_null ( ) || result. count <= 0 {
198+ let timestamps = if result
199+ . timestamps
200+ . is_null ( )
201+ || result. count <= 0
202+ {
139203 Vec :: new ( )
140204 } else {
141205 slice:: from_raw_parts ( result. timestamps , result. count as usize ) . to_vec ( )
@@ -155,14 +219,17 @@ impl KeywordSpotter {
155219 }
156220 }
157221
222+ /// Get the result for `stream` as a JSON string.
158223 pub fn get_result_as_json ( & self , stream : & OnlineStream ) -> Option < String > {
159224 unsafe {
160225 let p = sys:: SherpaOnnxGetKeywordResultAsJson ( self . ptr , stream. ptr ) ;
161226 if p. is_null ( ) {
162227 return None ;
163228 }
164229
165- let ans = CStr :: from_ptr ( p) . to_string_lossy ( ) . into_owned ( ) ;
230+ let ans = CStr :: from_ptr ( p)
231+ . to_string_lossy ( )
232+ . into_owned ( ) ;
166233 sys:: SherpaOnnxFreeKeywordResultJson ( p) ;
167234 Some ( ans)
168235 }
@@ -172,7 +239,10 @@ impl KeywordSpotter {
172239impl Drop for KeywordSpotter {
173240 fn drop ( & mut self ) {
174241 unsafe {
175- if !self . ptr . is_null ( ) {
242+ if !self
243+ . ptr
244+ . is_null ( )
245+ {
176246 sys:: SherpaOnnxDestroyKeywordSpotter ( self . ptr ) ;
177247 }
178248 }
0 commit comments