Skip to main content

audio_codec_bsd/
decoder.rs

1//! The container-decoder public API contract.
2//!
3//! This module defines the types every concrete decoder (FLAC, WAV, PCM)
4//! exchanges with its callers.
5
6use crate::error;
7use audio_core_bsd::AudioFrame;
8
9/// Supported container formats.
10///
11/// Each variant maps to a concrete decoder implementation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum FormatKind {
14    /// Free Lossless Audio Codec — lossless compressed container.
15    Flac,
16    /// RIFF Waveform — PCM or IEEE-float, uncompressed.
17    Wav,
18    /// Raw headerless PCM — little-endian samples with no container.
19    Pcm,
20}
21
22/// Metadata describing a decoded stream, populated at
23/// [`ContainerDecoder::open`] time.
24///
25/// All fields are known once the container header has been parsed; they do
26/// not change across subsequent [`ContainerDecoder::next_frame`] calls.
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[must_use]
29pub struct StreamInfo {
30    /// The sniffed container format (FLAC / WAV / PCM).
31    pub format: FormatKind,
32    /// Sample rate in Hz (`48000` is the common baseline).
33    pub sample_rate: u32,
34    /// Number of channels (`1` = mono, `2` = stereo, …).
35    pub channels: u16,
36    /// Bit depth of the *encoded* samples (e.g. `16`, `24`).
37    pub bits_per_sample: u32,
38    /// Total per-channel frame count, when known from the container header.
39    ///
40    /// `None` indicates an unbounded or streaming source (e.g. raw PCM with no
41    /// length prefix). Callers must not assume a finite length in that case.
42    pub total_frames: Option<u64>,
43}
44
45/// Multi-format container decoder.
46///
47/// Runs on a **worker thread** (not the RT audio thread): file I/O and heap
48/// allocation are expected and permitted here. Decoded frames should be handed
49/// to a lock-free ring buffer so the RT thread never blocks on this trait.
50///
51/// ## Design notes
52///
53/// - [`ContainerDecoder::open`] returns a populated [`StreamInfo`] rather than
54///   a bare stream handle, so callers can validate format/channel/rate before
55///   the first decode.
56/// - [`ContainerDecoder::next_frame`] returns `Option<AudioFrame>`:
57///   `Ok(None)` signals normal end-of-stream; `Err(CodecError::Eof)` is
58///   reserved for callers that treat EOF as a hard error.
59/// - Samples are stored in **planar** layout (`[ch0…, ch1…]`) per
60///   [`AudioFrame`]; symphonia's interleaved output is de-interleaved by the
61///   concrete decoder before construction.
62///
63pub trait ContainerDecoder: Send {
64    /// Open the container at `path`, sniff/probe the format, and return the
65    /// stream metadata.
66    ///
67    /// # Errors
68    ///
69    /// - [`CodecError::Io`] if the file cannot be opened or read.
70    /// - [`CodecError::Format`] if the container format is unrecognised.
71    ///
72    /// [`CodecError::Io`]: crate::CodecError
73    /// [`CodecError::Format`]: crate::CodecError
74    fn open(&mut self, path: &std::path::Path) -> error::Result<StreamInfo>;
75
76    /// Decode the next chunk as a planar [`AudioFrame`].
77    ///
78    /// Returns `Ok(None)` at end-of-stream (never panics on EOF). Samples are
79    /// stored in **planar** layout (`[ch0…, ch1…]`) per [`AudioFrame`].
80    ///
81    /// # Errors
82    ///
83    /// - [`CodecError::Decode`] if the stream is corrupt or a packet is
84    ///   malformed.
85    /// - [`CodecError::Io`] if an underlying read fails mid-stream.
86    ///
87    /// [`CodecError::Decode`]: crate::CodecError
88    /// [`CodecError::Io`]: crate::CodecError
89    fn next_frame(&mut self) -> error::Result<Option<AudioFrame>>;
90}
91
92#[cfg(test)]
93mod tests {
94    use super::{FormatKind, StreamInfo};
95
96    #[test]
97    fn format_kind_variants_are_distinct() {
98        assert_ne!(FormatKind::Flac, FormatKind::Wav);
99        assert_ne!(FormatKind::Wav, FormatKind::Pcm);
100        assert_ne!(FormatKind::Flac, FormatKind::Pcm);
101    }
102
103    #[test]
104    fn format_kind_copy_and_clone_round_trip() {
105        let a = FormatKind::Flac;
106        let b = a;
107        assert_eq!(a, b);
108        assert_eq!(a.clone(), b);
109    }
110
111    #[test]
112    fn format_kind_hash_is_consistent() {
113        use std::collections::hash_map::DefaultHasher;
114        use std::hash::{Hash, Hasher};
115
116        fn hash_of<T: Hash>(v: &T) -> u64 {
117            let mut h = DefaultHasher::new();
118            v.hash(&mut h);
119            h.finish()
120        }
121        // Equal values hash equally.
122        assert_eq!(hash_of(&FormatKind::Wav), hash_of(&FormatKind::Wav));
123        // The three variants are distinct (not a collision guarantee, but
124        // catches accidental aliasing).
125        assert_ne!(hash_of(&FormatKind::Flac), hash_of(&FormatKind::Wav));
126    }
127
128    #[test]
129    fn stream_info_round_trips_all_fields() {
130        let info = StreamInfo {
131            format: FormatKind::Flac,
132            sample_rate: 96_000,
133            channels: 2,
134            bits_per_sample: 24,
135            total_frames: Some(4_800_000),
136        };
137        assert_eq!(info.format, FormatKind::Flac);
138        assert_eq!(info.sample_rate, 96_000);
139        assert_eq!(info.channels, 2);
140        assert_eq!(info.bits_per_sample, 24);
141        assert_eq!(info.total_frames, Some(4_800_000));
142    }
143
144    #[test]
145    fn stream_info_with_unknown_total_frames() {
146        let info = StreamInfo {
147            format: FormatKind::Pcm,
148            sample_rate: 48_000,
149            channels: 1,
150            bits_per_sample: 16,
151            total_frames: None,
152        };
153        assert!(info.total_frames.is_none());
154    }
155
156    #[test]
157    fn stream_info_clone_and_eq_hold() {
158        let a = StreamInfo {
159            format: FormatKind::Wav,
160            sample_rate: 44_100,
161            channels: 2,
162            bits_per_sample: 16,
163            total_frames: Some(100),
164        };
165        assert_eq!(a.clone(), a);
166        // A differing field breaks equality.
167        let mut b = a.clone();
168        b.bits_per_sample = 24;
169        assert_ne!(a, b);
170    }
171}