use super::payload::{AmrInterleaving, AmrPayloadFrame};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Deinterleaved {
Frame(AmrPayloadFrame),
Lost,
}
#[derive(Debug, Clone, Default)]
pub struct Deinterleaver {
slots: Vec<Option<AmrPayloadFrame>>,
ill: Option<u8>,
seen: u16,
}
impl Deinterleaver {
#[must_use]
pub const fn new() -> Self {
Self {
slots: Vec::new(),
ill: None,
seen: 0,
}
}
pub fn push(
&mut self,
interleaving: AmrInterleaving,
frames: Vec<AmrPayloadFrame>,
) -> Vec<Deinterleaved> {
let group_len = usize::from(interleaving.group_len());
let index = usize::from(interleaving.ilp);
let starts_new_group = match self.ill {
Some(ill) => ill != interleaving.ill || self.seen & (1u16 << index) != 0,
None => false,
};
let mut flushed = if starts_new_group {
self.flush()
} else {
Vec::new()
};
if self.ill.is_none() {
self.ill = Some(interleaving.ill);
self.slots = vec![None; group_len * frames.len().max(1)];
}
let needed = index + (frames.len().saturating_sub(1)) * group_len + 1;
if self.slots.len() < needed {
self.slots.resize(needed, None);
}
for (offset, frame) in frames.into_iter().enumerate() {
let position = index + offset * group_len;
if let Some(slot) = self.slots.get_mut(position) {
*slot = Some(frame);
}
}
self.seen |= 1u16 << index;
if self.seen.count_ones() as usize >= group_len {
let mut completed = self.flush();
flushed.append(&mut completed);
}
flushed
}
pub fn flush(&mut self) -> Vec<Deinterleaved> {
let out = self
.slots
.drain(..)
.map(|slot| slot.map_or(Deinterleaved::Lost, Deinterleaved::Frame))
.collect();
self.ill = None;
self.seen = 0;
out
}
#[must_use]
pub fn buffered(&self) -> usize {
self.slots.iter().filter(|slot| slot.is_some()).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codecs::amr::mode::{AmrFrameType, AmrMode, AmrVariant};
const NB: AmrVariant = AmrVariant::NarrowBand;
fn frame(tag: u8) -> AmrPayloadFrame {
let mode = AmrMode::new(NB, 7).expect("12.2 is a narrowband mode");
AmrPayloadFrame::new(
AmrFrameType::Speech(mode),
true,
vec![tag; mode.octet_aligned_bytes()],
)
.expect("a full-length frame")
}
fn tags(out: &[Deinterleaved]) -> Vec<Option<u8>> {
out.iter()
.map(|entry| match entry {
Deinterleaved::Frame(frame) => Some(frame.data[0]),
Deinterleaved::Lost => None,
})
.collect()
}
fn il(ill: u8, ilp: u8) -> AmrInterleaving {
AmrInterleaving::new(ill, ilp).expect("valid interleaving fields")
}
#[test]
fn a_two_packet_group_reassembles_to_the_original_order() {
let mut deinterleaver = Deinterleaver::new();
let out = deinterleaver.push(il(1, 0), vec![frame(0), frame(2), frame(4)]);
assert!(
out.is_empty(),
"a group cannot be ordered until it is complete"
);
let out = deinterleaver.push(il(1, 1), vec![frame(1), frame(3), frame(5)]);
assert_eq!(
tags(&out),
[Some(0), Some(1), Some(2), Some(3), Some(4), Some(5)],
"the interleaved blocks must come back in their original order"
);
}
#[test]
fn a_lost_packet_becomes_isolated_gaps_not_a_burst() {
let mut deinterleaver = Deinterleaver::new();
deinterleaver.push(il(1, 0), vec![frame(0), frame(2), frame(4)]);
let out = deinterleaver.push(il(1, 0), vec![frame(10), frame(12), frame(14)]);
assert_eq!(
tags(&out),
[Some(0), None, Some(2), None, Some(4), None],
"losses must be single frames spread through the group"
);
}
#[test]
fn the_stride_is_the_group_length() {
let mut deinterleaver = Deinterleaver::new();
deinterleaver.push(il(2, 0), vec![frame(0), frame(3)]);
deinterleaver.push(il(2, 1), vec![frame(1), frame(4)]);
let out = deinterleaver.push(il(2, 2), vec![frame(2), frame(5)]);
assert_eq!(
tags(&out),
[Some(0), Some(1), Some(2), Some(3), Some(4), Some(5)]
);
}
#[test]
fn a_group_of_one_passes_frames_straight_through() {
let mut deinterleaver = Deinterleaver::new();
let out = deinterleaver.push(il(0, 0), vec![frame(0), frame(1)]);
assert_eq!(tags(&out), [Some(0), Some(1)]);
}
#[test]
fn a_duplicated_packet_starts_a_new_group_rather_than_overwriting() {
let mut deinterleaver = Deinterleaver::new();
deinterleaver.push(il(1, 0), vec![frame(0), frame(2)]);
let out = deinterleaver.push(il(1, 0), vec![frame(20), frame(22)]);
assert_eq!(tags(&out), [Some(0), None, Some(2), None]);
}
#[test]
fn an_endless_run_of_incomplete_groups_stays_bounded() {
let mut deinterleaver = Deinterleaver::new();
for _ in 0..1_000 {
deinterleaver.push(il(15, 0), vec![frame(1), frame(2), frame(3)]);
assert!(
deinterleaver.buffered() <= 3,
"an incomplete group must be flushed, not accumulated"
);
}
}
#[test]
fn flush_reports_gaps_for_the_positions_that_never_arrived() {
let mut deinterleaver = Deinterleaver::new();
deinterleaver.push(il(3, 2), vec![frame(2)]);
let out = deinterleaver.flush();
assert_eq!(tags(&out), [None, None, Some(2), None]);
assert_eq!(deinterleaver.buffered(), 0, "flush must reset the buffer");
}
}