Skip to main content

mittens_engine/engine/ecs/system/
audio_decode.rs

1//! PCM decode via symphonia.
2//!
3//! Decode-only — sample-rate conversion and channel remix live in
4//! [`super::audio_sample_format_convert`]; loudness normalization is a
5//! separate gain-policy stage on the audio render thread.
6//! See docs/spec/audio-sources.md and docs/task/audio-decode-convert-normalize-split.md.
7
8use std::fs::File;
9use std::path::Path;
10
11use symphonia::core::audio::{AudioBufferRef, Signal};
12use symphonia::core::codecs::DecoderOptions;
13use symphonia::core::errors::Error as SymphoniaError;
14use symphonia::core::formats::FormatOptions;
15use symphonia::core::io::MediaSourceStream;
16use symphonia::core::meta::MetadataOptions;
17use symphonia::core::probe::Hint;
18use symphonia::core::sample::Sample;
19
20/// Decoded PCM in the source asset's native format. Interleaved `f32`
21/// regardless of the encoded sample type — sample-format conversion is
22/// the next stage's job. Multi-channel data is interleaved frame-major.
23#[derive(Debug, Clone)]
24pub struct DecodedAudio {
25    pub samples: Vec<f32>,
26    pub channels: u16,
27    pub sample_rate: u32,
28}
29
30#[derive(Debug, Clone)]
31pub enum DecodeError {
32    OpenFailed(String),
33    ProbeFailed(String),
34    NoTrack,
35    DecoderInitFailed(String),
36    DecodeFailed(String),
37    Unsupported(String),
38}
39
40impl std::fmt::Display for DecodeError {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            DecodeError::OpenFailed(s) => write!(f, "open failed: {s}"),
44            DecodeError::ProbeFailed(s) => write!(f, "probe failed: {s}"),
45            DecodeError::NoTrack => write!(f, "no default track"),
46            DecodeError::DecoderInitFailed(s) => write!(f, "decoder init failed: {s}"),
47            DecodeError::DecodeFailed(s) => write!(f, "decode failed: {s}"),
48            DecodeError::Unsupported(s) => write!(f, "unsupported: {s}"),
49        }
50    }
51}
52
53impl std::error::Error for DecodeError {}
54
55/// Decode a complete audio file into interleaved `f32` PCM.
56pub fn decode_audio_file(path: impl AsRef<Path>) -> Result<DecodedAudio, DecodeError> {
57    let path = path.as_ref();
58    let file = File::open(path).map_err(|e| DecodeError::OpenFailed(e.to_string()))?;
59    let mss = MediaSourceStream::new(Box::new(file), Default::default());
60
61    let mut hint = Hint::new();
62    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
63        hint.with_extension(ext);
64    }
65
66    let probed = symphonia::default::get_probe()
67        .format(
68            &hint,
69            mss,
70            &FormatOptions::default(),
71            &MetadataOptions::default(),
72        )
73        .map_err(|e| DecodeError::ProbeFailed(e.to_string()))?;
74
75    let mut format = probed.format;
76    let track = format.default_track().ok_or(DecodeError::NoTrack)?;
77    let track_id = track.id;
78    let codec_params = track.codec_params.clone();
79    let sample_rate = codec_params
80        .sample_rate
81        .ok_or_else(|| DecodeError::Unsupported("missing sample_rate".into()))?;
82    let channels = codec_params
83        .channels
84        .map(|c| c.count() as u16)
85        .ok_or_else(|| DecodeError::Unsupported("missing channel layout".into()))?;
86
87    let mut decoder = symphonia::default::get_codecs()
88        .make(&codec_params, &DecoderOptions::default())
89        .map_err(|e| DecodeError::DecoderInitFailed(e.to_string()))?;
90
91    let mut samples: Vec<f32> = Vec::new();
92
93    loop {
94        let packet = match format.next_packet() {
95            Ok(p) => p,
96            Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
97                break;
98            }
99            Err(SymphoniaError::ResetRequired) => {
100                // The decoder requested reset; restart and continue from current point.
101                decoder.reset();
102                continue;
103            }
104            Err(e) => return Err(DecodeError::DecodeFailed(e.to_string())),
105        };
106        if packet.track_id() != track_id {
107            continue;
108        }
109
110        let decoded = match decoder.decode(&packet) {
111            Ok(d) => d,
112            Err(SymphoniaError::DecodeError(_)) => {
113                // Skip undecodable packet; keep going.
114                continue;
115            }
116            Err(e) => return Err(DecodeError::DecodeFailed(e.to_string())),
117        };
118
119        interleave_into(&decoded, &mut samples);
120    }
121
122    Ok(DecodedAudio {
123        samples,
124        channels,
125        sample_rate,
126    })
127}
128
129/// Push interleaved f32 frames from a typed AudioBufferRef into `out`.
130fn interleave_into(buf: &AudioBufferRef<'_>, out: &mut Vec<f32>) {
131    match buf {
132        AudioBufferRef::F32(b) => interleave_planar::<f32>(b, out),
133        AudioBufferRef::F64(b) => interleave_planar::<f64>(b, out),
134        AudioBufferRef::S8(b) => interleave_planar::<i8>(b, out),
135        AudioBufferRef::S16(b) => interleave_planar::<i16>(b, out),
136        AudioBufferRef::S24(b) => interleave_planar::<symphonia::core::sample::i24>(b, out),
137        AudioBufferRef::S32(b) => interleave_planar::<i32>(b, out),
138        AudioBufferRef::U8(b) => interleave_planar::<u8>(b, out),
139        AudioBufferRef::U16(b) => interleave_planar::<u16>(b, out),
140        AudioBufferRef::U24(b) => interleave_planar::<symphonia::core::sample::u24>(b, out),
141        AudioBufferRef::U32(b) => interleave_planar::<u32>(b, out),
142    }
143}
144
145fn interleave_planar<S>(buf: &symphonia::core::audio::AudioBuffer<S>, out: &mut Vec<f32>)
146where
147    S: Sample + symphonia::core::conv::IntoSample<f32>,
148{
149    let channels = buf.spec().channels.count();
150    let frames = buf.frames();
151    out.reserve(frames * channels);
152    for f in 0..frames {
153        for c in 0..channels {
154            let s: S = buf.chan(c)[f];
155            out.push(s.into_sample());
156        }
157    }
158}