use oxideav_opus::{ChannelMappingTable, MultistreamDecoder, OpusHead, OpusTocByte};
const FIXTURE_NB_MONO: &[u8] = include_bytes!("fixtures/silk-nb-mono-16kbps.opus");
const FIXTURE_MB_60MS: &[u8] = include_bytes!("fixtures/silk-mb-60ms-mono-20kbps.opus");
const FIXTURE_WB_STEREO: &[u8] = include_bytes!("fixtures/silk-wb-stereo-20kbps.opus");
fn ogg_packets(data: &[u8]) -> Vec<Vec<u8>> {
let mut off = 0usize;
let mut packets: Vec<Vec<u8>> = Vec::new();
let mut cur: Vec<u8> = Vec::new();
while off + 27 <= data.len() {
assert_eq!(&data[off..off + 4], b"OggS");
let nseg = data[off + 26] as usize;
let seg_table_end = off + 27 + nseg;
assert!(seg_table_end <= data.len());
let segtab = &data[off + 27..seg_table_end];
let mut p = seg_table_end;
for &s in segtab {
let seg_end = p + s as usize;
assert!(seg_end <= data.len());
cur.extend_from_slice(&data[p..seg_end]);
p = seg_end;
if s < 255 {
packets.push(std::mem::take(&mut cur));
}
}
off = p;
}
packets
}
fn fixture_head_bytes() -> Vec<u8> {
ogg_packets(FIXTURE_NB_MONO).remove(0)
}
fn fixture_audio_packets() -> Vec<Vec<u8>> {
let mut p = ogg_packets(FIXTURE_NB_MONO);
p.drain(..2);
p
}
fn mb_audio_packets() -> Vec<Vec<u8>> {
let mut p = ogg_packets(FIXTURE_MB_60MS);
p.drain(..2);
p
}
fn wb_stereo_audio_packets() -> Vec<Vec<u8>> {
let mut p = ogg_packets(FIXTURE_WB_STEREO);
p.drain(..2);
p
}
fn self_delimit_code0(packet: &[u8]) -> Vec<u8> {
let toc = OpusTocByte::from_byte(packet[0]);
assert_eq!(toc.frame_count_range(), (1, 1), "fixture is code 0");
let body = &packet[1..];
let mut out = vec![packet[0]];
let len = body.len();
if len < 252 {
out.push(len as u8);
} else {
out.push((252 + (len - 252) % 4) as u8);
out.push(((len - 252) / 4) as u8);
}
out.extend_from_slice(body);
out
}
#[test]
fn fixture_opus_head_parses() {
let head = OpusHead::parse(&fixture_head_bytes()).unwrap();
assert_eq!(head.version, 1);
assert_eq!(head.channel_count, 1);
assert_eq!(head.mapping_family, 0);
assert_eq!(head.mapping.stream_count, 1);
assert_eq!(head.mapping.coupled_count, 0);
assert_eq!(head.mapping.mapping, vec![0]);
assert_eq!(head.input_sample_rate, 8000);
assert_eq!(head.pre_skip, 312);
}
#[test]
fn single_stream_family0_matches_plain_decode() {
let head = OpusHead::parse(&fixture_head_bytes()).unwrap();
let mut ms = MultistreamDecoder::from_head(&head);
assert_eq!(ms.output_channels(), 1);
let mut plain = oxideav_opus::OpusDecoder::new();
for pk in fixture_audio_packets() {
let ms_out = ms.decode_packet(&pk).unwrap();
let plain_out = plain.decode_packet(&pk).unwrap();
assert_eq!(ms_out.channels, 1);
assert_eq!(
ms_out.pcm, plain_out.pcm,
"N=1 multistream must equal plain decode"
);
}
}
#[test]
fn two_mono_streams_map_to_distinct_output_channels() {
let audio = fixture_audio_packets();
let pkt_a = &audio[5];
let pkt_b = &audio[40];
let mut multistream_packet = self_delimit_code0(pkt_a);
multistream_packet.extend_from_slice(pkt_b);
let mapping = ChannelMappingTable {
stream_count: 2,
coupled_count: 0,
mapping: vec![0, 1],
};
let mut ms = MultistreamDecoder::new(mapping);
assert_eq!(ms.output_channels(), 2);
let out = ms.decode_packet(&multistream_packet).unwrap();
assert_eq!(out.channels, 2);
assert_eq!(out.pcm.len(), out.samples_per_channel * 2);
assert_eq!(out.samples_per_channel, 960);
let mut da = oxideav_opus::OpusDecoder::new();
let mut db = oxideav_opus::OpusDecoder::new();
let a = da.decode_packet(pkt_a).unwrap();
let b = db.decode_packet(pkt_b).unwrap();
for s in 0..out.samples_per_channel {
assert_eq!(out.pcm[s * 2], a.pcm[s], "left channel sample {s}");
assert_eq!(out.pcm[s * 2 + 1], b.pcm[s], "right channel sample {s}");
}
}
#[test]
fn mismatched_stream_durations_rejected() {
let nb = fixture_audio_packets();
let mb = mb_audio_packets();
let mut multistream_packet = self_delimit_code0(&nb[5]); multistream_packet.extend_from_slice(&mb[3]);
let mapping = ChannelMappingTable {
stream_count: 2,
coupled_count: 0,
mapping: vec![0, 1],
};
let mut ms = MultistreamDecoder::new(mapping);
assert!(
ms.decode_packet(&multistream_packet).is_err(),
"mismatched stream durations must be rejected"
);
}
#[test]
fn silence_index_255_yields_zero_channel() {
let audio = fixture_audio_packets();
let mut multistream_packet = self_delimit_code0(&audio[5]);
multistream_packet.extend_from_slice(&audio[40]);
let mapping = ChannelMappingTable {
stream_count: 2,
coupled_count: 0,
mapping: vec![0, 1, 255],
};
let mut ms = MultistreamDecoder::new(mapping);
let out = ms.decode_packet(&multistream_packet).unwrap();
assert_eq!(out.channels, 3);
for s in 0..out.samples_per_channel {
assert_eq!(out.pcm[s * 3 + 2], 0, "silence channel sample {s}");
}
let any_nonzero_left = (0..out.samples_per_channel).any(|s| out.pcm[s * 3] != 0);
assert!(any_nonzero_left, "left channel should carry signal");
}
#[test]
fn coupled_stereo_stream_splits_to_left_right() {
let stereo = wb_stereo_audio_packets();
let pkt = &stereo[5];
let mapping = ChannelMappingTable {
stream_count: 1,
coupled_count: 1,
mapping: vec![0, 1],
};
let mut ms = MultistreamDecoder::new(mapping);
assert_eq!(ms.output_channels(), 2);
let out = ms.decode_packet(pkt).unwrap();
assert_eq!(out.channels, 2);
let mut plain = oxideav_opus::OpusDecoder::new();
let plain_out = plain.decode_packet(pkt).unwrap();
assert_eq!(plain_out.channels, 2);
assert_eq!(out.pcm, plain_out.pcm);
}
#[test]
fn coupled_stream_swapped_channel_map() {
let stereo = wb_stereo_audio_packets();
let pkt = &stereo[7];
let mapping = ChannelMappingTable {
stream_count: 1,
coupled_count: 1,
mapping: vec![1, 0], };
let mut ms = MultistreamDecoder::new(mapping);
let out = ms.decode_packet(pkt).unwrap();
let mut plain = oxideav_opus::OpusDecoder::new();
let plain_out = plain.decode_packet(pkt).unwrap();
for s in 0..out.samples_per_channel {
assert_eq!(
out.pcm[s * 2],
plain_out.pcm[s * 2 + 1],
"swapped left sample {s}"
);
assert_eq!(
out.pcm[s * 2 + 1],
plain_out.pcm[s * 2],
"swapped right sample {s}"
);
}
}
#[test]
fn duplicate_index_routes_same_stream_to_two_outputs() {
let audio = fixture_audio_packets();
let mapping = ChannelMappingTable {
stream_count: 1,
coupled_count: 0,
mapping: vec![0, 0],
};
let mut ms = MultistreamDecoder::new(mapping);
let out = ms.decode_packet(&audio[10]).unwrap();
assert_eq!(out.channels, 2);
for s in 0..out.samples_per_channel {
assert_eq!(
out.pcm[s * 2],
out.pcm[s * 2 + 1],
"duplicated channel sample {s}"
);
}
}
#[test]
fn assemble_multistream_packet_matches_hand_construction() {
let audio = fixture_audio_packets();
let pkt_a = &audio[5];
let pkt_b = &audio[40];
let assembled = oxideav_opus::assemble_multistream_packet(&[pkt_a, pkt_b]).expect("assemble");
let mut hand = self_delimit_code0(pkt_a);
hand.extend_from_slice(pkt_b);
assert_eq!(assembled, hand);
}
#[test]
fn assembled_stream_pair_decodes_like_plain_decoders() {
let audio = fixture_audio_packets();
let mapping = ChannelMappingTable {
stream_count: 2,
coupled_count: 0,
mapping: vec![0, 1],
};
let mut ms = MultistreamDecoder::new(mapping);
let mut plain = oxideav_opus::OpusDecoder::new();
for pk in &audio {
let assembled = oxideav_opus::assemble_multistream_packet(&[pk, pk]).expect("assemble");
let out = ms.decode_packet(&assembled).expect("multistream decode");
let reference = plain.decode_packet(pk).expect("plain decode");
assert_eq!(out.channels, 2);
assert_eq!(out.samples_per_channel, reference.pcm.len());
for s in 0..out.samples_per_channel {
assert_eq!(out.pcm[s * 2], reference.pcm[s], "left sample {s}");
assert_eq!(out.pcm[s * 2 + 1], reference.pcm[s], "right sample {s}");
}
}
}
#[test]
fn fixture_opus_head_recomposes_byte_identically() {
let bytes = fixture_head_bytes();
let head = OpusHead::parse(&bytes).unwrap();
let composed = head.compose().expect("compose");
assert_eq!(composed, bytes);
}