use std::fs::File;
use std::path::Path;
use anyhow::{Context, Result, anyhow};
use rubato::audioadapter_buffers::direct::InterleavedSlice;
use rubato::{Fft, FixedSync, Resampler};
use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::errors::Error;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
const RATE: usize = 16000;
pub fn read(path: &str) -> Result<Vec<f32>> {
let (pcm, rate) = decode(path).map_err(|e| match e.downcast_ref() {
Some(Error::Unsupported(_)) => anyhow!("{path}: not a wav, mp3, m4a, flac or ogg file"),
_ => e.context(format!("reading {path}")),
})?;
if rate == RATE {
return Ok(pcm);
}
let mut resampler = Fft::<f32>::new(rate, RATE, 1024, 1, FixedSync::Input)?;
let input = InterleavedSlice::new(&pcm, 1, pcm.len())?;
Ok(resampler.process_all(&input, pcm.len(), None)?.take_data())
}
fn decode(path: &str) -> Result<(Vec<f32>, usize)> {
let mut hint = Hint::new();
if let Some(ext) = Path::new(path).extension().and_then(|e| e.to_str()) {
hint.with_extension(ext);
}
let source = MediaSourceStream::new(Box::new(File::open(path)?), Default::default());
let options = (FormatOptions::default(), MetadataOptions::default());
let mut format = symphonia::default::get_probe().probe(&hint, source, options.0, options.1)?;
let track = format.default_track(TrackType::Audio).context("no audio track")?;
let params = track.codec_params.as_ref().and_then(|p| p.audio()).context("not audio")?.clone();
let (id, rate) = (track.id, params.sample_rate.context("unknown sample rate")? as usize);
let codecs = symphonia::default::get_codecs();
let mut decoder = codecs.make_audio_decoder(¶ms, &AudioDecoderOptions::default())?;
let (mut pcm, mut frame) = (Vec::new(), Vec::new());
while let Some(packet) = format.next_packet()? {
if packet.track_id != id {
continue;
}
let audio = match decoder.decode(&packet) {
Ok(audio) => audio,
Err(Error::DecodeError(_)) => continue, Err(e) => return Err(e.into()),
};
let channels = audio.spec().channels().count();
audio.copy_to_vec_interleaved(&mut frame);
match channels {
1 => pcm.extend_from_slice(&frame),
n => pcm.extend(frame.chunks(n).map(|f| f.iter().sum::<f32>() / n as f32)),
}
}
Ok((pcm, rate))
}
#[cfg(test)]
mod tests {
use std::f32::consts::TAU;
use super::*;
fn decode_wav<S: hound::Sample + Copy>(
name: &str,
spec: hound::WavSpec,
samples: &[S],
) -> Vec<f32> {
let path = std::env::temp_dir().join(format!("wavo-{}-{name}.wav", std::process::id()));
let mut wav = hound::WavWriter::create(&path, spec).unwrap();
samples.iter().for_each(|&s| wav.write_sample(s).unwrap());
wav.finalize().unwrap();
let pcm = read(path.to_str().unwrap()).unwrap();
std::fs::remove_file(&path).unwrap();
pcm
}
fn spec(channels: u16, sample_rate: u32, format: hound::SampleFormat) -> hound::WavSpec {
let bits_per_sample = if format == hound::SampleFormat::Int { 16 } else { 32 };
hound::WavSpec { channels, sample_rate, bits_per_sample, sample_format: format }
}
#[test]
fn pcm16_16k_mono_is_bit_exact() {
let mut x = 1u32;
let mut samples = vec![i16::MIN, i16::MIN + 1, -1, 0, 1, i16::MAX - 1, i16::MAX];
samples.extend((0..48000).map(|_| {
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
(x >> 16) as i16
}));
let pcm = decode_wav("pcm16", spec(1, 16000, hound::SampleFormat::Int), &samples);
let expected: Vec<u32> = samples.iter().map(|&s| (s as f32 / 32768.0).to_bits()).collect();
assert!(pcm.iter().map(|s| s.to_bits()).eq(expected), "samples differ from hound's");
}
#[test]
fn stereo_44k_is_averaged_and_resampled() {
let tone = |t: f32| (TAU * 440.0 * t).sin();
let samples: Vec<f32> =
(0..44100).map(|i| tone(i as f32 / 44100.0)).flat_map(|s| [s, 0.5 * s]).collect();
let pcm = decode_wav("stereo", spec(2, 44100, hound::SampleFormat::Float), &samples);
assert!(pcm.len().abs_diff(16000) <= 1, "{} samples", pcm.len());
for (i, s) in pcm.iter().enumerate().take(15000).skip(1000) {
let expected = 0.75 * tone(i as f32 / 16000.0);
assert!((s - expected).abs() < 0.03, "sample {i}: {s} vs {expected}");
}
let peak = pcm[1000..15000].iter().fold(0f32, |m, s| m.max(s.abs()));
assert!((peak - 0.75).abs() < 0.005, "peak {peak}");
}
}