1use anyhow::{Context, Result};
14use opus::{Application, Channels, Decoder as OpusDecoder, Encoder as OpusEncoder};
15
16pub const UPLINK_RATE: u32 = 16_000;
18pub const DOWNLINK_RATE: u32 = 24_000;
20pub const FRAME_MS: u32 = 60;
22
23const DOWNLINK_FRAME_SAMPLES: usize = (DOWNLINK_RATE as usize * FRAME_MS as usize) / 1000;
25const UPLINK_DECODE_CAP: usize = (UPLINK_RATE as usize * 120) / 1000;
28
29pub struct UplinkDecoder {
32 decoder: OpusDecoder,
33}
34
35impl UplinkDecoder {
36 pub fn new() -> Result<Self> {
37 let decoder = OpusDecoder::new(UPLINK_RATE, Channels::Mono)
38 .context("creating 16 kHz Opus decoder")?;
39 Ok(Self { decoder })
40 }
41
42 pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<i16>> {
44 let mut out = vec![0i16; UPLINK_DECODE_CAP];
45 let decoded = self
46 .decoder
47 .decode(packet, &mut out[..], false)
48 .context("decoding uplink Opus packet")?;
49 out.truncate(decoded);
50 Ok(out)
51 }
52}
53
54pub struct DownlinkEncoder {
56 encoder: OpusEncoder,
57}
58
59impl DownlinkEncoder {
60 pub fn new() -> Result<Self> {
61 let encoder = OpusEncoder::new(DOWNLINK_RATE, Channels::Mono, Application::Voip)
62 .context("creating 24 kHz Opus encoder")?;
63 Ok(Self { encoder })
64 }
65
66 pub fn encode_stream(&mut self, pcm: &[i16]) -> Result<Vec<Vec<u8>>> {
69 let mut packets = Vec::new();
70 let mut offset = 0;
71 let mut scratch = [0u8; 4000];
74 while offset < pcm.len() {
75 let end = (offset + DOWNLINK_FRAME_SAMPLES).min(pcm.len());
76 let mut frame: Vec<i16> = pcm[offset..end].to_vec();
77 if frame.len() < DOWNLINK_FRAME_SAMPLES {
78 frame.resize(DOWNLINK_FRAME_SAMPLES, 0);
79 }
80 let n = self
81 .encoder
82 .encode(&frame, &mut scratch)
83 .context("encoding downlink Opus frame")?;
84 packets.push(scratch[..n].to_vec());
85 offset += DOWNLINK_FRAME_SAMPLES;
86 }
87 Ok(packets)
88 }
89}
90
91pub fn pcm16_to_wav(pcm: &[i16], sample_rate: u32) -> Result<Vec<u8>> {
94 let spec = hound::WavSpec {
95 channels: 1,
96 sample_rate,
97 bits_per_sample: 16,
98 sample_format: hound::SampleFormat::Int,
99 };
100 let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
101 {
102 let mut writer = hound::WavWriter::new(&mut cursor, spec).context("creating WAV writer")?;
103 for &sample in pcm {
104 writer.write_sample(sample).context("writing WAV sample")?;
105 }
106 writer.finalize().context("finalizing WAV")?;
107 }
108 Ok(cursor.into_inner())
109}
110
111pub struct DecodedWav {
114 pub samples: Vec<i16>,
115 pub sample_rate: u32,
116}
117
118pub fn wav_to_pcm16(wav: &[u8]) -> Result<DecodedWav> {
122 let cursor = std::io::Cursor::new(wav);
123 let mut reader = hound::WavReader::new(cursor).context("parsing WAV header")?;
124 let spec = reader.spec();
125 let channels = spec.channels.max(1) as usize;
126
127 let interleaved: Vec<i32> = match spec.sample_format {
128 hound::SampleFormat::Int => reader
129 .samples::<i32>()
130 .collect::<std::result::Result<Vec<_>, _>>()
131 .context("reading int WAV samples")?,
132 hound::SampleFormat::Float => reader
133 .samples::<f32>()
134 .map(|s| s.map(|v| (v.clamp(-1.0, 1.0) * i16::MAX as f32) as i32))
135 .collect::<std::result::Result<Vec<_>, _>>()
136 .context("reading float WAV samples")?,
137 };
138
139 let shift = match spec.bits_per_sample {
141 0..=16 => 0,
142 bits => bits - 16,
143 };
144
145 let mut mono = Vec::with_capacity(interleaved.len() / channels.max(1));
146 for frame in interleaved.chunks(channels) {
147 let sum: i64 = frame.iter().map(|&s| (s >> shift) as i64).sum();
148 let avg = (sum / channels as i64).clamp(i16::MIN as i64, i16::MAX as i64);
149 mono.push(avg as i16);
150 }
151
152 Ok(DecodedWav {
153 samples: mono,
154 sample_rate: spec.sample_rate,
155 })
156}
157
158pub fn resample_to(pcm: &[i16], from_rate: u32, to_rate: u32) -> Vec<i16> {
163 if from_rate == to_rate || pcm.is_empty() {
164 return pcm.to_vec();
165 }
166 let ratio = to_rate as f64 / from_rate as f64;
167 let out_len = ((pcm.len() as f64) * ratio).round() as usize;
168 let mut out = Vec::with_capacity(out_len);
169 for i in 0..out_len {
170 let src_pos = i as f64 / ratio;
171 let idx = src_pos.floor() as usize;
172 let frac = src_pos - idx as f64;
173 let a = pcm.get(idx).copied().unwrap_or(0) as f64;
174 let b = pcm.get(idx + 1).copied().unwrap_or(a as i16) as f64;
175 out.push((a + (b - a) * frac).round() as i16);
176 }
177 out
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn wav_roundtrip_preserves_samples() {
186 let pcm: Vec<i16> = (0..480).map(|i| ((i * 137) % 1000 - 500) as i16).collect();
187 let wav = pcm16_to_wav(&pcm, UPLINK_RATE).unwrap();
188 let decoded = wav_to_pcm16(&wav).unwrap();
189 assert_eq!(decoded.sample_rate, UPLINK_RATE);
190 assert_eq!(decoded.samples, pcm);
191 }
192
193 #[test]
194 fn resample_is_noop_when_rates_match() {
195 let pcm = vec![1i16, 2, 3, 4];
196 assert_eq!(resample_to(&pcm, 24_000, 24_000), pcm);
197 }
198
199 #[test]
200 fn resample_doubles_length_when_rate_doubles() {
201 let pcm = vec![0i16, 100, 0, 100];
202 let out = resample_to(&pcm, 12_000, 24_000);
203 assert_eq!(out.len(), 8);
204 }
205
206 #[test]
207 fn opus_encode_then_decode_roundtrips_length() {
208 let mut enc = DownlinkEncoder::new().unwrap();
210 let tone: Vec<i16> = (0..DOWNLINK_FRAME_SAMPLES)
211 .map(|i| ((i as f64 * 0.2).sin() * 8000.0) as i16)
212 .collect();
213 let packets = enc.encode_stream(&tone).unwrap();
214 assert_eq!(packets.len(), 1);
215 assert!(!packets[0].is_empty());
216
217 let mut dec = OpusDecoder::new(DOWNLINK_RATE, Channels::Mono).unwrap();
218 let mut out = vec![0i16; DOWNLINK_FRAME_SAMPLES];
219 let n = dec.decode(&packets[0], &mut out[..], false).unwrap();
220 assert_eq!(n, DOWNLINK_FRAME_SAMPLES);
221 }
222}