subx_cli/services/vad/
resample.rs1use log::{debug, trace};
6use rubato::{FftFixedIn, Resampler};
7use std::error::Error;
8use std::time::Instant;
9
10use once_cell::sync::Lazy;
11use std::sync::Mutex;
12
13type FftResamplerCache = Option<(u32, u32, FftFixedIn<f32>)>;
14
15static FFT_RESAMPLER_CACHE: Lazy<Mutex<FftResamplerCache>> = Lazy::new(|| Mutex::new(None));
16
17pub fn resample_to_target_rate(
19 input_samples: &[i16],
20 input_sample_rate: u32,
21 output_sample_rate: u32,
22) -> Result<Vec<i16>, Box<dyn Error>> {
23 let total_start = Instant::now();
24 debug!(
25 "[resample] input_samples: {} input_sample_rate: {} output_sample_rate: {}",
26 input_samples.len(),
27 input_sample_rate,
28 output_sample_rate
29 );
30 if input_sample_rate == output_sample_rate {
31 debug!("[resample] sample rate unchanged, fast path");
32 return Ok(input_samples.to_vec());
33 }
34 let t_convert = Instant::now();
35 let input: Vec<f32> = input_samples.iter().map(|&s| s as f32 / 32768.0).collect();
37 debug!(
38 "[resample] i16->f32 conversion done in {:.3?}",
39 t_convert.elapsed()
40 );
41 let input_len = input.len();
42 let input_channels = 1;
43 let resample_ratio = output_sample_rate as f64 / input_sample_rate as f64;
44 let chunk_size = 8192; let t_resampler_init = Instant::now();
46 let mut resampler = {
47 let mut cache = FFT_RESAMPLER_CACHE.lock().unwrap();
48 if let Some((in_sr, out_sr, ref mut cached)) = *cache {
49 if in_sr == input_sample_rate && out_sr == output_sample_rate {
50 debug!("[resample] using cached FftFixedIn");
51 cached.reset();
52 let mut new_resampler = FftFixedIn::<f32>::new(
53 input_sample_rate as usize,
54 output_sample_rate as usize,
55 chunk_size,
56 1, input_channels,
58 )?;
59 std::mem::swap(&mut new_resampler, cached);
60 new_resampler
61 } else {
62 debug!("[resample] creating new FftFixedIn (cache miss)");
63 let new_resampler = FftFixedIn::<f32>::new(
64 input_sample_rate as usize,
65 output_sample_rate as usize,
66 chunk_size,
67 1, input_channels,
69 )?;
70 *cache = Some((input_sample_rate, output_sample_rate, new_resampler));
71 let mut new_resampler = FftFixedIn::<f32>::new(
72 input_sample_rate as usize,
73 output_sample_rate as usize,
74 chunk_size,
75 1, input_channels,
77 )?;
78 std::mem::swap(&mut new_resampler, &mut cache.as_mut().unwrap().2);
79 new_resampler
80 }
81 } else {
82 debug!("[resample] creating new FftFixedIn (cache empty)");
83 let new_resampler = FftFixedIn::<f32>::new(
84 input_sample_rate as usize,
85 output_sample_rate as usize,
86 chunk_size,
87 1, input_channels,
89 )?;
90 *cache = Some((input_sample_rate, output_sample_rate, new_resampler));
91 let mut new_resampler = FftFixedIn::<f32>::new(
92 input_sample_rate as usize,
93 output_sample_rate as usize,
94 chunk_size,
95 1, input_channels,
97 )?;
98 std::mem::swap(&mut new_resampler, &mut cache.as_mut().unwrap().2);
99 new_resampler
100 }
101 };
102 debug!(
103 "[resample] FftFixedIn ready in {:.3?}",
104 t_resampler_init.elapsed()
105 );
106 let mut output: Vec<f32> =
107 Vec::with_capacity((input_len as f64 * resample_ratio) as usize + 128);
108 let mut pos = 0;
109 let t_resample = Instant::now();
110 let mut chunk_count = 0;
111 while pos < input_len {
113 let frames_needed = resampler.input_frames_next();
114 let end = (pos + frames_needed).min(input_len);
115 let mut chunk: Vec<f32> = Vec::with_capacity(frames_needed);
116 chunk.extend_from_slice(&input[pos..end]);
117 if end - pos < frames_needed {
118 chunk.resize(frames_needed, 0.0);
120 }
121 let chunk_ref = [&chunk[..]];
122 trace!(
123 "[resample] chunk {}: pos={} end={} frames_needed={}",
124 chunk_count, pos, end, frames_needed
125 );
126 let out_chunk = resampler.process(&chunk_ref, None)?;
127 output.extend_from_slice(&out_chunk[0]);
128 pos += frames_needed;
129 chunk_count += 1;
130 }
131 debug!(
132 "[resample] main resample loop done in {:.3?} ({} chunks)",
133 t_resample.elapsed(),
134 chunk_count
135 );
136 let t_flush = Instant::now();
138 let mut flush_count = 0;
139 loop {
140 let out_chunk = resampler.process_partial::<Vec<f32>>(None, None)?;
141 if out_chunk[0].is_empty() {
142 break;
143 }
144 output.extend_from_slice(&out_chunk[0]);
145 flush_count += 1;
146 }
147 debug!(
148 "[resample] flush done in {:.3?} ({} flushes)",
149 t_flush.elapsed(),
150 flush_count
151 );
152 let t_i16 = Instant::now();
154 let expected_len = ((input_samples.len() as f64) * resample_ratio).round() as usize;
156 let mut output_i16: Vec<i16> = output
157 .iter()
158 .map(|&s| (s.clamp(-1.0, 1.0) * 32767.0) as i16)
159 .collect();
160 if output_i16.len() > expected_len {
161 output_i16.truncate(expected_len);
162 }
163 debug!(
164 "[resample] f32->i16 conversion done in {:.3?}",
165 t_i16.elapsed()
166 );
167 debug!(
168 "[resample] total elapsed: {:.3?} (input {} -> output {} samples)",
169 total_start.elapsed(),
170 input_samples.len(),
171 output_i16.len()
172 );
173 Ok(output_i16)
174}