use super::mode::{AmrFrameType, AmrVariant};
use crate::error::{CodecError, Result};
const fn if2_has_fqi(variant: AmrVariant) -> bool {
matches!(variant, AmrVariant::WideBand)
}
const fn if2_header_bits(variant: AmrVariant) -> usize {
if if2_has_fqi(variant) {
5
} else {
4
}
}
const fn if2_core_bits(frame_type: AmrFrameType) -> usize {
match frame_type {
AmrFrameType::Speech(mode) => mode.bits(),
AmrFrameType::Sid(AmrVariant::NarrowBand) => 39,
AmrFrameType::Sid(AmrVariant::WideBand) => 40,
AmrFrameType::NoData | AmrFrameType::SpeechLost => 0,
}
}
#[must_use]
pub const fn if2_frame_len(variant: AmrVariant, frame_type: AmrFrameType) -> usize {
(if2_header_bits(variant) + if2_core_bits(frame_type)).div_ceil(8)
}
const fn if2_frame_type_index(frame_type: AmrFrameType) -> u8 {
match frame_type {
AmrFrameType::Speech(mode) => mode.index(),
AmrFrameType::Sid(AmrVariant::NarrowBand) => 8,
AmrFrameType::Sid(AmrVariant::WideBand) => 9,
AmrFrameType::SpeechLost => 14,
AmrFrameType::NoData => 15,
}
}
fn if2_set_bit(variant: AmrVariant, out: &mut [u8], index: usize, value: bool) {
if !value {
return;
}
let octet = index / 8;
let within = index % 8;
out[octet] |= if if2_has_fqi(variant) {
0x80 >> within
} else {
1 << within
};
}
fn if2_get_bit(variant: AmrVariant, data: &[u8], index: usize) -> bool {
let octet = data[index / 8];
let within = index % 8;
let mask = if if2_has_fqi(variant) {
0x80 >> within
} else {
1 << within
};
octet & mask != 0
}
pub fn if2_pack(
variant: AmrVariant,
frame_type: AmrFrameType,
bits: &[u8],
quality_ok: bool,
) -> Result<Vec<u8>> {
let expected = if2_core_bits(frame_type);
if bits.len() != expected {
return Err(CodecError::invalid_format(format!(
"IF2 {variant:?} frame needs {expected} core bits, got {}",
bits.len()
)));
}
let mut out = vec![0u8; if2_frame_len(variant, frame_type)];
let index_value = if2_frame_type_index(frame_type);
for bit in 0..4 {
let set = index_value & (0b1000 >> bit) != 0;
if if2_has_fqi(variant) {
if2_set_bit(variant, &mut out, bit, set);
} else {
if set {
out[0] |= 1 << (3 - bit);
}
}
}
let mut position = if2_header_bits(variant);
if if2_has_fqi(variant) {
if2_set_bit(variant, &mut out, 4, quality_ok);
}
for &bit in bits {
if2_set_bit(variant, &mut out, position, bit != 0);
position += 1;
}
Ok(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct If2Frame {
pub frame_type: AmrFrameType,
pub quality_ok: bool,
pub bits: Vec<u8>,
}
pub fn if2_unpack(variant: AmrVariant, data: &[u8]) -> Result<If2Frame> {
let Some(&first) = data.first() else {
return Err(CodecError::invalid_format("IF2 frame is empty"));
};
let index_value = if if2_has_fqi(variant) {
first >> 4
} else {
first & 0x0F
};
let frame_type = AmrFrameType::from_index(variant, index_value)?;
let expected_len = if2_frame_len(variant, frame_type);
if data.len() < expected_len {
return Err(CodecError::invalid_format(format!(
"IF2 {variant:?} frame of type {frame_type:?} needs {expected_len} octets, got {}",
data.len()
)));
}
let quality_ok = !if2_has_fqi(variant) || if2_get_bit(variant, data, 4);
let mut position = if2_header_bits(variant);
let mut bits = Vec::with_capacity(if2_core_bits(frame_type));
for _ in 0..if2_core_bits(frame_type) {
bits.push(u8::from(if2_get_bit(variant, data, position)));
position += 1;
}
Ok(If2Frame {
frame_type,
quality_ok,
bits,
})
}
#[must_use]
pub fn if1_crc(class_a_bits: &[u8]) -> u8 {
let mut remainder = 0u8;
for &bit in class_a_bits {
let feedback = (remainder >> 7) ^ (bit & 1);
remainder <<= 1;
if feedback != 0 {
remainder ^= 0x71;
}
}
remainder
}
const fn if1_class_a_bits(frame_type: AmrFrameType) -> usize {
match frame_type {
AmrFrameType::Speech(mode) => mode.class_a_bits(),
AmrFrameType::Sid(_) => if2_core_bits(frame_type),
AmrFrameType::NoData | AmrFrameType::SpeechLost => 0,
}
}
const fn if1_prefix_bits(variant: AmrVariant, frame_type: AmrFrameType) -> usize {
match frame_type {
AmrFrameType::NoData | AmrFrameType::SpeechLost => {
if if2_has_fqi(variant) {
5
} else {
4
}
}
_ => 24,
}
}
#[must_use]
pub const fn if1_frame_len(variant: AmrVariant, frame_type: AmrFrameType) -> usize {
(if1_prefix_bits(variant, frame_type) + if2_core_bits(frame_type)).div_ceil(8)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct If1Frame {
pub frame_type: AmrFrameType,
pub quality_ok: bool,
pub mode_indication: u8,
pub mode_request: u8,
pub crc_ok: bool,
pub bits: Vec<u8>,
}
fn if1_set_bit(out: &mut [u8], index: usize, value: bool) {
if value {
out[index / 8] |= 0x80 >> (index % 8);
}
}
fn if1_get_bit(data: &[u8], index: usize) -> bool {
data[index / 8] & (0x80 >> (index % 8)) != 0
}
pub fn if1_pack(variant: AmrVariant, frame: &If1Frame) -> Result<Vec<u8>> {
let expected = if2_core_bits(frame.frame_type);
if frame.bits.len() != expected {
return Err(CodecError::invalid_format(format!(
"IF1 {variant:?} frame needs {expected} core bits, got {}",
frame.bits.len()
)));
}
let top_mode = match variant {
AmrVariant::NarrowBand => 7,
AmrVariant::WideBand => 8,
};
if frame.mode_indication > top_mode || frame.mode_request > top_mode {
return Err(CodecError::invalid_format(format!(
"IF1 {variant:?} mode fields are 0..={top_mode}, got indication {} request {}",
frame.mode_indication, frame.mode_request
)));
}
let mut out = vec![0u8; if1_frame_len(variant, frame.frame_type)];
let ft = if2_frame_type_index(frame.frame_type);
for bit in 0..4 {
if1_set_bit(&mut out, bit, ft & (0b1000 >> bit) != 0);
}
match frame.frame_type {
AmrFrameType::NoData | AmrFrameType::SpeechLost => {
if if2_has_fqi(variant) {
if1_set_bit(&mut out, 4, frame.quality_ok);
}
return Ok(out);
}
_ => {}
}
if1_set_bit(&mut out, 4, frame.quality_ok);
match variant {
AmrVariant::NarrowBand => {
for bit in 0..3 {
if1_set_bit(
&mut out,
5 + bit,
frame.mode_indication & (0b100 >> bit) != 0,
);
}
for bit in 0..3 {
if1_set_bit(&mut out, 8 + bit, frame.mode_request & (0b100 >> bit) != 0);
}
}
AmrVariant::WideBand => {
for bit in 0..4 {
if1_set_bit(
&mut out,
8 + bit,
frame.mode_indication & (0b1000 >> bit) != 0,
);
}
for bit in 0..4 {
if1_set_bit(
&mut out,
12 + bit,
frame.mode_request & (0b1000 >> bit) != 0,
);
}
}
}
let class_a = if1_class_a_bits(frame.frame_type);
out[2] = if1_crc(&frame.bits[..class_a]);
for (offset, &bit) in frame.bits.iter().enumerate() {
if1_set_bit(&mut out, 24 + offset, bit != 0);
}
Ok(out)
}
pub fn if1_unpack(variant: AmrVariant, data: &[u8]) -> Result<If1Frame> {
let Some(&first) = data.first() else {
return Err(CodecError::invalid_format("IF1 frame is empty"));
};
let frame_type = AmrFrameType::from_index(variant, first >> 4)?;
let expected_len = if1_frame_len(variant, frame_type);
if data.len() < expected_len {
return Err(CodecError::invalid_format(format!(
"IF1 {variant:?} frame of type {frame_type:?} needs {expected_len} octets, got {}",
data.len()
)));
}
if matches!(frame_type, AmrFrameType::NoData | AmrFrameType::SpeechLost) {
return Ok(If1Frame {
frame_type,
quality_ok: !if2_has_fqi(variant) || if1_get_bit(data, 4),
mode_indication: 0,
mode_request: 0,
crc_ok: true,
bits: Vec::new(),
});
}
let quality_ok = if1_get_bit(data, 4);
let (mode_indication, mode_request) = match variant {
AmrVariant::NarrowBand => (first & 0b0000_0111, data[1] >> 5),
AmrVariant::WideBand => (data[1] >> 4, data[1] & 0x0F),
};
let core = if2_core_bits(frame_type);
let mut bits = Vec::with_capacity(core);
for offset in 0..core {
bits.push(u8::from(if1_get_bit(data, 24 + offset)));
}
let class_a = if1_class_a_bits(frame_type);
let crc_ok = if1_crc(&bits[..class_a]) == data[2];
Ok(If1Frame {
frame_type,
quality_ok,
mode_indication,
mode_request,
crc_ok,
bits,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codecs::amr::mode::AmrMode;
fn bits_of(len: usize) -> Vec<u8> {
(0..len)
.map(|index| u8::from(index % 3 == 0 || index % 7 == 1))
.collect()
}
#[test]
fn frame_lengths_match_the_specification_tables() {
let nb = AmrVariant::NarrowBand;
let nb_octets = [13usize, 14, 16, 18, 19, 21, 26, 31];
for (index, want) in nb_octets.iter().enumerate() {
let mode = AmrMode::new(nb, u8::try_from(index).expect("index")).expect("mode");
assert_eq!(
if2_frame_len(nb, AmrFrameType::Speech(mode)),
*want,
"narrowband mode {index}"
);
}
assert_eq!(
if2_frame_len(nb, AmrFrameType::Sid(nb)),
6,
"narrowband SID"
);
assert_eq!(
if2_frame_len(nb, AmrFrameType::NoData),
1,
"narrowband no-data"
);
let wb = AmrVariant::WideBand;
let wb_octets = [18usize, 23, 33, 37, 41, 47, 51, 59, 61];
for (index, want) in wb_octets.iter().enumerate() {
let mode = AmrMode::new(wb, u8::try_from(index).expect("index")).expect("mode");
assert_eq!(
if2_frame_len(wb, AmrFrameType::Speech(mode)),
*want,
"wideband mode {index}"
);
}
assert_eq!(if2_frame_len(wb, AmrFrameType::Sid(wb)), 6, "wideband SID");
assert_eq!(
if2_frame_len(wb, AmrFrameType::NoData),
1,
"wideband no-data"
);
}
#[test]
fn narrowband_matches_the_worked_example_from_26_101() {
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 3).expect("6.70 kbit/s");
let mut bits = vec![0u8; 134];
bits[0] = 1;
bits[2] = 1;
bits[3] = 1;
bits[4] = 1;
let packed = if2_pack(nb, AmrFrameType::Speech(mode), &bits, true).expect("packs");
assert_eq!(packed.len(), 18, "26.101 A.1b gives 18 octets for 6.70");
assert_eq!(packed[0] & 0x0F, 3, "frame type in the low nibble");
assert_eq!(packed[0] & 0b0001_0000, 0b0001_0000, "d(0) at bit 5");
assert_eq!(packed[0] & 0b0010_0000, 0, "d(1) at bit 6 is zero");
assert_eq!(packed[0] & 0b0100_0000, 0b0100_0000, "d(2) at bit 7");
assert_eq!(packed[0] & 0b1000_0000, 0b1000_0000, "d(3) at bit 8");
assert_eq!(packed[1] & 0b0000_0001, 1, "d(4) at bit 1 of octet 2");
}
#[test]
fn wideband_matches_the_worked_example_from_26_201() {
let wb = AmrVariant::WideBand;
let mode = AmrMode::new(wb, 1).expect("8.85 kbit/s");
let mut bits = vec![0u8; 177];
bits[0] = 1; bits[2] = 1; bits[3] = 1;
let packed = if2_pack(wb, AmrFrameType::Speech(mode), &bits, true).expect("packs");
assert_eq!(packed.len(), 23, "26.201 A.1b gives 23 octets for 8.85");
assert_eq!(packed[0] >> 4, 1, "frame type in the high nibble");
assert_eq!(packed[0] & 0b0000_1000, 0b0000_1000, "FQI at bit 4");
assert_eq!(packed[0] & 0b0000_0100, 0b0000_0100, "d(0) at bit 3");
assert_eq!(packed[0] & 0b0000_0010, 0, "d(1) at bit 2 is zero");
assert_eq!(packed[0] & 0b0000_0001, 1, "d(2) at bit 1");
assert_eq!(
packed[1] & 0b1000_0000,
0b1000_0000,
"d(3) at bit 8 of octet 2"
);
}
#[test]
fn the_quality_indicator_is_wideband_only() {
let wb = AmrVariant::WideBand;
let mode = AmrMode::new(wb, 0).expect("6.60");
let bits = bits_of(132);
for quality in [true, false] {
let packed = if2_pack(wb, AmrFrameType::Speech(mode), &bits, quality).expect("packs");
let frame = if2_unpack(wb, &packed).expect("unpacks");
assert_eq!(frame.quality_ok, quality, "wideband FQI must round-trip");
}
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 0).expect("4.75");
let packed = if2_pack(nb, AmrFrameType::Speech(mode), &bits_of(95), false).expect("packs");
let frame = if2_unpack(nb, &packed).expect("unpacks");
assert!(
frame.quality_ok,
"narrowband has no FQI, so it cannot report a bad frame"
);
}
#[test]
fn every_mode_of_both_variants_round_trips() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
for mode in AmrMode::all(variant) {
let bits = bits_of(mode.bits());
let packed =
if2_pack(variant, AmrFrameType::Speech(mode), &bits, true).expect("packs");
let frame = if2_unpack(variant, &packed).expect("unpacks");
assert_eq!(frame.frame_type, AmrFrameType::Speech(mode));
assert_eq!(frame.bits, bits, "{variant:?} {mode:?} did not survive IF2");
}
let sid = AmrFrameType::Sid(variant);
let packed = if2_pack(variant, sid, &bits_of(if2_core_bits(sid)), true).expect("packs");
assert_eq!(
if2_unpack(variant, &packed).expect("unpacks").frame_type,
sid
);
let packed = if2_pack(variant, AmrFrameType::NoData, &[], true).expect("packs");
assert_eq!(packed.len(), 1, "a no-data frame is its header alone");
assert_eq!(
if2_unpack(variant, &packed).expect("unpacks").frame_type,
AmrFrameType::NoData
);
}
}
#[test]
fn a_wrong_bit_count_is_refused_rather_than_padded() {
let wb = AmrVariant::WideBand;
let mode = AmrMode::new(wb, 8).expect("23.85");
assert!(if2_pack(wb, AmrFrameType::Speech(mode), &[1u8; 10], true).is_err());
assert!(if2_pack(wb, AmrFrameType::Speech(mode), &[1u8; 500], true).is_err());
}
#[test]
fn a_truncated_frame_is_refused_rather_than_read_past_its_end() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
let mode = AmrMode::new(variant, 0).expect("lowest mode");
let packed = if2_pack(
variant,
AmrFrameType::Speech(mode),
&bits_of(mode.bits()),
true,
)
.expect("packs");
for length in 0..packed.len() {
assert!(
if2_unpack(variant, &packed[..length]).is_err(),
"{variant:?}: a {length}-octet prefix must not parse as a whole frame"
);
}
}
}
#[test]
fn the_codec_crc_matches_hand_worked_vectors() {
assert_eq!(if1_crc(&[]), 0x00);
assert_eq!(if1_crc(&[1]), 0x71);
assert_eq!(if1_crc(&[1, 0, 0, 0, 0, 0, 0, 0]), 0xc1);
assert_eq!(if1_crc(&[1; 8]), 0x7e);
let alternating: Vec<u8> = (0..39).map(|i| u8::from(i % 2 == 1)).collect();
assert_eq!(if1_crc(&alternating), 0x9b, "the 39-bit NB SID shape");
assert_eq!(if1_crc(&[1; 54]), 0xe8, "the 54-bit WB 6.60 class A shape");
}
#[test]
fn the_codec_crc_is_not_the_rfc_4867_payload_crc() {
assert_eq!(if1_crc(&[1]), 0x71);
assert_ne!(if1_crc(&[1]), 0xB8);
}
#[test]
fn narrowband_if1_matches_the_worked_example_from_26_101() {
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 3).expect("6.70 kbit/s");
let frame = If1Frame {
frame_type: AmrFrameType::Speech(mode),
quality_ok: true,
mode_indication: 3,
mode_request: 1,
crc_ok: true,
bits: bits_of(134),
};
let packed = if1_pack(nb, &frame).expect("packs");
assert_eq!(packed[0], 0b0011_1011, "octet 1: FT=3, FQI=1, MI=3");
assert_eq!(packed[1], 0b0010_0000, "octet 2: MR=1, five spare zeros");
assert_eq!(
packed[2],
if1_crc(&frame.bits[..mode.class_a_bits()]),
"octet 3 is the CRC over the class A bits"
);
assert_eq!(packed[3] & 0x80 != 0, frame.bits[0] != 0);
assert_eq!(packed.len(), 20);
}
#[test]
fn wideband_if1_matches_the_worked_example_from_26_201() {
let wb = AmrVariant::WideBand;
let mode = AmrMode::new(wb, 2).expect("12.65 kbit/s");
let frame = If1Frame {
frame_type: AmrFrameType::Speech(mode),
quality_ok: true,
mode_indication: 3,
mode_request: 1,
crc_ok: true,
bits: bits_of(253),
};
let packed = if1_pack(wb, &frame).expect("packs");
assert_eq!(packed[0], 0b0010_1000, "octet 1: FT=2, FQI=1, spare");
assert_eq!(packed[1], 0b0011_0001, "octet 2: MI=3, MR=1");
assert_eq!(packed[2], if1_crc(&frame.bits[..mode.class_a_bits()]));
assert_eq!(packed[3] & 0x80 != 0, frame.bits[0] != 0);
assert_eq!(packed.len(), 35);
}
#[test]
fn every_if1_mode_round_trips() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
let top = match variant {
AmrVariant::NarrowBand => 7,
AmrVariant::WideBand => 8,
};
for mode in AmrMode::all(variant) {
let frame = If1Frame {
frame_type: AmrFrameType::Speech(mode),
quality_ok: mode.index() % 2 == 0,
mode_indication: mode.index(),
mode_request: top - mode.index(),
crc_ok: true,
bits: bits_of(mode.bits()),
};
let packed = if1_pack(variant, &frame).expect("packs");
let out = if1_unpack(variant, &packed).expect("unpacks");
assert_eq!(out, frame, "{variant:?} {mode:?} did not survive IF1");
assert!(out.crc_ok, "a locally built frame must verify");
}
}
}
#[test]
fn the_crc_covers_class_a_and_only_class_a() {
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 7).expect("12.2");
let frame = If1Frame {
frame_type: AmrFrameType::Speech(mode),
quality_ok: true,
mode_indication: 7,
mode_request: 7,
crc_ok: true,
bits: bits_of(mode.bits()),
};
let packed = if1_pack(nb, &frame).expect("packs");
let mut corrupted = packed.clone();
corrupted[3] ^= 0x80;
let out = if1_unpack(nb, &corrupted).expect("still parses");
assert!(!out.crc_ok, "a class A flip must fail the CRC");
let mut corrupted = packed;
let last = 24 + mode.bits() - 1;
corrupted[last / 8] ^= 0x80 >> (last % 8);
let out = if1_unpack(nb, &corrupted).expect("parses");
assert!(
out.crc_ok,
"class B is outside the CRC's coverage by specification"
);
}
#[test]
fn no_data_frames_are_header_only() {
let nb_packed = if1_pack(
AmrVariant::NarrowBand,
&If1Frame {
frame_type: AmrFrameType::NoData,
quality_ok: false,
mode_indication: 0,
mode_request: 0,
crc_ok: true,
bits: Vec::new(),
},
)
.expect("packs");
assert_eq!(nb_packed.len(), 1);
assert_eq!(nb_packed[0] >> 4, 15);
let out = if1_unpack(AmrVariant::NarrowBand, &nb_packed).expect("unpacks");
assert!(
out.quality_ok,
"narrowband FT15 has no FQI (26.101 Table 7), so quality cannot be reported"
);
let wb_packed = if1_pack(
AmrVariant::WideBand,
&If1Frame {
frame_type: AmrFrameType::SpeechLost,
quality_ok: false,
mode_indication: 0,
mode_request: 0,
crc_ok: true,
bits: Vec::new(),
},
)
.expect("packs");
assert_eq!(wb_packed.len(), 1);
assert_eq!(wb_packed[0] >> 4, 14);
let out = if1_unpack(AmrVariant::WideBand, &wb_packed).expect("unpacks");
assert!(
!out.quality_ok,
"wideband FT14 carries the FQI and it must survive"
);
}
#[test]
fn if1_refuses_bad_inputs() {
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 0).expect("4.75");
let mut frame = If1Frame {
frame_type: AmrFrameType::Speech(mode),
quality_ok: true,
mode_indication: 8, mode_request: 0,
crc_ok: true,
bits: bits_of(95),
};
assert!(if1_pack(nb, &frame).is_err());
frame.mode_indication = 0;
frame.bits = bits_of(94);
assert!(if1_pack(nb, &frame).is_err());
frame.bits = bits_of(95);
let packed = if1_pack(nb, &frame).expect("packs");
for length in 0..packed.len() {
assert!(if1_unpack(nb, &packed[..length]).is_err());
}
}
#[test]
fn stuffing_bits_are_zero() {
let nb = AmrVariant::NarrowBand;
let mode = AmrMode::new(nb, 0).expect("4.75");
let packed = if2_pack(nb, AmrFrameType::Speech(mode), &[1u8; 95], true).expect("packs");
assert_eq!(packed.len(), 13);
assert_eq!(
packed[12] & 0b1111_1000,
0,
"narrowband stuffing must be zero"
);
let wb = AmrVariant::WideBand;
let mode = AmrMode::new(wb, 1).expect("8.85");
let packed = if2_pack(wb, AmrFrameType::Speech(mode), &[1u8; 177], true).expect("packs");
assert_eq!(packed.len(), 23);
assert_eq!(
packed[22] & 0b0000_0011,
0,
"wideband stuffing must be zero"
);
}
}