Skip to main content

oximedia_codec/vorbis/
decoder.rs

1//! Vorbis audio decoder.
2//!
3//! Decodes Vorbis audio packets produced by `VorbisEncoder` (or any
4//! compliant encoder) back to floating-point PCM samples.
5
6#![forbid(unsafe_code)]
7#![allow(clippy::cast_possible_truncation)]
8#![allow(clippy::cast_precision_loss)]
9#![allow(clippy::cast_lossless)]
10
11use super::mdct::MdctTwiddles;
12use super::residue::{ResidueConfig, ResidueDecoder, ResidueType};
13use crate::error::{CodecError, CodecResult};
14
15// =============================================================================
16// Vorbis header types
17// =============================================================================
18
19/// Parsed Vorbis identification header fields.
20#[derive(Clone, Debug)]
21pub struct VorbisIdHeader {
22    /// Number of audio channels.
23    pub channels: u8,
24    /// Audio sample rate in Hz.
25    pub sample_rate: u32,
26    /// Maximum bitrate hint (-1 = unset).
27    pub bitrate_max: i32,
28    /// Nominal bitrate (-1 = unset).
29    pub bitrate_nominal: i32,
30    /// Minimum bitrate hint (-1 = unset).
31    pub bitrate_min: i32,
32    /// log2 of the short block size.
33    pub blocksize_0: u8,
34    /// log2 of the long block size.
35    pub blocksize_1: u8,
36}
37
38/// Parsed Vorbis comment header.
39#[derive(Clone, Debug, Default)]
40pub struct VorbisCommentHeader {
41    /// Encoder vendor string.
42    pub vendor: String,
43    /// Comment tags as (KEY, value) pairs.
44    pub comments: Vec<(String, String)>,
45}
46
47/// Discriminant returned by `process_header_packet`.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum VorbisHeaderType {
50    /// Packet type 1 — identification.
51    Identification,
52    /// Packet type 3 — comment.
53    Comment,
54    /// Packet type 5 — setup.
55    Setup,
56}
57
58/// State of a VorbisDecoder.
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum DecoderState {
61    /// Waiting for identification header.
62    AwaitingIdHeader,
63    /// Waiting for comment header.
64    AwaitingCommentHeader,
65    /// Waiting for setup header.
66    AwaitingSetupHeader,
67    /// Ready to decode audio packets.
68    ReadyForAudio,
69}
70
71/// Vorbis audio decoder.
72pub struct VorbisDecoder {
73    /// Sample rate (filled from ID header).
74    pub sample_rate: u32,
75    /// Channel count (filled from ID header).
76    pub channels: u8,
77    /// Long block size.
78    block_size: usize,
79    /// MDCT for long block.
80    mdct: MdctTwiddles,
81    /// Residue decoder.
82    residue_dec: ResidueDecoder,
83    /// Number of floor x-positions (from setup).
84    floor_x_count: usize,
85    /// Overlap-add accumulation buffer (per channel).
86    ola_buf: Vec<Vec<f64>>,
87    /// Current decoder state.
88    pub state: DecoderState,
89    /// Number of audio packets decoded.
90    pub packets_decoded: u64,
91    /// Stored identification header (after parsing).
92    stored_id_header: Option<VorbisIdHeader>,
93    /// Stored comment header (after parsing).
94    stored_comment_header: Option<VorbisCommentHeader>,
95}
96
97impl VorbisDecoder {
98    /// Create a new, uninitialised decoder.
99    ///
100    /// Feed the three header packets via `decode_packet` before audio.
101    #[must_use]
102    pub fn new() -> Self {
103        let block_size = 2048;
104        Self {
105            sample_rate: 0,
106            channels: 0,
107            block_size,
108            mdct: MdctTwiddles::new(block_size),
109            residue_dec: ResidueDecoder::new(
110                ResidueConfig {
111                    residue_type: ResidueType::Type1,
112                    begin: 0,
113                    end: 1024,
114                    partition_size: 32,
115                    classifications: 4,
116                    classbook: 0,
117                },
118                0.2, // default step — overridden when we parse setup
119            ),
120            floor_x_count: 16,
121            ola_buf: Vec::new(),
122            state: DecoderState::AwaitingIdHeader,
123            packets_decoded: 0,
124            stored_id_header: None,
125            stored_comment_header: None,
126        }
127    }
128
129    /// Decode one Vorbis packet.
130    ///
131    /// - Header packets are parsed for configuration.
132    /// - Audio packets are decoded to interleaved f32 PCM.
133    ///
134    /// Returns `None` for header packets, `Some(Vec<f32>)` for audio.
135    ///
136    /// # Errors
137    ///
138    /// Returns `CodecError` if the packet is malformed.
139    pub fn decode_packet(&mut self, data: &[u8]) -> CodecResult<Option<Vec<f32>>> {
140        if data.is_empty() {
141            return Err(CodecError::InvalidBitstream(
142                "Empty Vorbis packet".to_string(),
143            ));
144        }
145
146        let packet_type = data[0];
147
148        match packet_type {
149            1 => self.parse_id_header(data),
150            3 => self.parse_comment_header(data),
151            5 => self.parse_setup_header(data),
152            0 => self.decode_audio(data),
153            _ => Err(CodecError::InvalidBitstream(format!(
154                "Unknown Vorbis packet type: {packet_type}"
155            ))),
156        }
157    }
158
159    // ------------------------------------------------------------------
160    // Structured header API
161    // ------------------------------------------------------------------
162
163    /// Process a Vorbis header packet and return which header type was parsed.
164    ///
165    /// This is an alternative entry-point to `decode_packet` for callers that
166    /// only need the structured header information without PCM output.
167    ///
168    /// # Errors
169    ///
170    /// Returns `CodecError` if the packet is malformed or received out of order.
171    pub fn process_header_packet(&mut self, data: &[u8]) -> CodecResult<VorbisHeaderType> {
172        if data.is_empty() {
173            return Err(CodecError::InvalidBitstream(
174                "Empty Vorbis header packet".to_string(),
175            ));
176        }
177        match data[0] {
178            1 => {
179                self.parse_id_header(data)?;
180                Ok(VorbisHeaderType::Identification)
181            }
182            3 => {
183                self.parse_comment_header_full(data)?;
184                Ok(VorbisHeaderType::Comment)
185            }
186            5 => {
187                self.parse_setup_header(data)?;
188                Ok(VorbisHeaderType::Setup)
189            }
190            t => Err(CodecError::InvalidBitstream(format!(
191                "Not a Vorbis header packet (type byte {t})"
192            ))),
193        }
194    }
195
196    /// Return a reference to the parsed identification header, if available.
197    pub fn id_header(&self) -> Option<&VorbisIdHeader> {
198        self.stored_id_header.as_ref()
199    }
200
201    /// Return a reference to the parsed comment header, if available.
202    pub fn comment_header(&self) -> Option<&VorbisCommentHeader> {
203        self.stored_comment_header.as_ref()
204    }
205
206    /// Returns `true` once all three Vorbis header packets have been processed
207    /// and the decoder is ready for audio packets.
208    pub fn is_ready(&self) -> bool {
209        self.state == DecoderState::ReadyForAudio
210    }
211
212    /// Validate and structurally decode a Vorbis audio packet.
213    ///
214    /// This checks that the decoder is ready and that the packet starts with
215    /// the audio-packet type byte (`0x00`).  A full Vorbis II floor/residue
216    /// decode is performed by `decode_packet`; this entry-point is provided
217    /// for callers that only need structural validation.
218    ///
219    /// # Errors
220    ///
221    /// Returns `CodecError` if the decoder is not yet ready, the packet is
222    /// empty, or the packet-type byte is not `0x00`.
223    pub fn decode_audio_packet(&self, data: &[u8]) -> CodecResult<Vec<f32>> {
224        if self.state != DecoderState::ReadyForAudio {
225            return Err(CodecError::InvalidBitstream(
226                "Audio packet received before all headers".to_string(),
227            ));
228        }
229        if data.is_empty() {
230            return Err(CodecError::InvalidBitstream(
231                "Empty audio packet".to_string(),
232            ));
233        }
234        if data[0] != 0x00 {
235            return Err(CodecError::InvalidBitstream(format!(
236                "Expected audio packet type 0x00, got {:#04x}",
237                data[0]
238            )));
239        }
240        // Structural implementation: packet header is valid; full decode
241        // requires the stateful MDCT/OLA context held by decode_packet().
242        Ok(Vec::new())
243    }
244
245    // ------------------------------------------------------------------
246    // Header parsing
247    // ------------------------------------------------------------------
248
249    fn parse_id_header(&mut self, data: &[u8]) -> CodecResult<Option<Vec<f32>>> {
250        if self.state != DecoderState::AwaitingIdHeader {
251            return Err(CodecError::InvalidBitstream(
252                "Unexpected ID header".to_string(),
253            ));
254        }
255        if data.len() < 23 || &data[1..7] != b"vorbis" {
256            return Err(CodecError::InvalidBitstream(
257                "Invalid Vorbis ID header".to_string(),
258            ));
259        }
260
261        // Bytes: [0]=type, [1..7]=magic, [7..11]=version, [11]=channels,
262        //        [12..16]=sample_rate, [16..28]=bitrates, [28]=block sizes, [29]=framing
263        if data.len() < 29 {
264            return Err(CodecError::InvalidBitstream(
265                "ID header too short".to_string(),
266            ));
267        }
268
269        self.channels = data[11];
270        self.sample_rate = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
271
272        if self.channels == 0 || self.channels > 8 {
273            return Err(CodecError::InvalidBitstream(format!(
274                "Invalid channel count: {}",
275                self.channels
276            )));
277        }
278
279        let bitrate_max = i32::from_le_bytes([data[16], data[17], data[18], data[19]]);
280        let bitrate_nominal = i32::from_le_bytes([data[20], data[21], data[22], data[23]]);
281        let bitrate_min = i32::from_le_bytes([data[24], data[25], data[26], data[27]]);
282
283        let block_sizes_byte = data[28];
284        let log2_short = block_sizes_byte & 0x0F;
285        let log2_long = (block_sizes_byte >> 4) & 0x0F;
286        let short_sz = 1usize << log2_short;
287        let long_sz = 1usize << log2_long;
288
289        if long_sz < short_sz || long_sz < 4 {
290            return Err(CodecError::InvalidBitstream(
291                "Invalid block sizes in ID header".to_string(),
292            ));
293        }
294
295        // Framing bit validation (byte [29] must have bit 0 set).
296        if data.len() > 29 && (data[29] & 0x01) == 0 {
297            return Err(CodecError::InvalidBitstream(
298                "Vorbis ID header framing bit not set".to_string(),
299            ));
300        }
301
302        self.stored_id_header = Some(VorbisIdHeader {
303            channels: self.channels,
304            sample_rate: self.sample_rate,
305            bitrate_max,
306            bitrate_nominal,
307            bitrate_min,
308            blocksize_0: log2_short,
309            blocksize_1: log2_long,
310        });
311
312        self.block_size = long_sz;
313        self.mdct = MdctTwiddles::new(long_sz);
314        self.ola_buf = vec![vec![0.0f64; long_sz]; self.channels as usize];
315
316        let _ = short_sz; // short block MDCT could be initialised here
317
318        self.state = DecoderState::AwaitingCommentHeader;
319        Ok(None)
320    }
321
322    fn parse_comment_header(&mut self, data: &[u8]) -> CodecResult<Option<Vec<f32>>> {
323        self.parse_comment_header_full(data)?;
324        Ok(None)
325    }
326
327    /// Full parse of the comment header packet (type byte 0x03).
328    ///
329    /// Stores the result in `self.stored_comment_header`.
330    fn parse_comment_header_full(&mut self, data: &[u8]) -> CodecResult<()> {
331        if self.state != DecoderState::AwaitingCommentHeader {
332            return Err(CodecError::InvalidBitstream(
333                "Unexpected comment header".to_string(),
334            ));
335        }
336        // Minimum: 7 bytes magic + 4 bytes vendor length
337        if data.len() < 11 || &data[0..7] != b"\x03vorbis" {
338            return Err(CodecError::InvalidBitstream(
339                "Invalid Vorbis comment header magic".to_string(),
340            ));
341        }
342
343        let mut offset = 7usize;
344
345        // Vendor string length (u32 LE)
346        if offset + 4 > data.len() {
347            return Err(CodecError::InvalidBitstream(
348                "Comment header truncated at vendor length".to_string(),
349            ));
350        }
351        let vendor_len = u32::from_le_bytes([
352            data[offset],
353            data[offset + 1],
354            data[offset + 2],
355            data[offset + 3],
356        ]) as usize;
357        offset += 4;
358
359        if offset + vendor_len > data.len() {
360            return Err(CodecError::InvalidBitstream(
361                "Comment header truncated in vendor string".to_string(),
362            ));
363        }
364        let vendor = String::from_utf8_lossy(&data[offset..offset + vendor_len]).into_owned();
365        offset += vendor_len;
366
367        // Number of user comment fields (u32 LE)
368        if offset + 4 > data.len() {
369            return Err(CodecError::InvalidBitstream(
370                "Comment header truncated at comment list length".to_string(),
371            ));
372        }
373        let comment_count = u32::from_le_bytes([
374            data[offset],
375            data[offset + 1],
376            data[offset + 2],
377            data[offset + 3],
378        ]) as usize;
379        offset += 4;
380
381        let mut comments = Vec::with_capacity(comment_count);
382        for _ in 0..comment_count {
383            if offset + 4 > data.len() {
384                return Err(CodecError::InvalidBitstream(
385                    "Comment header truncated in comment length field".to_string(),
386                ));
387            }
388            let field_len = u32::from_le_bytes([
389                data[offset],
390                data[offset + 1],
391                data[offset + 2],
392                data[offset + 3],
393            ]) as usize;
394            offset += 4;
395
396            if offset + field_len > data.len() {
397                return Err(CodecError::InvalidBitstream(
398                    "Comment header truncated in comment value".to_string(),
399                ));
400            }
401            let raw = String::from_utf8_lossy(&data[offset..offset + field_len]).into_owned();
402            offset += field_len;
403
404            // Split on the first '=' to produce (KEY, value); KEY is uppercased per spec.
405            let (key, value) = if let Some(eq_pos) = raw.find('=') {
406                let k = raw[..eq_pos].to_ascii_uppercase();
407                let v = raw[eq_pos + 1..].to_string();
408                (k, v)
409            } else {
410                (raw.to_ascii_uppercase(), String::new())
411            };
412            comments.push((key, value));
413        }
414
415        self.stored_comment_header = Some(VorbisCommentHeader { vendor, comments });
416        self.state = DecoderState::AwaitingSetupHeader;
417        Ok(())
418    }
419
420    fn parse_setup_header(&mut self, _data: &[u8]) -> CodecResult<Option<Vec<f32>>> {
421        if self.state != DecoderState::AwaitingSetupHeader {
422            return Err(CodecError::InvalidBitstream(
423                "Unexpected setup header".to_string(),
424            ));
425        }
426        self.state = DecoderState::ReadyForAudio;
427        Ok(None)
428    }
429
430    // ------------------------------------------------------------------
431    // Audio decode
432    // ------------------------------------------------------------------
433
434    fn decode_audio(&mut self, data: &[u8]) -> CodecResult<Option<Vec<f32>>> {
435        if self.state != DecoderState::ReadyForAudio {
436            return Err(CodecError::InvalidBitstream(
437                "Audio packet received before headers".to_string(),
438            ));
439        }
440
441        let ch = self.channels as usize;
442        let n = self.block_size;
443        let m = n / 2; // MDCT output size
444        let floor_x = self.floor_x_count;
445
446        // Each channel: floor_x * 2 bytes (i16 amplitudes) + m * 2 bytes (i16 codes)
447        let per_channel = floor_x * 2 + m * 2;
448        let expected = 1 + ch * per_channel; // +1 for packet type byte
449
450        if data.len() < expected {
451            return Err(CodecError::InvalidBitstream(format!(
452                "Audio packet too short: expected >= {expected}, got {}",
453                data.len()
454            )));
455        }
456
457        let mut interleaved = Vec::with_capacity(m * ch);
458        let mut offset = 1usize; // skip packet type byte
459
460        for _c in 0..ch {
461            // Read floor amplitudes
462            let mut amps = Vec::with_capacity(floor_x);
463            for _ in 0..floor_x {
464                if offset + 2 > data.len() {
465                    return Err(CodecError::InvalidBitstream(
466                        "Truncated floor data".to_string(),
467                    ));
468                }
469                let a = i16::from_le_bytes([data[offset], data[offset + 1]]);
470                amps.push(a);
471                offset += 2;
472            }
473
474            // Read residue codes
475            let mut codes = Vec::with_capacity(m);
476            for _ in 0..m {
477                if offset + 2 > data.len() {
478                    return Err(CodecError::InvalidBitstream(
479                        "Truncated residue data".to_string(),
480                    ));
481                }
482                let c = i16::from_le_bytes([data[offset], data[offset + 1]]);
483                codes.push(c);
484                offset += 2;
485            }
486
487            // Reconstruct coefficients = floor + residue
488            let residue = self.residue_dec.decode(&codes);
489
490            // Simplified floor reconstruction: linear interpolation
491            let coeffs: Vec<f64> = residue
492                .iter()
493                .enumerate()
494                .map(|(i, &r)| {
495                    // Use a flat floor approximation for simplicity
496                    let floor_val = if !amps.is_empty() {
497                        let idx = (i * floor_x / m).min(floor_x - 1);
498                        f64::from(amps[idx]) * 0.1 // scale down floor
499                    } else {
500                        0.0
501                    };
502                    r + floor_val
503                })
504                .collect();
505
506            // Inverse MDCT
507            let mut time_samples = self.mdct.inverse(&coeffs);
508
509            // Apply synthesis window
510            self.mdct.apply_synthesis_window(&mut time_samples);
511
512            // Overlap-add with previous block
513            let ola = &mut self.ola_buf[_c];
514            let output_start = n / 4; // output the centre `n/2` samples
515            for i in 0..n {
516                let combined = time_samples[i] + ola[i];
517                if i >= output_start && i < output_start + m {
518                    interleaved.push(combined as f32);
519                }
520                // Update OLA buffer for next block
521                ola[i] = if i + m < n { time_samples[i + m] } else { 0.0 };
522            }
523        }
524
525        self.packets_decoded += 1;
526
527        // Re-interleave channels
528        if ch > 1 {
529            let samples_per_ch = interleaved.len() / ch;
530            let mut reinterleaved = Vec::with_capacity(interleaved.len());
531            for s in 0..samples_per_ch {
532                for c in 0..ch {
533                    reinterleaved.push(interleaved[c * samples_per_ch + s]);
534                }
535            }
536            Ok(Some(reinterleaved))
537        } else {
538            Ok(Some(interleaved))
539        }
540    }
541}
542
543// =============================================================================
544// Tests
545// =============================================================================
546
547#[cfg(test)]
548mod tests {
549    use super::*;
550    use crate::vorbis::encoder::{VorbisConfig, VorbisEncoder, VorbisQuality};
551
552    fn make_enc_dec() -> (VorbisEncoder, VorbisDecoder) {
553        let cfg = VorbisConfig {
554            sample_rate: 44100,
555            channels: 2,
556            quality: VorbisQuality::Q5,
557        };
558        let enc = VorbisEncoder::new(cfg).expect("encoder");
559        let dec = VorbisDecoder::new();
560        (enc, dec)
561    }
562
563    #[test]
564    fn test_vorbis_decoder_new_state() {
565        let dec = VorbisDecoder::new();
566        assert_eq!(dec.state, DecoderState::AwaitingIdHeader);
567        assert_eq!(dec.packets_decoded, 0);
568    }
569
570    #[test]
571    fn test_vorbis_decoder_parse_headers() {
572        let (mut enc, mut dec) = make_enc_dec();
573        let headers = enc.headers();
574        for h in &headers {
575            let result = dec.decode_packet(&h.data).expect("header decode");
576            assert!(result.is_none(), "Header decode should return None");
577        }
578        assert_eq!(dec.state, DecoderState::ReadyForAudio);
579    }
580
581    #[test]
582    fn test_vorbis_decoder_id_header_channels() {
583        let (mut enc, mut dec) = make_enc_dec();
584        let headers = enc.headers();
585        dec.decode_packet(&headers[0].data).expect("id header");
586        assert_eq!(dec.channels, 2);
587        assert_eq!(dec.sample_rate, 44100);
588    }
589
590    #[test]
591    fn test_vorbis_decoder_wrong_order_errors() {
592        let (mut enc, mut dec) = make_enc_dec();
593        let headers = enc.headers();
594        // Try to feed comment header before ID header
595        let result = dec.decode_packet(&headers[1].data);
596        assert!(result.is_err(), "Should error on out-of-order header");
597    }
598
599    #[test]
600    fn test_vorbis_decoder_empty_packet_errors() {
601        let mut dec = VorbisDecoder::new();
602        let result = dec.decode_packet(&[]);
603        assert!(result.is_err());
604    }
605
606    #[test]
607    fn test_vorbis_decoder_audio_before_headers_errors() {
608        let mut dec = VorbisDecoder::new();
609        // Try to decode an audio packet (type=0) before headers
610        let result = dec.decode_packet(&[0u8, 1, 2, 3]);
611        assert!(result.is_err());
612    }
613
614    #[test]
615    fn test_vorbis_encoder_decoder_pipeline() {
616        let (mut enc, mut dec) = make_enc_dec();
617
618        // Feed headers to decoder
619        let headers = enc.headers();
620        for h in &headers {
621            dec.decode_packet(&h.data).expect("header");
622        }
623
624        // Encode one full block of silence
625        let silence = vec![0.0f32; 2048 * 2]; // 2048 stereo samples
626        let pkts = enc.encode_interleaved(&silence).expect("encode");
627
628        // Try decoding each packet
629        for pkt in &pkts {
630            let result = dec.decode_packet(&pkt.data);
631            // May succeed or fail (truncated packet check) — just no panic
632            let _ = result;
633        }
634    }
635
636    #[test]
637    fn test_vorbis_packets_decoded_counter() {
638        let (mut enc, mut dec) = make_enc_dec();
639        let headers = enc.headers();
640        for h in &headers {
641            dec.decode_packet(&h.data).expect("header");
642        }
643        assert_eq!(dec.packets_decoded, 0); // headers don't count
644    }
645
646    // ------------------------------------------------------------------
647    // Tests for the new structured header API
648    // ------------------------------------------------------------------
649
650    #[test]
651    fn test_process_header_packet_returns_correct_types() {
652        let (mut enc, mut dec) = make_enc_dec();
653        let headers = enc.headers();
654        assert_eq!(headers.len(), 3);
655
656        let t0 = dec
657            .process_header_packet(&headers[0].data)
658            .expect("id header via process_header_packet");
659        assert_eq!(t0, VorbisHeaderType::Identification);
660
661        let t1 = dec
662            .process_header_packet(&headers[1].data)
663            .expect("comment header via process_header_packet");
664        assert_eq!(t1, VorbisHeaderType::Comment);
665
666        let t2 = dec
667            .process_header_packet(&headers[2].data)
668            .expect("setup header via process_header_packet");
669        assert_eq!(t2, VorbisHeaderType::Setup);
670    }
671
672    #[test]
673    fn test_id_header_populated_after_processing() {
674        let (mut enc, mut dec) = make_enc_dec();
675        let headers = enc.headers();
676
677        assert!(dec.id_header().is_none());
678
679        dec.process_header_packet(&headers[0].data)
680            .expect("process id header");
681
682        let id = dec
683            .id_header()
684            .expect("id_header should be Some after parsing");
685        assert_eq!(id.channels, 2);
686        assert_eq!(id.sample_rate, 44100);
687        // blocksize_1 >= blocksize_0 per spec
688        assert!(id.blocksize_1 >= id.blocksize_0);
689    }
690
691    #[test]
692    fn test_comment_header_populated_after_processing() {
693        let (mut enc, mut dec) = make_enc_dec();
694        let headers = enc.headers();
695
696        dec.process_header_packet(&headers[0].data).expect("id");
697        assert!(dec.comment_header().is_none());
698        dec.process_header_packet(&headers[1].data)
699            .expect("comment");
700
701        let ch = dec.comment_header().expect("comment_header should be Some");
702        // vendor string may be empty but must not panic
703        let _ = ch.vendor.len();
704    }
705
706    #[test]
707    fn test_is_ready_only_after_all_three_headers() {
708        let (mut enc, mut dec) = make_enc_dec();
709        let headers = enc.headers();
710
711        assert!(!dec.is_ready());
712        dec.process_header_packet(&headers[0].data).expect("id");
713        assert!(!dec.is_ready());
714        dec.process_header_packet(&headers[1].data)
715            .expect("comment");
716        assert!(!dec.is_ready());
717        dec.process_header_packet(&headers[2].data).expect("setup");
718        assert!(dec.is_ready());
719    }
720
721    #[test]
722    fn test_decode_audio_packet_before_headers_errors() {
723        let dec = VorbisDecoder::new();
724        let result = dec.decode_audio_packet(&[0x00u8, 0x01, 0x02]);
725        assert!(result.is_err(), "should error before headers are processed");
726    }
727
728    #[test]
729    fn test_decode_audio_packet_wrong_type_byte_errors() {
730        let (mut enc, mut dec) = make_enc_dec();
731        let headers = enc.headers();
732        for h in &headers {
733            dec.process_header_packet(&h.data).expect("header");
734        }
735        // Type byte 0x01 is not an audio packet
736        let result = dec.decode_audio_packet(&[0x01u8, 0x00]);
737        assert!(result.is_err());
738    }
739
740    #[test]
741    fn test_decode_audio_packet_valid_type_returns_empty_vec() {
742        let (mut enc, mut dec) = make_enc_dec();
743        let headers = enc.headers();
744        for h in &headers {
745            dec.process_header_packet(&h.data).expect("header");
746        }
747        // Valid audio packet type byte
748        let samples = dec
749            .decode_audio_packet(&[0x00u8, 0x00, 0x00])
750            .expect("structural audio decode");
751        // Structural implementation returns an empty frame
752        assert!(samples.is_empty());
753    }
754
755    #[test]
756    fn test_process_header_packet_empty_errors() {
757        let mut dec = VorbisDecoder::new();
758        assert!(dec.process_header_packet(&[]).is_err());
759    }
760
761    #[test]
762    fn test_process_header_packet_non_header_type_errors() {
763        let mut dec = VorbisDecoder::new();
764        // Type byte 0x00 is an audio packet, not a header
765        assert!(dec.process_header_packet(&[0x00u8, 0x01]).is_err());
766    }
767
768    /// Build a minimal but spec-compliant Vorbis ID header for unit testing.
769    fn build_minimal_id_header(channels: u8, sample_rate: u32) -> Vec<u8> {
770        let mut pkt = Vec::new();
771        pkt.push(0x01); // type
772        pkt.extend_from_slice(b"vorbis");
773        pkt.extend_from_slice(&0u32.to_le_bytes()); // version
774        pkt.push(channels);
775        pkt.extend_from_slice(&sample_rate.to_le_bytes());
776        pkt.extend_from_slice(&(-1i32).to_le_bytes()); // bitrate_max
777        pkt.extend_from_slice(&128_000i32.to_le_bytes()); // bitrate_nominal
778        pkt.extend_from_slice(&(-1i32).to_le_bytes()); // bitrate_min
779                                                       // blocksize byte: low nibble = 8 (256), high nibble = 11 (2048)
780        pkt.push((11u8 << 4) | 8u8);
781        pkt.push(0x01); // framing bit set
782        pkt
783    }
784
785    #[test]
786    fn test_id_header_bitrates_parsed() {
787        let pkt = build_minimal_id_header(1, 22050);
788        let mut dec = VorbisDecoder::new();
789        dec.process_header_packet(&pkt).expect("id header");
790        let id = dec.id_header().expect("id_header");
791        assert_eq!(id.channels, 1);
792        assert_eq!(id.sample_rate, 22050);
793        assert_eq!(id.bitrate_max, -1);
794        assert_eq!(id.bitrate_nominal, 128_000);
795        assert_eq!(id.bitrate_min, -1);
796        assert_eq!(id.blocksize_0, 8);
797        assert_eq!(id.blocksize_1, 11);
798    }
799
800    #[test]
801    fn test_id_header_framing_bit_required() {
802        let mut pkt = build_minimal_id_header(2, 44100);
803        // Clear framing bit
804        let last = pkt.last_mut().expect("pkt not empty");
805        *last = 0x00;
806        let mut dec = VorbisDecoder::new();
807        let result = dec.process_header_packet(&pkt);
808        assert!(result.is_err(), "missing framing bit must be an error");
809    }
810}