veridian_kernel/audio/codecs/
mod.rs1#![allow(dead_code)]
35
36pub mod mp3;
37pub mod vorbis;
38
39pub(crate) type Fp16 = i32;
45
46pub(crate) type Fp30 = i32;
48
49pub(crate) const FP16_SHIFT: i32 = 16;
51
52pub(crate) const FP16_ONE: Fp16 = 1 << FP16_SHIFT;
54
55pub(crate) const FP30_SHIFT: i32 = 30;
57
58pub(crate) const FP30_ONE: Fp30 = 1 << FP30_SHIFT;
60
61#[inline]
63pub(crate) fn fp30_mul(a: Fp30, b: Fp30) -> Fp30 {
64 let result = (a as i64).checked_mul(b as i64).unwrap_or(0) >> FP30_SHIFT;
65 result as Fp30
66}
67
68#[inline]
70pub(crate) fn fp16_mul(a: Fp16, b: Fp16) -> Fp16 {
71 let result = (a as i64).checked_mul(b as i64).unwrap_or(0) >> FP16_SHIFT;
72 if result > i32::MAX as i64 {
73 i32::MAX
74 } else if result < i32::MIN as i64 {
75 i32::MIN
76 } else {
77 result as i32
78 }
79}
80
81#[inline]
83pub(crate) fn fp30_to_fp16(v: Fp30) -> Fp16 {
84 v >> (FP30_SHIFT - FP16_SHIFT)
85}
86
87#[inline]
89pub(crate) fn fp16_from_i32(v: i32) -> Fp16 {
90 v.checked_shl(FP16_SHIFT as u32)
91 .unwrap_or(if v >= 0 { i32::MAX } else { i32::MIN })
92}
93
94#[inline]
96pub(crate) fn fp16_to_i16(fp: Fp16) -> i16 {
97 let shifted = fp >> FP16_SHIFT;
98 if shifted > i16::MAX as i32 {
99 i16::MAX
100 } else if shifted < i16::MIN as i32 {
101 i16::MIN
102 } else {
103 shifted as i16
104 }
105}
106
107#[inline]
112pub(crate) fn read_u8(data: &[u8], pos: usize) -> Option<u8> {
113 data.get(pos).copied()
114}
115
116#[inline]
117pub(crate) fn read_u16_le(data: &[u8], pos: usize) -> Option<u16> {
118 if pos + 2 > data.len() {
119 return None;
120 }
121 Some(u16::from_le_bytes([data[pos], data[pos + 1]]))
122}
123
124#[inline]
125pub(crate) fn read_u32_le(data: &[u8], pos: usize) -> Option<u32> {
126 if pos + 4 > data.len() {
127 return None;
128 }
129 Some(u32::from_le_bytes([
130 data[pos],
131 data[pos + 1],
132 data[pos + 2],
133 data[pos + 3],
134 ]))
135}
136
137#[inline]
138pub(crate) fn read_u64_le(data: &[u8], pos: usize) -> Option<u64> {
139 if pos + 8 > data.len() {
140 return None;
141 }
142 Some(u64::from_le_bytes([
143 data[pos],
144 data[pos + 1],
145 data[pos + 2],
146 data[pos + 3],
147 data[pos + 4],
148 data[pos + 5],
149 data[pos + 6],
150 data[pos + 7],
151 ]))
152}
153
154#[inline]
155pub(crate) fn read_u16_be(data: &[u8], pos: usize) -> Option<u16> {
156 if pos + 2 > data.len() {
157 return None;
158 }
159 Some(u16::from_be_bytes([data[pos], data[pos + 1]]))
160}
161
162#[inline]
163pub(crate) fn read_u32_be(data: &[u8], pos: usize) -> Option<u32> {
164 if pos + 4 > data.len() {
165 return None;
166 }
167 Some(u32::from_be_bytes([
168 data[pos],
169 data[pos + 1],
170 data[pos + 2],
171 data[pos + 3],
172 ]))
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum CodecError {
182 BufferTooShort,
184 InvalidMagic,
186 CrcMismatch,
188 UnsupportedVersion,
190 InvalidHeader,
192 UnsupportedFeature,
194 HuffmanError,
196 BitstreamCorrupt,
198 UnsupportedChannels,
200 UnsupportedSampleRate,
202 InternalOverflow,
204 EndOfStream,
206}
207
208impl core::fmt::Display for CodecError {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 match self {
211 CodecError::BufferTooShort => write!(f, "buffer too short"),
212 CodecError::InvalidMagic => write!(f, "invalid magic/sync"),
213 CodecError::CrcMismatch => write!(f, "CRC mismatch"),
214 CodecError::UnsupportedVersion => write!(f, "unsupported version"),
215 CodecError::InvalidHeader => write!(f, "invalid header"),
216 CodecError::UnsupportedFeature => write!(f, "unsupported feature"),
217 CodecError::HuffmanError => write!(f, "huffman decode error"),
218 CodecError::BitstreamCorrupt => write!(f, "bitstream corrupt"),
219 CodecError::UnsupportedChannels => write!(f, "unsupported channels"),
220 CodecError::UnsupportedSampleRate => write!(f, "unsupported sample rate"),
221 CodecError::InternalOverflow => write!(f, "internal overflow"),
222 CodecError::EndOfStream => write!(f, "end of stream"),
223 }
224 }
225}
226
227pub type CodecResult<T> = Result<T, CodecError>;
229
230pub(crate) const MDCT_COS_TABLE_64: [Fp30; 64] = {
239 let mut table = [0i32; 64];
240 table[0] = 0x4000_0000; let cos_step: i64 = 0x3FFF_B10B;
243
244 table[1] = cos_step as i32;
245
246 let mut i = 2usize;
247 while i < 64 {
248 let prev1 = table[i - 1] as i64;
249 let prev2 = table[i - 2] as i64;
250 let val = ((2 * cos_step * prev1) >> 30) - prev2;
251 if val > 0x4000_0000 {
252 table[i] = 0x4000_0000;
253 } else if val < -0x4000_0000 {
254 table[i] = -0x4000_0000;
255 } else {
256 table[i] = val as i32;
257 }
258 i += 1;
259 }
260
261 table
262};
263
264pub(crate) fn get_cos_from_table(table_idx: usize) -> Fp30 {
268 let idx = table_idx % 256; let half = idx % 128; let base_idx = if half < 64 { half } else { 127 - half };
272 let base_val = MDCT_COS_TABLE_64[base_idx.min(63)];
273
274 if (64..192).contains(&idx) {
276 -base_val
277 } else {
278 base_val
279 }
280}
281
282pub(crate) fn integer_cos_fp30(angle_q30: Fp30) -> Fp30 {
289 let x = angle_q30 as i64;
290 let x2 = (x.checked_mul(x).unwrap_or(0)) >> FP30_SHIFT;
291 let x4 = (x2.checked_mul(x2).unwrap_or(0)) >> FP30_SHIFT;
292
293 let one = FP30_ONE as i64;
294 let term2 = x2 >> 1;
295 let inv_24 = (FP30_ONE as i64) / 24;
296 let term4 = (x4.checked_mul(inv_24).unwrap_or(0)) >> FP30_SHIFT;
297
298 let result = one - term2 + term4;
299
300 if result > FP30_ONE as i64 {
301 FP30_ONE
302 } else if result < -(FP30_ONE as i64) {
303 -FP30_ONE
304 } else {
305 result as Fp30
306 }
307}
308
309pub(crate) fn ilog(val: u32) -> u8 {
311 if val == 0 {
312 return 0;
313 }
314 32 - val.leading_zeros() as u8
315}
316
317pub use mp3::*;
319pub use vorbis::*;
320
321#[cfg(test)]
322mod tests {
323 #[allow(unused_imports)]
324 use alloc::vec;
325
326 use super::*;
327
328 #[test]
331 fn test_fp30_mul_identity() {
332 let result = fp30_mul(FP30_ONE, FP30_ONE);
333 assert_eq!(result, FP30_ONE);
334 }
335
336 #[test]
337 fn test_fp30_mul_half() {
338 let half = FP30_ONE / 2;
339 let result = fp30_mul(half, FP30_ONE);
340 assert_eq!(result, half);
341 }
342
343 #[test]
344 fn test_fp16_mul_saturation() {
345 let big = i32::MAX;
346 let result = fp16_mul(big, FP16_ONE * 2);
347 assert_eq!(result, i32::MAX);
348 }
349
350 #[test]
351 fn test_fp16_to_i16_clamp() {
352 assert_eq!(fp16_to_i16(fp16_from_i32(0)), 0);
353 assert_eq!(fp16_to_i16(fp16_from_i32(100)), 100);
354 assert_eq!(fp16_to_i16(fp16_from_i32(-100)), -100);
355 assert_eq!(fp16_to_i16(i32::MAX), i16::MAX);
356 assert_eq!(fp16_to_i16(i32::MIN), i16::MIN);
357 }
358
359 #[test]
362 fn test_ilog() {
363 assert_eq!(ilog(0), 0);
364 assert_eq!(ilog(1), 1);
365 assert_eq!(ilog(2), 2);
366 assert_eq!(ilog(3), 2);
367 assert_eq!(ilog(4), 3);
368 assert_eq!(ilog(255), 8);
369 assert_eq!(ilog(256), 9);
370 }
371
372 #[test]
375 fn test_codec_error_display() {
376 let err = CodecError::BufferTooShort;
377 let msg = alloc::format!("{}", err);
378 assert_eq!(msg, "buffer too short");
379 }
380
381 #[test]
384 fn test_get_cos_from_table_symmetry() {
385 let cos_0 = get_cos_from_table(0);
386 assert!(cos_0 > 0);
387
388 let cos_90 = get_cos_from_table(64);
389 assert!(cos_90 <= 0);
390
391 let cos_180 = get_cos_from_table(128);
392 assert!(cos_180 < 0);
393 }
394}