use anyhow::{anyhow, Result};
use std::path::Path;
use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::errors::Error as SymphoniaError;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
pub fn decode_with(path: &Path, mut sink: impl FnMut(&[f32], u32, usize)) -> Result<()> {
let file = std::fs::File::open(path)?;
let stream = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
hint.with_extension(ext);
}
let mut format = symphonia::default::get_probe().probe(
&hint,
stream,
FormatOptions::default(),
MetadataOptions::default(),
)?;
let track = format
.default_track(TrackType::Audio)
.ok_or_else(|| anyhow!("no audio track"))?;
let track_id = track.id;
let params = track
.codec_params
.as_ref()
.and_then(|p| p.audio())
.ok_or_else(|| anyhow!("no audio codec parameters"))?
.clone();
let mut decoder = symphonia::default::get_codecs()
.make_audio_decoder(¶ms, &AudioDecoderOptions::default())?;
let mut spec: Option<(u32, usize)> = None;
let mut buf: Vec<f32> = Vec::new();
while let Some(packet) = format.next_packet()? {
if packet.track_id != track_id {
continue;
}
let decoded = match decoder.decode(&packet) {
Ok(d) => d,
Err(SymphoniaError::DecodeError(_)) => continue,
Err(e) => return Err(e.into()),
};
let current = (decoded.spec().rate(), decoded.spec().channels().count());
match spec {
Some(s) if s != current => return Err(anyhow!("stream parameters changed mid-file")),
Some(_) => {}
None => spec = Some(current),
}
buf.clear();
decoded.copy_to_vec_interleaved(&mut buf);
sink(&buf, current.0, current.1);
}
spec.map(|_| ()).ok_or_else(|| anyhow!("no audio decoded"))
}
#[cfg(test)]
mod tests {
use super::*;
fn write_wav(path: &Path, rate: u32, channels: u16, frames: usize) {
let data_len = (frames * channels as usize * 2) as u32;
let mut out = Vec::with_capacity(44 + data_len as usize);
out.extend_from_slice(b"RIFF");
out.extend_from_slice(&(36 + data_len).to_le_bytes());
out.extend_from_slice(b"WAVEfmt ");
out.extend_from_slice(&16u32.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&channels.to_le_bytes());
out.extend_from_slice(&rate.to_le_bytes());
out.extend_from_slice(&(rate * channels as u32 * 2).to_le_bytes());
out.extend_from_slice(&(channels * 2).to_le_bytes());
out.extend_from_slice(&16u16.to_le_bytes());
out.extend_from_slice(b"data");
out.extend_from_slice(&data_len.to_le_bytes());
for i in 0..frames {
let v = ((i as f64 * 0.05).sin() * 16000.0) as i16;
for _ in 0..channels {
out.extend_from_slice(&v.to_le_bytes());
}
}
std::fs::write(path, out).unwrap();
}
#[test]
fn decodes_a_generated_wav() {
let path = std::env::temp_dir().join(format!("baken-decode-{}.wav", std::process::id()));
write_wav(&path, 44100, 2, 44100);
let mut samples = Vec::new();
let mut spec = (0, 0);
decode_with(&path, |chunk, rate, channels| {
spec = (rate, channels);
samples.extend_from_slice(chunk);
})
.unwrap();
let _ = std::fs::remove_file(&path);
assert_eq!(spec, (44100, 2));
assert_eq!(samples.len(), 88200);
assert!((samples[2 * 10] - (0.5f64.sin() * 16000.0) as i16 as f32 / 32768.0).abs() < 1e-6);
}
}