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}