#![cfg(feature = "import")]
use std::path::{Path, PathBuf};
use atome::import::{self, Encoding};
fn test_data(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("test_data")
.join(name)
}
fn peak(samples: &[f32]) -> f32 {
samples
.iter()
.fold(0f32, |peak, sample| peak.max(sample.abs()))
}
fn signal_region(channel: &[f32], level: f32) -> &[f32] {
let floor = 0.05 * level;
let first = channel.iter().position(|s| s.abs() > floor).unwrap_or(0);
let last = channel
.iter()
.rposition(|s| s.abs() > floor)
.unwrap_or(channel.len().saturating_sub(1));
&channel[first..=last.max(first)]
}
fn dominant_frequency(samples: &[f32], channels: u16, rate: u32) -> f32 {
let channel: Vec<f32> = samples.iter().step_by(channels as usize).copied().collect();
let level = peak(&channel);
let signal = signal_region(&channel, level);
let (low, high) = (-0.3 * level, 0.3 * level);
let mut cycles = 0usize;
let mut armed = false;
for &sample in signal {
if sample < low {
armed = true;
} else if armed && sample > high {
cycles += 1;
armed = false;
}
}
cycles as f32 * rate as f32 / signal.len() as f32
}
fn signal_seconds(samples: &[f32], channels: u16, rate: u32) -> f32 {
let channel: Vec<f32> = samples.iter().step_by(channels as usize).copied().collect();
let level = peak(&channel);
signal_region(&channel, level).len() as f32 / rate as f32
}
fn check_decoded(
name: &str,
expected: Encoding,
rate: u32,
tone: f32,
seconds: f32,
tolerance: f32,
) -> bool {
let path = test_data(name);
if !path.exists() {
eprintln!("skipping {name}: fixture not present");
return false;
}
let encoding = import::find_type(&path).expect("identify");
assert_eq!(encoding, expected, "{name} identified wrongly");
let decoded = import::decode(&path).unwrap_or_else(|error| panic!("{name}: {error}"));
let samples = decoded.samples.to_vec::<f32>();
assert_eq!(decoded.channels, 2, "{name} channel count");
assert_eq!(decoded.sample_rate, rate, "{name} sample rate");
let amplitude = peak(&samples);
assert!(
(0.01..=1.01).contains(&litude),
"{name} peak amplitude {amplitude} is silence or clipping"
);
let duration = signal_seconds(&samples, decoded.channels, decoded.sample_rate);
assert!(
(duration - seconds).abs() < seconds * 0.05,
"{name} holds {duration}s of signal, expected about {seconds}s"
);
let frequency = dominant_frequency(&samples, decoded.channels, decoded.sample_rate);
assert!(
(frequency - tone).abs() < tolerance,
"{name} decoded a {frequency} Hz tone, expected {tone}"
);
true
}
fn check_streamed(name: &str) {
let path = test_data(name);
if !path.exists() {
return;
}
let whole = import::decode(&path).expect("decode");
let whole_samples = whole.samples.to_vec::<f32>();
let mut stream = import::stream::<f32>(&path).expect("stream");
assert_eq!(stream.sample_rate(), whole.sample_rate, "{name} stream rate");
assert_eq!(stream.channels(), whole.channels, "{name} stream channels");
let mut block = vec![0f32; 101];
let mut streamed = Vec::new();
loop {
let read = stream.read(&mut block).expect("stream read");
if read == 0 {
break;
}
streamed.extend_from_slice(&block[..read]);
}
assert_eq!(
streamed, whole_samples,
"{name} streamed different audio to decoding in one shot"
);
}
#[test]
fn real_world_mp3() {
assert!(
check_decoded("test415hz.mp3", Encoding::Mp3, 44_100, 415.0, 5.0, 3.0),
"tests/test_data/test415hz.mp3 is missing"
);
check_streamed("test415hz.mp3");
}
#[test]
fn wav_pcm() {
if check_decoded("tone.wav", Encoding::Pcm, 48_000, 440.0, 1.0, 1.0) {
check_streamed("tone.wav");
}
}
#[test]
fn aiff_pcm() {
if check_decoded("tone.aiff", Encoding::Pcm, 48_000, 440.0, 1.0, 1.0) {
check_streamed("tone.aiff");
}
}
#[test]
fn caf_pcm() {
if check_decoded("tone.caf", Encoding::Pcm, 48_000, 440.0, 1.0, 1.0) {
check_streamed("tone.caf");
}
}
#[test]
fn flac() {
if check_decoded("tone.flac", Encoding::Flac, 48_000, 440.0, 1.0, 1.0) {
check_streamed("tone.flac");
}
}
#[test]
fn mp3() {
if check_decoded("tone.mp3", Encoding::Mp3, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone.mp3");
}
}
#[test]
fn vorbis_in_ogg() {
if check_decoded("tone.ogg", Encoding::Vorbis, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone.ogg");
}
}
#[test]
fn aac_in_mp4() {
if check_decoded("tone_aac.m4a", Encoding::AacLc, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone_aac.m4a");
}
}
#[test]
fn aac_in_adts() {
if check_decoded("tone.aac", Encoding::AacLc, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone.aac");
}
}
#[test]
fn alac_in_mp4() {
if check_decoded("tone_alac.m4a", Encoding::Alac, 48_000, 440.0, 1.0, 1.0) {
check_streamed("tone_alac.m4a");
}
}
#[test]
#[cfg(feature = "import-opus")]
fn opus_in_ogg() {
if check_decoded("tone.opus", Encoding::Opus, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone.opus");
}
}
#[test]
#[cfg(feature = "import-opus")]
fn opus_in_matroska() {
if check_decoded("tone_opus.mkv", Encoding::Opus, 48_000, 440.0, 1.0, 5.0) {
check_streamed("tone_opus.mkv");
}
}
#[test]
fn formats_without_a_decoder_fail_clearly() {
for encoding in [
Encoding::Ac3,
Encoding::EAc3,
Encoding::Dts,
Encoding::DtsHdMa,
Encoding::TrueHd,
Encoding::Wma,
Encoding::AmrNb,
Encoding::AmrWb,
] {
let error = import::stream_as::<f32>(&test_data("test415hz.mp3"), encoding)
.err()
.expect("must fail");
let message = error.message().unwrap_or_default();
assert!(message.contains("no Rust decoder"), "{encoding:?}: {message}");
assert!(!message.contains("feature"), "{encoding:?}: {message}");
}
}
#[test]
fn a_reader_streams_what_the_file_does() {
let path = test_data("test415hz.mp3");
assert!(path.exists(), "tests/test_data/test415hz.mp3 is missing");
let read_all = |stream: &mut dyn import::AudioStream<f32>| {
let mut block = vec![0f32; 4096];
let mut samples = Vec::new();
loop {
let read = stream.read(&mut block).expect("stream read");
if read == 0 {
break samples;
}
samples.extend_from_slice(&block[..read]);
}
};
let mut from_file = import::stream::<f32>(&path).expect("stream");
let bytes = std::fs::read(&path).expect("read the fixture");
let mut from_reader = import::stream_reader::<f32>(Box::new(std::io::Cursor::new(bytes)), "test415hz.mp3").expect("stream_reader");
assert_eq!(from_reader.sample_rate(), from_file.sample_rate());
assert_eq!(from_reader.channels(), from_file.channels());
assert_eq!(read_all(from_reader.as_mut()), read_all(from_file.as_mut()));
}