Skip to main content

rtp_engine/
resample.rs

1//! Audio resampling utilities.
2//!
3//! Provides sample rate conversion between codec rates (typically 8kHz)
4//! and device rates (typically 44.1kHz or 48kHz).
5
6/// Resample audio from one sample rate to another using linear interpolation.
7///
8/// # Arguments
9/// * `input` - Input samples
10/// * `from_rate` - Source sample rate in Hz
11/// * `to_rate` - Target sample rate in Hz
12///
13/// # Returns
14/// Resampled audio samples.
15pub fn resample_linear(input: &[f32], from_rate: u32, to_rate: u32) -> Vec<f32> {
16    if from_rate == to_rate || input.is_empty() {
17        return input.to_vec();
18    }
19
20    let ratio = from_rate as f64 / to_rate as f64;
21    let output_len = ((input.len() as f64) / ratio).ceil() as usize;
22    let mut output = Vec::with_capacity(output_len);
23
24    for i in 0..output_len {
25        let src_idx_f = i as f64 * ratio;
26        let src_idx = src_idx_f as usize;
27        let frac = src_idx_f - src_idx as f64;
28
29        let s0 = input.get(src_idx).copied().unwrap_or(0.0) as f64;
30        let s1 = input
31            .get(src_idx + 1)
32            .copied()
33            .unwrap_or_else(|| input.get(src_idx).copied().unwrap_or(0.0)) as f64;
34
35        let sample = s0 + frac * (s1 - s0);
36        output.push(sample as f32);
37    }
38
39    output
40}
41
42/// Resample i16 PCM audio from one sample rate to another.
43pub fn resample_linear_i16(input: &[i16], from_rate: u32, to_rate: u32) -> Vec<i16> {
44    if from_rate == to_rate || input.is_empty() {
45        return input.to_vec();
46    }
47
48    let ratio = from_rate as f64 / to_rate as f64;
49    let output_len = ((input.len() as f64) / ratio).ceil() as usize;
50    let mut output = Vec::with_capacity(output_len);
51
52    for i in 0..output_len {
53        let src_idx_f = i as f64 * ratio;
54        let src_idx = src_idx_f as usize;
55        let frac = src_idx_f - src_idx as f64;
56
57        let s0 = input.get(src_idx).copied().unwrap_or(0) as f64;
58        let s1 = input
59            .get(src_idx + 1)
60            .copied()
61            .unwrap_or_else(|| input.get(src_idx).copied().unwrap_or(0)) as f64;
62
63        let sample = s0 + frac * (s1 - s0);
64        output.push(sample.round() as i16);
65    }
66
67    output
68}
69
70/// Convert f32 samples (-1.0 to 1.0) to i16 samples.
71pub fn f32_to_i16(input: &[f32]) -> Vec<i16> {
72    input.iter().map(|&s| (s * 32767.0) as i16).collect()
73}
74
75/// Convert i16 samples to f32 samples (-1.0 to 1.0).
76pub fn i16_to_f32(input: &[i16]) -> Vec<f32> {
77    input.iter().map(|&s| s as f32 / 32768.0).collect()
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_resample_upsample() {
86        // Upsample 8kHz to 48kHz (6x)
87        let input: Vec<f32> = vec![0.0, 0.5, 1.0, 0.5, 0.0];
88        let output = resample_linear(&input, 8000, 48000);
89
90        // Should have roughly 6x more samples
91        assert!(output.len() >= 25);
92        assert!(output.len() <= 35);
93    }
94
95    #[test]
96    fn test_resample_downsample() {
97        // Downsample 48kHz to 8kHz (1/6x)
98        let input: Vec<f32> = (0..960).map(|i| (i as f32 / 960.0) * 2.0 - 1.0).collect();
99        let output = resample_linear(&input, 48000, 8000);
100
101        // Should have roughly 1/6 the samples
102        assert!(output.len() >= 150);
103        assert!(output.len() <= 180);
104    }
105
106    #[test]
107    fn test_resample_same_rate() {
108        let input: Vec<f32> = vec![0.1, 0.2, 0.3, 0.4, 0.5];
109        let output = resample_linear(&input, 8000, 8000);
110
111        assert_eq!(input, output);
112    }
113
114    #[test]
115    fn test_resample_empty() {
116        let input: Vec<f32> = vec![];
117        let output = resample_linear(&input, 8000, 48000);
118
119        assert!(output.is_empty());
120    }
121
122    #[test]
123    fn test_f32_i16_conversion() {
124        let f32_samples = vec![0.0, 0.5, -0.5, 1.0, -1.0];
125        let i16_samples = f32_to_i16(&f32_samples);
126
127        assert_eq!(i16_samples[0], 0);
128        assert!((i16_samples[1] - 16383).abs() < 2);
129        assert!((i16_samples[2] + 16383).abs() < 2);
130
131        let back = i16_to_f32(&i16_samples);
132        for (a, b) in f32_samples.iter().zip(back.iter()) {
133            assert!((a - b).abs() < 0.001);
134        }
135    }
136
137    #[test]
138    fn test_resample_i16() {
139        let input: Vec<i16> = vec![0, 16384, 32767, 16384, 0];
140        let output = resample_linear_i16(&input, 8000, 48000);
141
142        assert!(output.len() >= 25);
143    }
144
145    #[test]
146    fn test_resample_single_sample() {
147        let input: Vec<f32> = vec![0.5];
148        let output = resample_linear(&input, 8000, 48000);
149
150        // Single sample replicates based on ratio
151        assert!(!output.is_empty());
152        assert!(output.iter().all(|&v| (v - 0.5).abs() < 0.001));
153    }
154
155    #[test]
156    fn test_resample_boundary_values() {
157        let input: Vec<f32> = vec![-1.0, 1.0, -1.0, 1.0];
158        let output = resample_linear(&input, 8000, 16000);
159
160        // Check values stay within [-1, 1]
161        for v in &output {
162            assert!(*v >= -1.0 && *v <= 1.0);
163        }
164    }
165
166    #[test]
167    fn test_f32_to_i16_clipping() {
168        // Test values beyond [-1, 1] range
169        let input = vec![1.5, -1.5, 2.0, -2.0];
170        let output = f32_to_i16(&input);
171
172        // Should clip to i16::MAX and i16::MIN
173        assert_eq!(output[0], i16::MAX);
174        assert_eq!(output[1], i16::MIN);
175        assert_eq!(output[2], i16::MAX);
176        assert_eq!(output[3], i16::MIN);
177    }
178
179    #[test]
180    fn test_i16_to_f32_range() {
181        let input = vec![i16::MIN, 0, i16::MAX];
182        let output = i16_to_f32(&input);
183
184        assert!(output[0] <= -0.99 && output[0] >= -1.0);
185        assert!((output[1]).abs() < 0.001);
186        assert!(output[2] >= 0.99 && output[2] <= 1.0);
187    }
188
189    #[test]
190    fn test_resample_various_ratios() {
191        let input: Vec<f32> = (0..100).map(|i| (i as f32 / 100.0) * 2.0 - 1.0).collect();
192
193        // Test various common sample rate conversions
194        let rates = [
195            (8000, 16000),
196            (16000, 8000),
197            (8000, 44100),
198            (44100, 8000),
199            (48000, 44100),
200            (44100, 48000),
201        ];
202
203        for (from, to) in rates {
204            let output = resample_linear(&input, from, to);
205            let expected_len = (input.len() as f64 * to as f64 / from as f64) as usize;
206            // Allow some tolerance for rounding
207            assert!(
208                (output.len() as i64 - expected_len as i64).abs() <= 1,
209                "Resample {}->{}: expected ~{}, got {}",
210                from,
211                to,
212                expected_len,
213                output.len()
214            );
215        }
216    }
217
218    #[test]
219    fn test_resample_preserves_dc() {
220        // DC signal (constant value) should remain constant
221        let input: Vec<f32> = vec![0.5; 100];
222        let output = resample_linear(&input, 8000, 48000);
223
224        for v in &output {
225            assert!((v - 0.5).abs() < 0.001, "DC not preserved: {}", v);
226        }
227    }
228}