use super::sort_tables::{
SORT_1265, SORT_1425, SORT_1585, SORT_1825, SORT_1985, SORT_2305, SORT_2385, SORT_660,
SORT_885, SORT_SID,
};
use crate::codecs::amr::mode::AmrMode;
pub const MAX_FRAME_BITS: usize = 477;
#[must_use]
#[cfg(test)]
pub(crate) const fn sort_table_for(mode: AmrMode) -> &'static [u16] {
sort_table(mode)
}
#[must_use]
const fn sort_table(mode: AmrMode) -> &'static [u16] {
match mode.index() {
0 => &SORT_660,
1 => &SORT_885,
2 => &SORT_1265,
3 => &SORT_1425,
4 => &SORT_1585,
5 => &SORT_1825,
6 => &SORT_1985,
7 => &SORT_2305,
8 => &SORT_2385,
_ => &SORT_SID,
}
}
pub fn finish_sid_payload(payload: &mut [u8], is_update: bool, mode: u8) {
assert_eq!(payload.len(), 5, "a wideband SID payload is five bytes");
assert!(mode <= 8, "the SID's mode indication names a speech mode");
payload[4] &= 0b1110_0000;
payload[4] |= u8::from(is_update) << 4;
payload[4] |= mode & 0x0F;
}
pub fn blank_sid_first(payload: &mut [u8]) {
assert_eq!(payload.len(), 5, "a wideband SID payload is five bytes");
payload[0] = 0;
payload[1] = 0;
payload[2] = 0;
payload[3] = 0;
payload[4] &= 0b0001_1111;
}
#[must_use]
pub fn parse_sid(payload: &[u8]) -> Option<([u16; 5], u16, bool)> {
if payload.len() != 5 {
return None;
}
let mut bits = [0u8; 35];
for (i, &target) in SORT_SID.iter().enumerate() {
bits[target as usize] = (payload[i / 8] >> (7 - (i % 8))) & 1;
}
let mut at = 0usize;
let mut take = |width: usize| -> u16 {
let mut value = 0u16;
for _ in 0..width {
value = (value << 1) | u16::from(bits[at]);
at += 1;
}
value
};
let indices = [take(6), take(6), take(6), take(5), take(5)];
let energy = take(6);
let dither = take(1) == 1;
Some((indices, energy, dither))
}
#[must_use]
pub fn sid_is_update(payload: &[u8]) -> bool {
assert_eq!(payload.len(), 5, "a wideband SID payload is five bytes");
payload[4] & 0b0001_0000 != 0
}
#[must_use]
pub fn sid_mode_indication(payload: &[u8]) -> Option<u8> {
assert_eq!(payload.len(), 5, "a wideband SID payload is five bytes");
let mode = payload[4] & 0x0F;
(mode <= 8).then_some(mode)
}
#[derive(Debug, Clone)]
pub struct CodecBits {
bits: [u8; MAX_FRAME_BITS],
len: usize,
cursor: usize,
}
impl CodecBits {
#[must_use]
pub fn unpack(mode: AmrMode, payload: &[u8]) -> Option<Self> {
let sort = sort_table(mode);
let len = sort.len();
if payload.len() * 8 < len {
return None;
}
let mut bits = [0u8; MAX_FRAME_BITS];
for (i, &target) in sort.iter().enumerate() {
let bit = (payload[i / 8] >> (7 - (i % 8))) & 1;
bits[target as usize] = bit;
}
Some(Self {
bits,
len,
cursor: 0,
})
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn remaining(&self) -> usize {
self.len - self.cursor
}
#[must_use]
pub fn take(&mut self, width: usize) -> Option<u16> {
debug_assert!(width <= 16, "a parameter field is at most 16 bits");
if self.remaining() < width {
return None;
}
let mut value = 0u16;
for _ in 0..width {
value = (value << 1) | u16::from(self.bits[self.cursor]);
self.cursor += 1;
}
Some(value)
}
#[must_use]
pub fn bits(&self) -> &[u8] {
&self.bits[..self.len]
}
}
#[must_use]
pub const fn isf_index_widths(mode: AmrMode) -> &'static [usize] {
if mode.index() == 0 {
&[8, 8, 7, 7, 6]
} else {
&[8, 8, 6, 7, 7, 5, 5]
}
}
#[cfg(test)]
mod tests {
use super::super::lp::isp_to_lp::tests_support::{block_has, block_row, has_block};
use super::*;
use crate::codecs::amr::mode::AmrVariant;
use crate::codecs::amr::storage;
fn fixture(mode_index: usize) -> &'static [u8] {
const FILES: [&[u8]; 9] = [
include_bytes!("../testdata/amrwb_mode0.amr"),
include_bytes!("../testdata/amrwb_mode1.amr"),
include_bytes!("../testdata/amrwb_mode2.amr"),
include_bytes!("../testdata/amrwb_mode3.amr"),
include_bytes!("../testdata/amrwb_mode4.amr"),
include_bytes!("../testdata/amrwb_mode5.amr"),
include_bytes!("../testdata/amrwb_mode6.amr"),
include_bytes!("../testdata/amrwb_mode7.amr"),
include_bytes!("../testdata/amrwb_mode8.amr"),
];
FILES[mode_index]
}
fn bits_to_hex(bits: &[u8]) -> String {
let mut out = String::new();
for chunk in bits.chunks(4) {
let mut nibble = 0u8;
for i in 0..4 {
nibble = (nibble << 1) | chunk.get(i).copied().unwrap_or(0);
}
out.push(char::from_digit(u32::from(nibble), 16).expect("nibble"));
}
out
}
#[test]
fn unpacking_real_bitstreams_is_bit_exact_against_ts26173() {
let mut checked = 0;
for mode_index in 0..9 {
let block = format!("bitstream{mode_index}");
assert!(has_block(&block), "fixture block {block} missing");
let (_, frames) = storage::read(fixture(mode_index)).expect("fixture parses");
for f in 0.. {
if !block_has(&block, &format!("meta{f}")) {
break;
}
let meta = block_row(&block, &format!("meta{f}"));
let want_mode = meta[0];
let want_bits = usize::try_from(meta[1]).expect("bit count is positive");
assert_eq!(
usize::try_from(want_mode).expect("mode"),
mode_index,
"{block} frame {f}: fixture is for a different mode"
);
let frame = frames.get(f).expect("fixture has this frame");
let mode = AmrMode::new(
AmrVariant::WideBand,
u8::try_from(mode_index).expect("mode index"),
)
.expect("mode");
let bits = CodecBits::unpack(mode, &frame.data).expect("unpacks");
assert_eq!(bits.len(), want_bits, "{block} frame {f}: bit count");
assert_eq!(
bits_to_hex(bits.bits()),
block_row_str(&block, &format!("bits{f}")),
"{block} frame {f}: unsorted codec bits"
);
checked += 1;
}
}
assert!(checked >= 18, "only {checked} frames checked");
}
fn block_row_str(block: &str, label: &str) -> String {
const LP_STAGES: &str = include_str!("../testdata/lp_stages_wb.txt");
let mut in_block = false;
for line in LP_STAGES.lines() {
if line.trim_end() == block {
in_block = true;
continue;
}
if in_block {
if !line.starts_with(' ') {
break;
}
let mut parts = line.split_whitespace();
if parts.next() == Some(label) {
return parts.next().expect("value").to_owned();
}
}
}
panic!("block {block:?} has no row {label:?}");
}
#[test]
fn every_sort_table_is_a_permutation() {
for mode_index in 0..9 {
let mode = AmrMode::new(
AmrVariant::WideBand,
u8::try_from(mode_index).expect("mode index"),
)
.expect("mode");
let sort = sort_table(mode);
let mut seen = vec![false; sort.len()];
for &target in sort {
let target = target as usize;
assert!(
target < seen.len(),
"mode {mode_index}: index {target} out of range"
);
assert!(
!seen[target],
"mode {mode_index}: index {target} appears twice"
);
seen[target] = true;
}
}
}
#[test]
fn a_short_payload_is_rejected_rather_than_padded() {
let mode = AmrMode::new(AmrVariant::WideBand, 8).expect("mode");
assert!(CodecBits::unpack(mode, &[0u8; 59]).is_none());
assert!(CodecBits::unpack(mode, &[0u8; 60]).is_some());
}
#[test]
fn reading_past_the_end_fails_rather_than_returning_zeros() {
let mode = AmrMode::new(AmrVariant::WideBand, 0).expect("mode");
let mut bits = CodecBits::unpack(mode, &[0xffu8; 17]).expect("unpacks");
assert_eq!(bits.remaining(), 132);
assert!(bits.take(16).is_some());
while bits.remaining() >= 8 {
assert!(bits.take(8).is_some());
}
let left = bits.remaining();
assert!(bits.take(left + 1).is_none(), "overrun was not rejected");
assert!(bits.take(left).is_some());
}
#[test]
fn the_payload_order_is_not_the_codec_order() {
for mode_index in 0..9 {
let mode = AmrMode::new(
AmrVariant::WideBand,
u8::try_from(mode_index).expect("mode index"),
)
.expect("mode");
let sort = sort_table(mode);
assert!(
sort.iter().enumerate().any(|(i, &t)| usize::from(t) != i),
"mode {mode_index}: sorting table is the identity"
);
}
}
}