use crate::framing::OperatingMode;
use crate::range_decoder::RangeDecoder;
pub const SILK_ONLY_REDUNDANCY_MIN_REMAINING_BITS: u32 = 17;
pub const HYBRID_REDUNDANCY_MIN_REMAINING_BITS: u32 = 37;
pub const REDUNDANCY_FLAG_ICDF_FTB: u32 = 12;
pub const REDUNDANCY_FLAG_ICDF: [u8; 2] = [1, 0];
pub const REDUNDANCY_POSITION_ICDF_FTB: u32 = 1;
pub const REDUNDANCY_POSITION_ICDF: [u8; 2] = [1, 0];
pub const HYBRID_REDUNDANCY_SIZE_BASELINE_BYTES: usize = 2;
pub const HYBRID_REDUNDANCY_SIZE_DEC_UINT_FT: u32 = 256;
pub const REDUNDANCY_MIN_SIZE_BYTES: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RedundancyPosition {
End,
Beginning,
}
impl RedundancyPosition {
pub fn from_symbol(symbol: u32) -> Self {
if symbol == 0 {
RedundancyPosition::End
} else {
RedundancyPosition::Beginning
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RedundancyDecision {
NotPresent,
Present {
position: RedundancyPosition,
size_bytes: usize,
},
Invalid,
}
impl RedundancyDecision {
pub fn is_present(&self) -> bool {
matches!(self, RedundancyDecision::Present { .. })
}
pub fn position(&self) -> Option<RedundancyPosition> {
match self {
RedundancyDecision::Present { position, .. } => Some(*position),
_ => None,
}
}
pub fn size_bytes(&self) -> Option<usize> {
match self {
RedundancyDecision::Present { size_bytes, .. } => Some(*size_bytes),
_ => None,
}
}
}
pub fn remaining_bits(rd: &RangeDecoder<'_>, opus_frame_bytes: usize) -> u32 {
let total = (opus_frame_bytes as u64).saturating_mul(8);
let tell = rd.tell() as u64;
total.saturating_sub(tell).min(u32::MAX as u64) as u32
}
pub fn whole_bytes_remaining(rd: &RangeDecoder<'_>, opus_frame_bytes: usize) -> usize {
let remaining = remaining_bits(rd, opus_frame_bytes);
(remaining / 8) as usize
}
pub fn decode_redundancy(
rd: &mut RangeDecoder<'_>,
mode: OperatingMode,
opus_frame_bytes: usize,
) -> RedundancyDecision {
match mode {
OperatingMode::CeltOnly => RedundancyDecision::NotPresent,
OperatingMode::SilkOnly => decode_silk_only(rd, opus_frame_bytes),
OperatingMode::Hybrid => decode_hybrid(rd, opus_frame_bytes),
}
}
fn decode_silk_only(rd: &mut RangeDecoder<'_>, opus_frame_bytes: usize) -> RedundancyDecision {
if remaining_bits(rd, opus_frame_bytes) < SILK_ONLY_REDUNDANCY_MIN_REMAINING_BITS {
return RedundancyDecision::NotPresent;
}
let pos_symbol = rd.dec_icdf(&REDUNDANCY_POSITION_ICDF, REDUNDANCY_POSITION_ICDF_FTB);
let position = RedundancyPosition::from_symbol(pos_symbol);
let size_bytes = whole_bytes_remaining(rd, opus_frame_bytes);
if size_bytes < REDUNDANCY_MIN_SIZE_BYTES {
return RedundancyDecision::Invalid;
}
RedundancyDecision::Present {
position,
size_bytes,
}
}
fn decode_hybrid(rd: &mut RangeDecoder<'_>, opus_frame_bytes: usize) -> RedundancyDecision {
if remaining_bits(rd, opus_frame_bytes) < HYBRID_REDUNDANCY_MIN_REMAINING_BITS {
return RedundancyDecision::NotPresent;
}
let flag = rd.dec_icdf(&REDUNDANCY_FLAG_ICDF, REDUNDANCY_FLAG_ICDF_FTB);
if flag == 0 {
return RedundancyDecision::NotPresent;
}
let pos_symbol = rd.dec_icdf(&REDUNDANCY_POSITION_ICDF, REDUNDANCY_POSITION_ICDF_FTB);
let position = RedundancyPosition::from_symbol(pos_symbol);
let payload = match rd.dec_uint(HYBRID_REDUNDANCY_SIZE_DEC_UINT_FT) {
Ok(v) => v as usize,
Err(_) => return RedundancyDecision::Invalid,
};
let claimed_size = HYBRID_REDUNDANCY_SIZE_BASELINE_BYTES.saturating_add(payload);
let whole_bytes = whole_bytes_remaining(rd, opus_frame_bytes);
if claimed_size > whole_bytes {
return RedundancyDecision::Invalid;
}
RedundancyDecision::Present {
position,
size_bytes: claimed_size,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rd<'a>(buf: &'a [u8]) -> RangeDecoder<'a> {
RangeDecoder::new(buf)
}
#[test]
fn celt_only_always_not_present() {
let buf = [0x80, 0x00, 0x00, 0x00];
let mut decoder = rd(&buf);
let decision = decode_redundancy(&mut decoder, OperatingMode::CeltOnly, buf.len());
assert_eq!(decision, RedundancyDecision::NotPresent);
assert_eq!(decoder.tell(), 1);
}
#[test]
fn silk_only_below_17_bits_remaining_is_not_present() {
let buf = [0xAA, 0x55];
let mut decoder = rd(&buf);
let decision = decode_redundancy(&mut decoder, OperatingMode::SilkOnly, buf.len());
assert_eq!(decision, RedundancyDecision::NotPresent);
}
#[test]
fn silk_only_with_full_buffer_is_present() {
let buf = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = rd(&buf);
let decision = decode_redundancy(&mut decoder, OperatingMode::SilkOnly, buf.len());
assert!(decision.is_present());
let size = decision.size_bytes().unwrap();
assert!(size >= REDUNDANCY_MIN_SIZE_BYTES);
assert!(size <= buf.len());
}
#[test]
fn silk_only_size_eq_whole_bytes_remaining_invariant() {
let buf = [0x55, 0xAA, 0xC3, 0x3C, 0x96, 0x69];
let mut decoder = rd(&buf);
let pre_decision_decoder_clone_tell;
{
let mut probe = rd(&buf);
let _ = probe.dec_icdf(&REDUNDANCY_POSITION_ICDF, REDUNDANCY_POSITION_ICDF_FTB);
pre_decision_decoder_clone_tell = whole_bytes_remaining(&probe, buf.len());
}
let decision = decode_redundancy(&mut decoder, OperatingMode::SilkOnly, buf.len());
if let RedundancyDecision::Present { size_bytes, .. } = decision {
assert_eq!(size_bytes, pre_decision_decoder_clone_tell);
} else {
panic!("expected SILK-only redundancy present, got {:?}", decision);
}
}
#[test]
fn hybrid_below_37_bits_remaining_is_not_present() {
let buf = [0xDE, 0xAD, 0xBE, 0xEF];
let mut decoder = rd(&buf);
let decision = decode_redundancy(&mut decoder, OperatingMode::Hybrid, buf.len());
assert_eq!(decision, RedundancyDecision::NotPresent);
}
#[test]
fn hybrid_with_full_buffer_decodes_flag() {
let buf = [0u8; 16];
let mut decoder = rd(&buf);
let decision = decode_redundancy(&mut decoder, OperatingMode::Hybrid, buf.len());
assert!(decoder.tell() > 1);
let _ = decision;
}
#[test]
fn icdf_tables_match_rfc_pdfs() {
assert_eq!(REDUNDANCY_FLAG_ICDF, [1u8, 0]);
assert_eq!(REDUNDANCY_FLAG_ICDF_FTB, 12);
assert_eq!(REDUNDANCY_POSITION_ICDF, [1u8, 0]);
assert_eq!(REDUNDANCY_POSITION_ICDF_FTB, 1);
}
#[test]
fn position_symbol_round_trip() {
assert_eq!(RedundancyPosition::from_symbol(0), RedundancyPosition::End);
assert_eq!(
RedundancyPosition::from_symbol(1),
RedundancyPosition::Beginning
);
assert_eq!(
RedundancyPosition::from_symbol(42),
RedundancyPosition::Beginning
);
}
#[test]
fn remaining_bits_matches_total_minus_tell() {
let buf = [0xFFu8; 5];
let decoder = rd(&buf);
let total_bits = (buf.len() as u32) * 8;
let tell = decoder.tell();
assert_eq!(remaining_bits(&decoder, buf.len()), total_bits - tell);
}
#[test]
fn whole_bytes_remaining_floors_to_bytes() {
let buf = [0u8; 5];
let decoder = rd(&buf);
assert_eq!(whole_bytes_remaining(&decoder, buf.len()), 4);
}
#[test]
fn decision_helpers_consistent() {
let absent = RedundancyDecision::NotPresent;
assert!(!absent.is_present());
assert_eq!(absent.position(), None);
assert_eq!(absent.size_bytes(), None);
let present = RedundancyDecision::Present {
position: RedundancyPosition::Beginning,
size_bytes: 7,
};
assert!(present.is_present());
assert_eq!(present.position(), Some(RedundancyPosition::Beginning));
assert_eq!(present.size_bytes(), Some(7));
let invalid = RedundancyDecision::Invalid;
assert!(!invalid.is_present());
assert_eq!(invalid.position(), None);
assert_eq!(invalid.size_bytes(), None);
}
#[test]
fn constants_match_rfc_text() {
assert_eq!(SILK_ONLY_REDUNDANCY_MIN_REMAINING_BITS, 17);
assert_eq!(HYBRID_REDUNDANCY_MIN_REMAINING_BITS, 37);
assert_eq!(HYBRID_REDUNDANCY_SIZE_BASELINE_BYTES, 2);
assert_eq!(HYBRID_REDUNDANCY_SIZE_DEC_UINT_FT, 256);
assert_eq!(REDUNDANCY_MIN_SIZE_BYTES, 2);
}
}