⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/audio/codecs/
mod.rs

1//! OGG Vorbis and MP3 audio decoders (integer-only, no_std)
2//!
3//! Provides software decoding of OGG Vorbis and MPEG-1 Layer III (MP3) audio
4//! streams using only integer and fixed-point arithmetic. No floating-point
5//! operations are used, making these decoders suitable for kernel context
6//! where FPU state may not be available.
7//!
8//! ## Fixed-Point Formats
9//!
10//! - **16.16**: General-purpose, used for volume/gain. 1.0 = `0x0001_0000`.
11//! - **2.30**: High-precision twiddle factors for MDCT/IMDCT. 1.0 =
12//!   `0x4000_0000`.
13//!
14//! ## OGG Vorbis
15//!
16//! Implements the OGG container (RFC 3533) and Vorbis I codec (Xiph.org spec):
17//! - OGG page parsing with CRC32 verification
18//! - Vorbis identification, comment, and setup header decoding
19//! - Floor type 1, residue types 0/1/2, codebook Huffman trees
20//! - MDCT via integer butterfly operations with 2.30 twiddle factors
21//! - Vorbis window function via fixed-point lookup table
22//!
23//! ## MP3 (MPEG-1 Layer III)
24//!
25//! Implements ISO 11172-3 / ISO 13818-3 Layer III decoding:
26//! - Frame sync and header parsing (bitrate, sample rate, channel mode)
27//! - Side information and scalefactor band decoding
28//! - Huffman decoding (33 tables)
29//! - Integer requantization via lookup tables
30//! - Joint stereo (MS and intensity)
31//! - 36-point and 12-point IMDCT via integer butterflies
32//! - 32-subband synthesis polyphase filterbank (integer coefficients)
33
34#![allow(dead_code)]
35
36pub mod mp3;
37pub mod vorbis;
38
39// ============================================================================
40// Fixed-Point Arithmetic (shared)
41// ============================================================================
42
43/// 16.16 fixed-point type
44pub(crate) type Fp16 = i32;
45
46/// 2.30 fixed-point type for high-precision trig
47pub(crate) type Fp30 = i32;
48
49/// 16.16: number of fractional bits
50pub(crate) const FP16_SHIFT: i32 = 16;
51
52/// 16.16: representation of 1.0
53pub(crate) const FP16_ONE: Fp16 = 1 << FP16_SHIFT;
54
55/// 2.30: number of fractional bits
56pub(crate) const FP30_SHIFT: i32 = 30;
57
58/// 2.30: representation of 1.0
59pub(crate) const FP30_ONE: Fp30 = 1 << FP30_SHIFT;
60
61/// Multiply two 2.30 fixed-point values, returning 2.30 result
62#[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/// Multiply two 16.16 fixed-point values with saturation
69#[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/// Convert 2.30 to 16.16 (shift right by 14)
82#[inline]
83pub(crate) fn fp30_to_fp16(v: Fp30) -> Fp16 {
84    v >> (FP30_SHIFT - FP16_SHIFT)
85}
86
87/// Convert i32 integer to 16.16 fixed-point
88#[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/// Clamp a 16.16 fixed-point value to i16 range and return the sample
95#[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// ============================================================================
108// Byte Reading Helpers
109// ============================================================================
110
111#[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// ============================================================================
176// Codec Error Type
177// ============================================================================
178
179/// Error type for codec operations
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum CodecError {
182    /// Input data too short
183    BufferTooShort,
184    /// Invalid magic bytes or sync word
185    InvalidMagic,
186    /// CRC check failed
187    CrcMismatch,
188    /// Unsupported codec version
189    UnsupportedVersion,
190    /// Invalid header field
191    InvalidHeader,
192    /// Unsupported feature (e.g., floor type, residue type)
193    UnsupportedFeature,
194    /// Huffman decode failed
195    HuffmanError,
196    /// Bitstream corruption
197    BitstreamCorrupt,
198    /// Unsupported channel configuration
199    UnsupportedChannels,
200    /// Unsupported sample rate
201    UnsupportedSampleRate,
202    /// Internal buffer overflow
203    InternalOverflow,
204    /// End of stream reached
205    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
227/// Codec result type
228pub type CodecResult<T> = Result<T, CodecError>;
229
230// ============================================================================
231// Shared Lookup Tables and Helpers
232// ============================================================================
233
234/// Pre-computed cosine table for common MDCT sizes (64 entries, covering
235/// 0 to pi/2 in equal steps). Values in 2.30 fixed-point.
236///
237/// cos_table[i] = cos(i * pi / (2 * 64)) in 2.30
238pub(crate) const MDCT_COS_TABLE_64: [Fp30; 64] = {
239    let mut table = [0i32; 64];
240    table[0] = 0x4000_0000; // 1.0 in 2.30
241
242    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
264/// Get cosine value from lookup table with quadrant folding
265///
266/// table_idx is in units of pi/128 (0..512 covers 0..4*pi)
267pub(crate) fn get_cos_from_table(table_idx: usize) -> Fp30 {
268    let idx = table_idx % 256; // Reduce to 0..2*pi (256 = pi)
269    let half = idx % 128; // Reduce to 0..pi
270
271    let base_idx = if half < 64 { half } else { 127 - half };
272    let base_val = MDCT_COS_TABLE_64[base_idx.min(63)];
273
274    // Sign: cos is negative in (pi/2, 3pi/2)
275    if (64..192).contains(&idx) {
276        -base_val
277    } else {
278        base_val
279    }
280}
281
282/// Integer cosine approximation in 2.30 fixed-point
283///
284/// Uses a polynomial approximation: cos(x) ~ 1 - x^2/2 + x^4/24
285/// where x is in 2.30 fixed-point representing radians / pi.
286///
287/// Input `angle_frac` is in units of 1/(4*N) turns, pre-scaled to 2.30.
288pub(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
309/// Integer log2 (number of bits needed to represent value)
310pub(crate) fn ilog(val: u32) -> u8 {
311    if val == 0 {
312        return 0;
313    }
314    32 - val.leading_zeros() as u8
315}
316
317// Re-export all public types from submodules
318pub 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    // --- Fixed-point arithmetic tests ---
329
330    #[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    // --- ilog tests ---
360
361    #[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    // --- Codec error display ---
373
374    #[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    // --- Cosine table tests ---
382
383    #[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}