use super::*;
#[cfg(feature = "file-decode")]
fn eager_opus_reference(bytes: &[u8]) -> anyhow::Result<Vec<f32>> {
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
let source = BytesMediaSource::new(bytes::Bytes::copy_from_slice(bytes));
let mss = MediaSourceStream::new(Box::new(source), Default::default());
let mut format = symphonia::default::get_probe().probe(
&Hint::new(),
mss,
FormatOptions::default(),
MetadataOptions::default(),
)?;
let (track_id, sample_rate, channels) = {
let track = format
.default_track(TrackType::Audio)
.ok_or_else(|| anyhow::anyhow!("no audio track"))?;
let p = track
.codec_params
.as_ref()
.and_then(|p| p.audio())
.ok_or_else(|| anyhow::anyhow!("no audio params"))?;
(
track.id,
p.sample_rate.ok_or_else(|| anyhow::anyhow!("no rate"))?,
p.channels.as_ref().map(|c| c.count()).unwrap_or(1),
)
};
let mono = mix_channels_to_mono(&crate::inference::audio::opus::decode_opus_channels(
&mut *format,
track_id,
channels,
usize::MAX,
f64::INFINITY,
)?);
let mut resampler =
crate::inference::audio::resample::ResampleTo16k::new(SampleRate(sample_rate), None);
for piece in mono.chunks(crate::inference::audio::resample::RESAMPLE_STAGING_FRAMES) {
resampler.stage().extend_from_slice(piece);
resampler.flush_full()?;
}
let mut out = Vec::new();
resampler.finish_into(&mut out)?;
Ok(out)
}
#[test]
fn test_opus_streaming_decode_matches_whole_buffer() {
for (name, bytes) in [
(
"opus_tone.ogg",
&include_bytes!("../../../../tests/fixtures/opus/opus_tone.ogg")[..],
),
(
"opus_tone_no_eos.ogg",
&include_bytes!("../../../../tests/fixtures/opus/opus_tone_no_eos.ogg")[..],
),
(
"opus_tone_60ms.ogg",
&include_bytes!("../../../../tests/fixtures/opus/opus_tone_60ms.ogg")[..],
),
] {
let streamed = decode_audio_bytes(bytes).expect("streaming decode");
let eager = eager_opus_reference(bytes).expect("whole-buffer decode");
assert!(!streamed.is_empty(), "{name} decoded to nothing");
assert_eq!(streamed, eager, "{name}: streaming decode diverged");
}
}
#[test]
fn test_opus_streaming_windows_match_slice_over_flat_decode() {
let bytes = bytes::Bytes::from_static(include_bytes!(
"../../../../tests/fixtures/opus/opus_tone.ogg"
));
let spec = WindowSpec::new(16_000, 16_000, 3_200);
let flat = FileWindows::from_bytes(bytes.clone(), WindowSpec::flat(), None)
.expect("open flat")
.drain_to_vec()
.expect("drain");
let mut src = FileWindows::from_bytes(bytes, spec, None).expect("open windows");
let mut got = Vec::new();
while let Some(w) = src.next_window().expect("window") {
got.push((w.start_sample, w.samples.to_vec()));
}
let mut want = Vec::new();
let mut sw = SliceWindows::new(&flat, spec);
while let Some(w) = sw.next_window().expect("slice window") {
want.push((w.start_sample, w.samples.to_vec()));
}
assert!(
got.len() > 1,
"expected the windowed regime, got {}",
got.len()
);
assert_eq!(got, want);
assert_eq!(src.total_16k_samples(), flat.len());
}
#[test]
fn test_push_mono_mix_matches_mix_channels_to_mono() {
for channels in [1usize, 2] {
let frames = 97;
let pcm: Vec<f32> = (0..frames * channels)
.map(|i| ((i as f32) * 0.37).sin() * 0.8 - 0.13)
.collect();
let mut got = Vec::new();
crate::inference::audio::opus::push_mono_mix(&pcm, channels, frames, &mut got);
let per_channel: Vec<Vec<f32>> = (0..channels)
.map(|c| (0..frames).map(|f| pcm[f * channels + c]).collect())
.collect();
assert_eq!(
got,
mix_channels_to_mono(&per_channel),
"channels={channels}"
);
}
}
#[test]
fn test_is_recoverable_packet_eof_matches_unexpected_eof_only() {
use std::io::{Error as IoError, ErrorKind};
use symphonia::core::errors::Error as SymError;
let eof = SymError::IoError(IoError::new(
ErrorKind::UnexpectedEof,
"unexpected end of file",
));
assert!(is_recoverable_packet_eof(&eof));
let other_io = SymError::IoError(IoError::other("disk full"));
assert!(!is_recoverable_packet_eof(&other_io));
let decode = SymError::DecodeError("bad page");
assert!(!is_recoverable_packet_eof(&decode));
let unsupported = SymError::Unsupported("codec");
assert!(!is_recoverable_packet_eof(&unsupported));
}
#[test]
fn test_decode_audio_bytes_opus_ogg_missing_eos_succeeds() {
let no_eos = include_bytes!("../../../../tests/fixtures/opus/opus_tone_no_eos.ogg");
let with_eos = include_bytes!("../../../../tests/fixtures/opus/opus_tone.ogg");
let decoded_no_eos = decode_audio_bytes(no_eos).expect("OGG/Opus without EOS must decode");
let decoded_with_eos =
decode_audio_bytes(with_eos).expect("OGG/Opus with EOS must still decode");
assert!(
!decoded_no_eos.is_empty(),
"missing-EOS stream must yield non-empty PCM"
);
let delta = (decoded_no_eos.len() as i64 - decoded_with_eos.len() as i64).unsigned_abs();
assert!(
delta <= 2,
"no-EOS length {} diverged from with-EOS length {}",
decoded_no_eos.len(),
decoded_with_eos.len()
);
let start = decoded_no_eos.len().min(decoded_with_eos.len()) / 4;
let end = start + 1000;
for (a, b) in decoded_no_eos[start..end]
.iter()
.zip(decoded_with_eos[start..end].iter())
{
assert!((a - b).abs() < f32::EPSILON);
}
}
#[test]
fn test_decode_audio_file_opus_missing_eos_matches_bytes() {
let no_eos = include_bytes!("../../../../tests/fixtures/opus/opus_tone_no_eos.ogg");
let mut tmp = tempfile::NamedTempFile::with_suffix(".ogg").expect("temp file");
std::io::Write::write_all(&mut tmp, no_eos).expect("write temp file");
let via_file = decode_audio_file(tmp.path().to_str().expect("utf-8 path"))
.expect("missing-EOS OGG/Opus file must decode");
let via_bytes = decode_audio_bytes(no_eos).expect("missing-EOS bytes must decode");
assert_eq!(via_file.len(), via_bytes.len());
for (a, b) in via_file.iter().zip(via_bytes.iter()) {
assert!((a - b).abs() < f32::EPSILON);
}
}
#[test]
fn test_decode_audio_bytes_truncated_opus_headers_only_still_errors() {
let full = include_bytes!("../../../../tests/fixtures/opus/opus_tone.ogg");
let mut pages = Vec::new();
let mut i = 0usize;
let data = full;
while i + 27 <= data.len() {
if &data[i..i + 4] != b"OggS" {
break;
}
let nseg = data[i + 26] as usize;
let body: usize = data[i + 27..i + 27 + nseg]
.iter()
.map(|&s| s as usize)
.sum();
let page_end = i + 27 + nseg + body;
pages.push(page_end);
i = page_end;
if pages.len() == 2 {
break;
}
}
assert!(pages.len() >= 2, "fixture must have header pages");
let headers_only = &data[..pages[1]];
let err = decode_audio_bytes(headers_only).expect_err("headers-only Opus must fail");
let msg = format!("{err:#}");
assert!(
msg.contains("packet")
|| msg.contains("end of file")
|| msg.contains("audio")
|| msg.contains("Decode")
|| msg.contains("Unsupported")
|| msg.contains("malformed")
|| msg.contains("Opus")
|| msg.contains("track")
|| msg.contains("empty")
|| msg.contains("No "),
"unexpected error for headers-only: {msg}"
);
}
#[test]
fn test_decode_audio_bytes_random_bytes_still_errors() {
let junk = [0u8; 64];
assert!(decode_audio_bytes(&junk).is_err());
}
#[test]
fn test_decode_audio_bytes_opus_ogg_matches_ffmpeg_reference() {
let ogg = include_bytes!("../../../../tests/fixtures/opus/opus_tone.ogg");
let reference_pcm = include_bytes!("../../../../tests/fixtures/opus/opus_tone_ffmpeg.pcm");
let ours = decode_audio_bytes(ogg).expect("OGG/Opus must decode");
let reference: Vec<f32> = reference_pcm
.as_chunks::<2>()
.0
.iter()
.map(|c| f32::from(i16::from_le_bytes(*c)) / 32768.0)
.collect();
assert!(
ours.len() > 46_000 && ours.len() < 50_000,
"unexpected decoded length {}",
ours.len()
);
let rmse = best_lag_rmse(&ours, &reference, 1024);
assert!(
rmse < 0.02,
"Opus decode diverged from ffmpeg reference: RMSE {rmse}"
);
}
#[test]
fn test_decode_audio_bytes_opus_code3_multiframe_matches_ffmpeg_reference() {
let ogg = include_bytes!("../../../../tests/fixtures/opus/opus_tone_60ms.ogg");
let reference_pcm = include_bytes!("../../../../tests/fixtures/opus/opus_tone_60ms_ffmpeg.pcm");
let ours = decode_audio_bytes(ogg).expect("multi-frame OGG/Opus must decode");
let reference: Vec<f32> = reference_pcm
.as_chunks::<2>()
.0
.iter()
.map(|c| f32::from(i16::from_le_bytes(*c)) / 32768.0)
.collect();
assert!(
ours.len() > 46_000 && ours.len() < 50_000,
"unexpected decoded length {}",
ours.len()
);
let rmse = best_lag_rmse(&ours, &reference, 1024);
assert!(
rmse < 0.02,
"multi-frame Opus decode diverged from ffmpeg reference: RMSE {rmse}"
);
}
#[test]
fn test_decode_audio_bytes_webm_opus_live_matches_ffmpeg_reference() {
let webm = include_bytes!("../../../../tests/fixtures/opus/opus_tone_webm_live.webm");
let reference_pcm =
include_bytes!("../../../../tests/fixtures/opus/opus_tone_webm_live_ffmpeg.pcm");
let ours = decode_audio_bytes(webm).expect("live WebM/Opus must decode");
let reference: Vec<f32> = reference_pcm
.as_chunks::<2>()
.0
.iter()
.map(|c| f32::from(i16::from_le_bytes(*c)) / 32768.0)
.collect();
assert!(
ours.len() > 46_000 && ours.len() < 50_000,
"unexpected decoded length {}",
ours.len()
);
let rmse = best_lag_rmse(&ours, &reference, 1024);
assert!(
rmse < 0.02,
"WebM/Opus decode diverged from ffmpeg reference: RMSE {rmse}"
);
}
#[test]
fn test_decode_audio_file_webm_extension_matches_bytes() {
let webm = include_bytes!("../../../../tests/fixtures/opus/opus_tone_webm_live.webm");
let mut tmp = tempfile::NamedTempFile::with_suffix(".webm").expect("temp file");
std::io::Write::write_all(&mut tmp, webm).expect("write temp file");
let via_file =
decode_audio_file(tmp.path().to_str().expect("utf-8 path")).expect("WebM file must decode");
let via_bytes = decode_audio_bytes(webm).expect("WebM bytes must decode");
assert_eq!(via_file.len(), via_bytes.len());
for (a, b) in via_file.iter().zip(via_bytes.iter()) {
assert!((a - b).abs() < f32::EPSILON);
}
}
#[test]
fn test_decode_audio_file_opus_extension_matches_bytes() {
let ogg = include_bytes!("../../../../tests/fixtures/opus/opus_tone.ogg");
let mut tmp = tempfile::NamedTempFile::with_suffix(".opus").expect("temp file");
std::io::Write::write_all(&mut tmp, ogg).expect("write temp file");
let via_file = decode_audio_file(tmp.path().to_str().expect("utf-8 path"))
.expect("OGG/Opus file must decode");
let via_bytes = decode_audio_bytes(ogg).expect("OGG/Opus bytes must decode");
assert_eq!(via_file.len(), via_bytes.len());
for (a, b) in via_file.iter().zip(via_bytes.iter()) {
assert!((a - b).abs() < f32::EPSILON);
}
}