use oxideav_opus::{FecDecodeStatus, OpusDecoder};
const FIXTURE_FEC: &[u8] = include_bytes!("fixtures/fec-on.opus");
const FIXTURE_NB_MONO: &[u8] = include_bytes!("fixtures/silk-nb-mono-16kbps.opus");
const FIXTURE_WB_STEREO: &[u8] = include_bytes!("fixtures/silk-wb-stereo-20kbps.opus");
fn ogg_audio_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", "lost Ogg page sync at {off}");
let nseg = data[off + 26] as usize;
let seg_table_end = off + 27 + nseg;
assert!(seg_table_end <= data.len(), "truncated Ogg lacing table");
let segtab = &data[off + 27..seg_table_end];
let mut p = seg_table_end;
for &lace in segtab {
let l = lace as usize;
assert!(p + l <= data.len(), "Ogg segment overruns buffer");
cur.extend_from_slice(&data[p..p + l]);
p += l;
if lace < 255 {
packets.push(std::mem::take(&mut cur));
}
}
off = p;
}
packets.into_iter().skip(2).collect()
}
#[test]
fn fec_fixture_recovers_at_least_one_frame() {
let packets = ogg_audio_packets(FIXTURE_FEC);
assert!(!packets.is_empty(), "no audio packets parsed from fec-on");
let mut dec = OpusDecoder::new();
let mut recovered = 0usize;
let mut recovered_nonsilent = 0usize;
for pkt in &packets {
let fec = dec
.decode_packet_fec(pkt)
.expect("fec decode must not error");
assert_eq!(fec.channels, 1);
assert_eq!(fec.sample_rate_hz, 48_000);
assert_eq!(fec.pcm.len(), 960, "recovered frame is one 20 ms frame");
match fec.status {
FecDecodeStatus::Recovered => {
recovered += 1;
if fec.pcm.iter().any(|&s| s != 0) {
recovered_nonsilent += 1;
}
}
FecDecodeStatus::NoLbrr => {}
other => panic!("unexpected FEC status on a SILK packet: {other:?}"),
}
}
assert!(
recovered > 0,
"the FEC-enabled fixture must recover at least one frame from LBRR"
);
assert!(
recovered_nonsilent > 0,
"a recovered 440 Hz frame must be non-silent (real reconstruction)"
);
}
#[test]
fn fec_decode_advances_then_regular_decode_continues() {
let packets = ogg_audio_packets(FIXTURE_FEC);
assert!(packets.len() >= 3);
let mut dec = OpusDecoder::new();
dec.decode_packet(&packets[0]).expect("warmup decode");
let fec = dec.decode_packet_fec(&packets[2]).expect("fec decode");
assert!(matches!(
fec.status,
FecDecodeStatus::Recovered | FecDecodeStatus::NoLbrr
));
let out = dec.decode_packet(&packets[2]).expect("regular decode");
assert_eq!(out.channels, 1);
assert_eq!(out.samples_per_channel(), 960);
}
#[test]
fn fec_on_non_fec_stream_reports_no_lbrr_or_recovered() {
let packets = ogg_audio_packets(FIXTURE_NB_MONO);
let mut dec = OpusDecoder::new();
for pkt in &packets {
let fec = dec
.decode_packet_fec(pkt)
.expect("fec decode must not error");
assert_eq!(fec.channels, 1);
assert_eq!(fec.pcm.len(), 960);
assert!(
matches!(
fec.status,
FecDecodeStatus::NoLbrr | FecDecodeStatus::Recovered
),
"non-FEC stream must not yield a decode error: {:?}",
fec.status
);
if matches!(fec.status, FecDecodeStatus::NoLbrr) {
assert!(fec.pcm.iter().all(|&s| s == 0), "NoLbrr must emit silence");
}
}
}
#[test]
fn fec_on_stereo_stream_routes_cleanly() {
let packets = ogg_audio_packets(FIXTURE_WB_STEREO);
let mut dec = OpusDecoder::new();
for pkt in &packets {
let fec = dec
.decode_packet_fec(pkt)
.expect("stereo fec decode must not error");
assert_eq!(fec.channels, 2);
assert_eq!(fec.sample_rate_hz, 48_000);
assert_eq!(fec.pcm.len(), 1920);
assert!(
matches!(
fec.status,
FecDecodeStatus::NoLbrr | FecDecodeStatus::Recovered
),
"stereo SILK packet must route cleanly: {:?}",
fec.status
);
if matches!(fec.status, FecDecodeStatus::NoLbrr) {
assert!(
fec.pcm.iter().all(|&s| s == 0),
"NoLbrr stereo must emit interleaved silence"
);
}
}
}