use super::mode::{AmrMode, AmrModeSet};
use crate::error::{CodecError, Result};
#[derive(Debug, Clone)]
pub struct ModeChangePolicy {
mode_set: AmrModeSet,
period: u8,
neighbor_only: bool,
current: AmrMode,
since_change: u32,
}
impl ModeChangePolicy {
pub fn new(
mode_set: AmrModeSet,
period: u8,
neighbor_only: bool,
initial: AmrMode,
) -> Result<Self> {
if period != 1 && period != 2 {
return Err(CodecError::invalid_config(format!(
"AMR mode-change-period must be 1 or 2, got {period}"
)));
}
if !mode_set.contains(initial) {
return Err(CodecError::invalid_config(format!(
"initial mode {} is outside the mode-set ({})",
initial.index(),
mode_set.to_sdp_value()
)));
}
Ok(Self {
mode_set,
period,
neighbor_only,
current: initial,
since_change: u32::from(period),
})
}
#[must_use]
pub const fn current(&self) -> AmrMode {
self.current
}
#[must_use]
pub const fn can_change_now(&self) -> bool {
self.since_change >= self.period as u32
}
pub const fn advance(&mut self) {
self.since_change = self.since_change.saturating_add(1);
}
#[must_use]
pub fn preview(&self, desired: AmrMode) -> AmrMode {
if !self.mode_set.contains(desired) {
return self.current;
}
if desired == self.current {
return self.current;
}
if !self.can_change_now() {
return self.current;
}
if self.neighbor_only {
self.step_toward(desired)
} else {
desired
}
}
pub fn request(&mut self, desired: AmrMode) -> AmrMode {
let next = self.preview(desired);
if next != self.current {
self.current = next;
self.since_change = 0;
}
next
}
fn step_toward(&self, desired: AmrMode) -> AmrMode {
let modes = self.mode_set.modes();
let Some(current_pos) = modes.iter().position(|&m| m == self.current) else {
return self.current;
};
let Some(target_pos) = modes.iter().position(|&m| m == desired) else {
return self.current;
};
let next_pos = if target_pos > current_pos {
current_pos + 1
} else {
current_pos - 1
};
modes.get(next_pos).copied().unwrap_or(self.current)
}
}
#[derive(Debug, Clone)]
pub struct CmrDamper {
mode_set: AmrModeSet,
interval_frames: u32,
elapsed: u32,
seen: u16,
}
impl CmrDamper {
pub fn new(mode_set: AmrModeSet, interval_frames: u32) -> Result<Self> {
if interval_frames == 0 {
return Err(CodecError::invalid_config(
"CMR interval must be at least one frame-block",
));
}
Ok(Self {
mode_set,
interval_frames,
elapsed: 0,
seen: 0,
})
}
pub const fn observe(&mut self, mode: AmrMode) {
if self.mode_set.contains(mode) {
self.seen |= 1u16 << mode.index();
}
}
pub fn advance(&mut self) -> Option<AmrMode> {
self.elapsed += 1;
if self.elapsed < self.interval_frames {
return None;
}
self.elapsed = 0;
let request = self.next_unused_mode();
self.seen = 0;
request
}
fn next_unused_mode(&self) -> Option<AmrMode> {
if self.seen == 0 {
return None;
}
let highest_seen =
15u8.saturating_sub(u8::try_from(self.seen.leading_zeros()).unwrap_or(15));
self.mode_set
.modes()
.into_iter()
.find(|mode| mode.index() > highest_seen)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codecs::amr::mode::AmrVariant;
const WB: AmrVariant = AmrVariant::WideBand;
fn mode(index: u8) -> AmrMode {
AmrMode::new(WB, index).unwrap()
}
fn policy(modes: &[u8], period: u8, neighbor: bool, initial: u8) -> ModeChangePolicy {
ModeChangePolicy::new(
AmrModeSet::from_indices(WB, modes).unwrap(),
period,
neighbor,
mode(initial),
)
.unwrap()
}
#[test]
fn unrestricted_policy_changes_on_every_frame_block() {
let mut p = policy(&[0, 1, 2, 3, 4], 1, false, 0);
assert_eq!(p.request(mode(4)), mode(4));
assert_eq!(p.current(), mode(4));
p.advance();
assert_eq!(p.request(mode(1)), mode(1));
}
#[test]
fn a_second_change_within_one_frame_block_is_deferred() {
let mut p = policy(&[0, 1, 2], 1, false, 0);
assert_eq!(p.request(mode(2)), mode(2));
assert_eq!(p.request(mode(1)), mode(2), "no frame boundary has passed");
p.advance();
assert_eq!(p.request(mode(1)), mode(1));
}
#[test]
fn period_two_separates_changes_by_two_frame_blocks() {
let mut p = policy(&[0, 1, 2], 2, false, 0);
assert_eq!(p.request(mode(2)), mode(2));
p.advance(); assert!(!p.can_change_now());
assert_eq!(p.request(mode(0)), mode(2), "only one frame-block elapsed");
p.advance(); assert!(p.can_change_now());
assert_eq!(p.request(mode(0)), mode(0));
}
#[test]
fn period_one_allows_a_change_on_every_frame_block() {
let mut p = policy(&[0, 1, 2], 1, false, 0);
for target in [2u8, 0, 2, 1, 0] {
assert_eq!(p.request(mode(target)), mode(target));
p.advance();
}
}
#[test]
fn period_two_permits_half_as_many_changes_as_period_one() {
let changes = |period: u8| {
let mut p = policy(&[0, 1], period, false, 0);
let mut changes = 0;
let mut want = mode(1);
for _ in 0..40 {
let before = p.current();
if p.request(want) != before {
changes += 1;
want = if want == mode(1) { mode(0) } else { mode(1) };
}
p.advance();
}
changes
};
assert_eq!(changes(1), 40);
assert_eq!(changes(2), 20);
}
#[test]
fn neighbor_restriction_steps_through_the_active_set_not_mode_indices() {
let mut p = policy(&[0, 4, 8], 1, true, 0);
assert_eq!(p.request(mode(8)), mode(4), "one step, to the next in set");
p.advance();
assert_eq!(p.request(mode(8)), mode(8), "second step reaches it");
p.advance();
assert_eq!(p.request(mode(0)), mode(4), "and back down one step");
}
#[test]
fn neighbor_restriction_walks_to_a_distant_target_over_several_requests() {
let mut p = policy(&[0, 1, 2, 3, 4, 5, 6, 7, 8], 1, true, 0);
let target = mode(8);
for expected in 1..=8u8 {
p.advance();
assert_eq!(p.request(target), mode(expected));
}
p.advance();
assert_eq!(p.request(target), mode(8));
}
#[test]
fn modes_outside_the_negotiated_set_are_never_selected() {
let mut p = policy(&[0, 2, 4], 1, false, 0);
assert_eq!(p.request(mode(3)), mode(0), "mode 3 is not in the set");
assert_eq!(p.current(), mode(0));
assert_eq!(p.request(mode(4)), mode(4));
}
#[test]
fn preview_does_not_mutate() {
let p = policy(&[0, 1, 2], 1, false, 0);
assert_eq!(p.preview(mode(2)), mode(2));
assert_eq!(p.current(), mode(0), "preview must not change state");
}
#[test]
fn policy_construction_validates_its_inputs() {
let set = AmrModeSet::from_indices(WB, &[0, 1]).unwrap();
assert!(ModeChangePolicy::new(set.clone(), 3, false, mode(0)).is_err());
assert!(ModeChangePolicy::new(set.clone(), 0, false, mode(0)).is_err());
assert!(ModeChangePolicy::new(set, 1, false, mode(5)).is_err());
}
#[test]
fn damper_emits_at_most_one_request_per_interval() {
let set = AmrModeSet::from_indices(WB, &[0, 1, 2]).unwrap();
let mut d = CmrDamper::new(set, 5).unwrap();
let mut requests = Vec::new();
for _ in 0..20 {
d.observe(mode(0));
if let Some(request) = d.advance() {
requests.push(request.index());
}
}
assert_eq!(requests, vec![1, 1, 1, 1]);
}
#[test]
fn damper_asks_only_for_the_next_step_up() {
let set = AmrModeSet::from_indices(WB, &[0, 3, 6, 8]).unwrap();
let mut d = CmrDamper::new(set, 2).unwrap();
d.observe(mode(0));
d.observe(mode(3));
assert_eq!(d.advance(), None);
assert_eq!(d.advance().map(AmrMode::index), Some(6));
}
#[test]
fn damper_is_silent_when_the_peer_already_uses_the_top_mode() {
let set = AmrModeSet::from_indices(WB, &[0, 1, 2]).unwrap();
let mut d = CmrDamper::new(set, 2).unwrap();
d.observe(mode(2));
assert_eq!(d.advance(), None);
assert_eq!(d.advance(), None);
}
#[test]
fn damper_is_silent_with_no_evidence() {
let set = AmrModeSet::all(WB);
let mut d = CmrDamper::new(set, 3).unwrap();
assert_eq!(d.advance(), None);
assert_eq!(d.advance(), None);
assert_eq!(d.advance(), None, "interval closed with nothing observed");
}
#[test]
fn damper_ignores_modes_outside_the_negotiated_set() {
let set = AmrModeSet::from_indices(WB, &[0, 1]).unwrap();
let mut d = CmrDamper::new(set, 2).unwrap();
d.observe(mode(8));
assert_eq!(d.advance(), None);
assert_eq!(d.advance(), None);
}
#[test]
fn damper_resets_its_observations_each_interval() {
let set = AmrModeSet::from_indices(WB, &[0, 1, 2]).unwrap();
let mut d = CmrDamper::new(set, 2).unwrap();
d.observe(mode(2));
d.advance();
assert_eq!(d.advance(), None);
d.observe(mode(0));
d.advance();
assert_eq!(d.advance().map(AmrMode::index), Some(1));
}
#[test]
fn damper_rejects_a_zero_interval() {
assert!(CmrDamper::new(AmrModeSet::all(WB), 0).is_err());
}
#[test]
fn damper_and_policy_compose_without_oscillating() {
let set = AmrModeSet::from_indices(WB, &[0, 2, 4, 6]).unwrap();
let mut p = ModeChangePolicy::new(set.clone(), 2, true, mode(0)).unwrap();
let mut d = CmrDamper::new(set, 4).unwrap();
let mut trajectory = vec![p.current().index()];
for _ in 0..40 {
d.observe(p.current());
if let Some(request) = d.advance() {
p.request(request);
}
p.advance();
trajectory.push(p.current().index());
}
assert_eq!(*trajectory.last().unwrap(), 6);
for pair in trajectory.windows(2) {
let step = i32::from(pair[1]) - i32::from(pair[0]);
assert!(
step == 0 || step == 2,
"jumped from {} to {}",
pair[0],
pair[1]
);
}
}
}