use super::tables::{
BITNO_102, BITNO_122, BITNO_475, BITNO_515, BITNO_59, BITNO_67, BITNO_74, BITNO_795, BITNO_SID,
SORT_102, SORT_122, SORT_475, SORT_515, SORT_59, SORT_67, SORT_74, SORT_795, SORT_SID,
};
pub const MAX_FRAME_BITS: usize = 244;
#[must_use]
pub const fn sort_table_for(mode_index: u8) -> &'static [u16] {
sort_table(mode_index)
}
const fn sort_table(mode_index: u8) -> &'static [u16] {
match mode_index {
0 => &SORT_475,
1 => &SORT_515,
2 => &SORT_59,
3 => &SORT_67,
4 => &SORT_74,
5 => &SORT_795,
6 => &SORT_102,
7 => &SORT_122,
_ => &SORT_SID,
}
}
#[must_use]
pub const fn parameter_widths(mode_index: u8) -> &'static [usize] {
match mode_index {
0 => &BITNO_475,
1 => &BITNO_515,
2 => &BITNO_59,
3 => &BITNO_67,
4 => &BITNO_74,
5 => &BITNO_795,
6 => &BITNO_102,
7 => &BITNO_122,
_ => &BITNO_SID,
}
}
#[must_use]
pub fn unpack(mode_index: u8, payload: &[u8]) -> Option<Vec<u8>> {
let sort = sort_table(mode_index);
if payload.len() * 8 < sort.len() {
return None;
}
let mut bits = vec![0u8; sort.len()];
for (i, &target) in sort.iter().enumerate() {
bits[target as usize] = (payload[i / 8] >> (7 - (i % 8))) & 1;
}
Some(bits)
}
#[must_use]
pub fn read_parameters(mode_index: u8, bits: &[u8]) -> Option<Vec<u16>> {
let widths = parameter_widths(mode_index);
let mut out = Vec::with_capacity(widths.len());
let mut cursor = 0usize;
for &width in widths {
if cursor + width > bits.len() {
return None;
}
let mut value = 0u16;
for _ in 0..width {
value = (value << 1) | u16::from(bits[cursor]);
cursor += 1;
}
out.push(value);
}
if cursor != bits.len() {
return None;
}
Some(out)
}
#[must_use]
pub fn parse(mode_index: u8, payload: &[u8]) -> Option<Vec<u16>> {
let bits = unpack(mode_index, payload)?;
read_parameters(mode_index, &bits)
}
pub fn finish_sid_payload(payload: &mut [u8], update: bool, mode: u8) {
assert_eq!(payload.len(), 5, "a narrowband SID is five octets");
assert!(mode < 8, "mode {mode} is not a speech mode");
let reversed = ((mode & 4) >> 2) | (mode & 2) | ((mode & 1) << 2);
let tail = (u8::from(update) << 4) | (reversed << 1);
payload[4] = (payload[4] & 0xE0) | tail;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SidHeader {
pub update: bool,
pub mode_index: u8,
}
#[must_use]
pub fn parse_sid_header(payload: &[u8]) -> Option<SidHeader> {
if payload.len() < 5 {
return None;
}
let bit = |i: usize| (payload[i / 8] >> (7 - (i % 8))) & 1;
let update = bit(35) != 0;
let reversed = (bit(36) << 2) | (bit(37) << 1) | bit(38);
let mode_index = ((reversed & 4) >> 2) | (reversed & 2) | ((reversed & 1) << 2);
Some(SidHeader { update, mode_index })
}
#[cfg(test)]
mod tests {
use super::*;
const STAGES: &str = include_str!("../testdata/stages_nb.txt");
fn block_rows(block: &str) -> impl Iterator<Item = &'static str> + '_ {
STAGES
.lines()
.skip_while(move |l| l.trim_end() != block)
.skip(1)
.take_while(|l| l.starts_with(' '))
}
fn row(block: &str, label: &str) -> Vec<i64> {
for line in block_rows(block) {
let mut parts = line.split_whitespace();
if parts.next() == Some(label) {
return parts.map(|v| v.parse().expect("integer")).collect();
}
}
panic!("block {block:?} has no row {label:?}");
}
fn row_str(block: &str, label: &str) -> String {
for line in block_rows(block) {
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:?}");
}
fn has_row(block: &str, label: &str) -> bool {
block_rows(block).any(|l| l.split_whitespace().next() == Some(label))
}
fn fixture(mode_index: usize) -> &'static [u8] {
const FILES: [&[u8]; 8] = [
include_bytes!("../testdata/amrnb_mode0.amr"),
include_bytes!("../testdata/amrnb_mode1.amr"),
include_bytes!("../testdata/amrnb_mode2.amr"),
include_bytes!("../testdata/amrnb_mode3.amr"),
include_bytes!("../testdata/amrnb_mode4.amr"),
include_bytes!("../testdata/amrnb_mode5.amr"),
include_bytes!("../testdata/amrnb_mode6.amr"),
include_bytes!("../testdata/amrnb_mode7.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_ts26073() {
let mut checked = 0;
for mode_index in 0..8u8 {
let block = format!("nb{mode_index}");
let bytes = fixture(usize::from(mode_index));
let mut offset = 6usize;
for f in 0.. {
if !has_row(&block, &format!("meta{f}")) {
break;
}
let meta = row(&block, &format!("meta{f}"));
let want_mode = meta[0];
let want_bits = usize::try_from(meta[1]).expect("bit count");
let want_params = usize::try_from(meta[2]).expect("parameter count");
assert_eq!(want_mode, i64::from(mode_index), "{block}: mode");
let toc = bytes[offset];
assert_eq!(
i64::from((toc >> 3) & 0x0f),
want_mode,
"{block} frame {f}: ToC mode"
);
let payload_len = want_bits.div_ceil(8);
let payload = &bytes[offset + 1..offset + 1 + payload_len];
let bits = unpack(mode_index, payload).expect("unpacks");
assert_eq!(bits.len(), want_bits, "{block} frame {f}: bit count");
assert_eq!(
bits_to_hex(&bits),
row_str(&block, &format!("bits{f}")),
"{block} frame {f}: unsorted codec bits"
);
let got = read_parameters(mode_index, &bits).expect("parameters");
let want = row(&block, &format!("prm{f}"));
assert_eq!(got.len(), want_params, "{block} frame {f}: parameter count");
assert_eq!(got.len(), want.len(), "{block} frame {f}: count vs fixture");
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
i64::from(g),
w,
"{block} frame {f}: parameter {i} = {g} but the reference gives {w}"
);
}
offset += 1 + payload_len;
checked += 1;
}
}
assert!(checked >= 16, "only {checked} frames checked");
}
#[test]
fn every_layout_consumes_its_frame_exactly() {
for mode_index in 0..8u8 {
let widths: usize = parameter_widths(mode_index).iter().sum();
let sorted = sort_table(mode_index).len();
assert_eq!(
widths, sorted,
"mode {mode_index}: widths sum to {widths} but the sort table has {sorted}"
);
}
}
#[test]
fn every_sort_table_is_a_permutation() {
for mode_index in 0..8u8 {
let sort = sort_table(mode_index);
let mut seen = vec![false; sort.len()];
for &target in sort {
let t = target as usize;
assert!(t < seen.len(), "mode {mode_index}: index {t} out of range");
assert!(!seen[t], "mode {mode_index}: index {t} appears twice");
seen[t] = true;
}
}
}
#[test]
fn the_parameter_count_grows_with_the_bit_rate() {
let counts: Vec<usize> = (0..8u8).map(|m| parameter_widths(m).len()).collect();
assert!(
counts[7] > counts[0],
"12.2 carries {} parameters against 4.75's {}",
counts[7],
counts[0]
);
}
#[test]
fn a_truncated_payload_is_rejected() {
let full = vec![0u8; 31];
assert!(unpack(7, &full).is_some());
assert!(unpack(7, &full[..30]).is_none());
}
}