use std::io::Cursor;
use oxideav_basic::{register_containers, slin};
use oxideav_core::{
CodecId, CodecParameters, MediaType, Packet, SampleFormat, StreamInfo, TimeBase,
};
use oxideav_core::{ContainerRegistry, ProbeData, WriteSeek, PROBE_SCORE_EXTENSION};
fn mono_s16_stream(sample_rate: u32) -> StreamInfo {
let mut params = CodecParameters::audio(CodecId::new("pcm_s16le"));
params.media_type = MediaType::Audio;
params.channels = Some(1);
params.sample_rate = Some(sample_rate);
params.sample_format = Some(SampleFormat::S16);
StreamInfo {
index: 0,
time_base: TimeBase::new(1, sample_rate as i64),
duration: None,
start_time: Some(0),
params,
}
}
fn synth_100ms_s16(sample_rate: u32) -> Vec<u8> {
let n = (sample_rate as usize) / 10; let mut out = Vec::with_capacity(n * 2);
for i in 0..n {
let s = (((i as i32) * 327) % 65536 - 32768) as i16;
out.extend_from_slice(&s.to_le_bytes());
}
out
}
fn roundtrip_through_file(sample_rate: u32, ext: &str) {
let payload = synth_100ms_s16(sample_rate);
assert_eq!(payload.len(), (sample_rate as usize / 10) * 2);
let mut reg = ContainerRegistry::new();
register_containers(&mut reg);
let tmp = std::env::temp_dir().join(format!("oxideav-basic-slin-{sample_rate}.{ext}"));
let _ = std::fs::remove_file(&tmp);
let stream = mono_s16_stream(sample_rate);
{
let f = std::fs::File::create(&tmp).unwrap();
let ws: Box<dyn WriteSeek> = Box::new(f);
let mut mux = reg
.open_muxer("slin", ws, std::slice::from_ref(&stream))
.expect("open slin muxer");
mux.write_header().unwrap();
let pkt = Packet::new(0, stream.time_base, payload.clone());
mux.write_packet(&pkt).unwrap();
mux.write_trailer().unwrap();
}
let on_disk = std::fs::read(&tmp).unwrap();
assert_eq!(
on_disk, payload,
"slin file body must equal raw S16LE payload"
);
let f = std::fs::File::open(&tmp).unwrap();
let mut dmx = slin::open_demuxer_with_rate(Box::new(f), sample_rate).expect("open demuxer");
assert_eq!(dmx.format_name(), "slin");
assert_eq!(dmx.streams().len(), 1);
assert_eq!(
dmx.streams()[0].params.sample_rate,
Some(sample_rate),
"demuxer should report the sample rate it was opened at"
);
assert_eq!(dmx.streams()[0].params.channels, Some(1));
assert_eq!(
dmx.streams()[0].params.sample_format,
Some(SampleFormat::S16)
);
let mut out = Vec::new();
loop {
match dmx.next_packet() {
Ok(p) => out.extend_from_slice(&p.data),
Err(oxideav_core::Error::Eof) => break,
Err(e) => panic!("demux error: {e}"),
}
}
assert_eq!(out, payload, "round-tripped PCM bytes must match input");
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn roundtrip_sln_8k() {
roundtrip_through_file(8_000, "sln");
}
#[test]
fn roundtrip_sln48_48k() {
roundtrip_through_file(48_000, "sln48");
}
#[test]
fn probe_sln_extension_is_weak_but_positive() {
let data = ProbeData {
buf: &[],
ext: Some("sln"),
};
let score = slin::probe(&data);
assert!(score > 0, "slin probe must fire on .sln extension");
assert!(
score <= PROBE_SCORE_EXTENSION,
"slin probe must stay at or below PROBE_SCORE_EXTENSION ({PROBE_SCORE_EXTENSION}), got {score}"
);
assert!(score <= 25, "slin probe must be weak (<= 25), got {score}");
}
#[test]
fn probe_without_extension_returns_zero() {
let data = ProbeData {
buf: &[0xAA; 512],
ext: None,
};
assert_eq!(slin::probe(&data), 0);
}
#[test]
fn probe_unrelated_extension_returns_zero() {
let data = ProbeData {
buf: &[],
ext: Some("wav"),
};
assert_eq!(slin::probe(&data), 0);
}
#[test]
fn registry_probe_input_picks_slin_on_extension() {
let mut reg = ContainerRegistry::new();
register_containers(&mut reg);
let mut cur = Cursor::new(vec![0u8; 16]);
let name = reg.probe_input(&mut cur, Some("sln16")).expect("probe");
assert_eq!(name, "slin");
}