use crate::error::{CodecError, Result};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AmrVariant {
NarrowBand,
WideBand,
}
impl AmrVariant {
#[must_use]
pub const fn speech_mode_count(self) -> u8 {
match self {
Self::NarrowBand => 8,
Self::WideBand => 9,
}
}
#[must_use]
pub const fn sample_rate(self) -> u32 {
match self {
Self::NarrowBand => 8_000,
Self::WideBand => 16_000,
}
}
#[must_use]
pub const fn frame_samples(self) -> usize {
match self {
Self::NarrowBand => 160,
Self::WideBand => 320,
}
}
#[must_use]
pub const fn clock_rate(self) -> u32 {
self.sample_rate()
}
#[must_use]
pub const fn sid_frame_type(self) -> u8 {
match self {
Self::NarrowBand => 8,
Self::WideBand => 9,
}
}
#[must_use]
pub const fn sid_bits(self) -> usize {
match self {
Self::NarrowBand => 39,
Self::WideBand => 40,
}
}
#[must_use]
pub const fn sdp_name(self) -> &'static str {
match self {
Self::NarrowBand => "AMR",
Self::WideBand => "AMR-WB",
}
}
#[must_use]
pub const fn storage_magic(self) -> &'static [u8] {
match self {
Self::NarrowBand => b"#!AMR\n",
Self::WideBand => b"#!AMR-WB\n",
}
}
}
impl fmt::Display for AmrVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.sdp_name())
}
}
const NB_BITS: [usize; 8] = [95, 103, 118, 134, 148, 159, 204, 244];
const NB_CLASS_A: [usize; 8] = [42, 49, 55, 58, 61, 75, 65, 81];
const NB_BITRATE: [u32; 8] = [4_750, 5_150, 5_900, 6_700, 7_400, 7_950, 10_200, 12_200];
const WB_BITS: [usize; 9] = [132, 177, 253, 285, 317, 365, 397, 461, 477];
const WB_CLASS_A: [usize; 9] = [54, 64, 72, 72, 72, 72, 72, 72, 72];
const WB_BITRATE: [u32; 9] = [
6_600, 8_850, 12_650, 14_250, 15_850, 18_250, 19_850, 23_050, 23_850,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AmrMode {
variant: AmrVariant,
index: u8,
}
impl AmrMode {
pub fn new(variant: AmrVariant, index: u8) -> Result<Self> {
if index >= variant.speech_mode_count() {
return Err(CodecError::invalid_config(format!(
"{variant} mode {index} out of range (valid: 0-{})",
variant.speech_mode_count() - 1
)));
}
Ok(Self { variant, index })
}
#[must_use]
pub const fn variant(self) -> AmrVariant {
self.variant
}
#[must_use]
pub const fn index(self) -> u8 {
self.index
}
#[must_use]
pub const fn bits(self) -> usize {
match self.variant {
AmrVariant::NarrowBand => NB_BITS[self.index as usize],
AmrVariant::WideBand => WB_BITS[self.index as usize],
}
}
#[must_use]
pub const fn octet_aligned_bytes(self) -> usize {
self.bits().div_ceil(8)
}
#[must_use]
pub const fn class_a_bits(self) -> usize {
match self.variant {
AmrVariant::NarrowBand => NB_CLASS_A[self.index as usize],
AmrVariant::WideBand => WB_CLASS_A[self.index as usize],
}
}
#[must_use]
pub const fn bitrate(self) -> u32 {
match self.variant {
AmrVariant::NarrowBand => NB_BITRATE[self.index as usize],
AmrVariant::WideBand => WB_BITRATE[self.index as usize],
}
}
#[must_use]
pub fn all(variant: AmrVariant) -> Vec<Self> {
(0..variant.speech_mode_count())
.map(|index| Self { variant, index })
.collect()
}
}
impl fmt::Display for AmrMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kbits = f64::from(self.bitrate()) / 1000.0;
write!(f, "{} {kbits:.2} kbit/s", self.variant)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AmrFrameType {
Speech(AmrMode),
Sid(AmrVariant),
NoData,
SpeechLost,
}
impl AmrFrameType {
#[must_use]
pub const fn frame_type_index(self) -> u8 {
match self {
Self::Speech(mode) => mode.index(),
Self::Sid(variant) => variant.sid_frame_type(),
Self::SpeechLost => 14,
Self::NoData => 15,
}
}
#[must_use]
pub const fn bits(self) -> usize {
match self {
Self::Speech(mode) => mode.bits(),
Self::Sid(variant) => variant.sid_bits(),
Self::NoData | Self::SpeechLost => 0,
}
}
#[must_use]
pub const fn octet_aligned_bytes(self) -> usize {
self.bits().div_ceil(8)
}
pub fn from_index(variant: AmrVariant, index: u8) -> Result<Self> {
if index < variant.speech_mode_count() {
return Ok(Self::Speech(AmrMode { variant, index }));
}
if index == variant.sid_frame_type() {
return Ok(Self::Sid(variant));
}
if index == 15 {
return Ok(Self::NoData);
}
if index == 14 && variant == AmrVariant::WideBand {
return Ok(Self::SpeechLost);
}
Err(CodecError::InvalidPayload {
details: format!("frame type {index} is reserved for {variant}; discard the packet"),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AmrModeSet {
variant: AmrVariant,
mask: u16,
}
impl AmrModeSet {
#[must_use]
pub const fn all(variant: AmrVariant) -> Self {
let mask = (1u16 << variant.speech_mode_count()) - 1;
Self { variant, mask }
}
pub fn from_indices(variant: AmrVariant, modes: &[u8]) -> Result<Self> {
if modes.is_empty() {
return Err(CodecError::invalid_config(
"AMR mode-set must contain at least one mode",
));
}
let mut mask = 0u16;
for &index in modes {
let mode = AmrMode::new(variant, index)?;
mask |= 1u16 << mode.index();
}
Ok(Self { variant, mask })
}
#[must_use]
pub const fn variant(&self) -> AmrVariant {
self.variant
}
#[must_use]
pub const fn contains(&self, mode: AmrMode) -> bool {
match (self.variant, mode.variant()) {
(AmrVariant::NarrowBand, AmrVariant::NarrowBand)
| (AmrVariant::WideBand, AmrVariant::WideBand) => {
self.mask & (1u16 << mode.index()) != 0
}
_ => false,
}
}
#[must_use]
pub fn modes(&self) -> Vec<AmrMode> {
AmrMode::all(self.variant)
.into_iter()
.filter(|&mode| self.contains(mode))
.collect()
}
#[must_use]
pub fn highest(&self) -> Option<AmrMode> {
self.modes().last().copied()
}
#[must_use]
pub fn lowest(&self) -> Option<AmrMode> {
self.modes().first().copied()
}
pub fn intersect(&self, other: &Self) -> Result<Self> {
if self.variant != other.variant {
return Err(CodecError::invalid_config(
"cannot intersect AMR mode sets of different variants",
));
}
let mask = self.mask & other.mask;
if mask == 0 {
return Err(CodecError::invalid_config(
"AMR mode-set intersection is empty; the payload type must be rejected",
));
}
Ok(Self {
variant: self.variant,
mask,
})
}
#[must_use]
pub fn is_superset_of(&self, other: &Self) -> bool {
self.variant == other.variant && self.mask & other.mask == other.mask
}
#[must_use]
pub fn to_sdp_value(&self) -> String {
self.modes()
.iter()
.map(|mode| mode.index().to_string())
.collect::<Vec<_>>()
.join(",")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nb_frame_sizes_match_rfc4867() {
let expected = [
(0, 4_750, 95, 42, 12),
(1, 5_150, 103, 49, 13),
(2, 5_900, 118, 55, 15),
(3, 6_700, 134, 58, 17),
(4, 7_400, 148, 61, 19),
(5, 7_950, 159, 75, 20),
(6, 10_200, 204, 65, 26),
(7, 12_200, 244, 81, 31),
];
for (index, bitrate, bits, class_a, bytes) in expected {
let mode = AmrMode::new(AmrVariant::NarrowBand, index).unwrap();
assert_eq!(mode.bitrate(), bitrate, "mode {index} bitrate");
assert_eq!(mode.bits(), bits, "mode {index} bits");
assert_eq!(mode.class_a_bits(), class_a, "mode {index} class A");
assert_eq!(mode.octet_aligned_bytes(), bytes, "mode {index} bytes");
}
}
#[test]
fn wb_frame_sizes_match_rfc4867() {
let expected = [
(0, 6_600, 132, 54, 17),
(1, 8_850, 177, 64, 23),
(2, 12_650, 253, 72, 32),
(3, 14_250, 285, 72, 36),
(4, 15_850, 317, 72, 40),
(5, 18_250, 365, 72, 46),
(6, 19_850, 397, 72, 50),
(7, 23_050, 461, 72, 58),
(8, 23_850, 477, 72, 60),
];
for (index, bitrate, bits, class_a, bytes) in expected {
let mode = AmrMode::new(AmrVariant::WideBand, index).unwrap();
assert_eq!(mode.bitrate(), bitrate, "mode {index} bitrate");
assert_eq!(mode.bits(), bits, "mode {index} bits");
assert_eq!(mode.class_a_bits(), class_a, "mode {index} class A");
assert_eq!(mode.octet_aligned_bytes(), bytes, "mode {index} bytes");
}
}
#[test]
fn class_a_bits_never_exceed_the_frame() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
for mode in AmrMode::all(variant) {
assert!(
mode.class_a_bits() <= mode.bits(),
"{mode}: {} class A bits in a {}-bit frame",
mode.class_a_bits(),
mode.bits()
);
}
}
}
#[test]
fn wb_mode_8_adds_exactly_one_high_band_gain_per_subframe() {
let m7 = AmrMode::new(AmrVariant::WideBand, 7).unwrap();
let m8 = AmrMode::new(AmrVariant::WideBand, 8).unwrap();
assert_eq!(m8.bits() - m7.bits(), 16);
}
#[test]
fn frame_bits_are_consistent_with_bitrate() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
for mode in AmrMode::all(variant) {
let expected = (mode.bitrate() as usize) / 50;
assert_eq!(
mode.bits(),
expected,
"{mode} carries {} bits but its rate implies {expected}",
mode.bits()
);
}
}
}
#[test]
fn mode_index_is_range_checked_per_variant() {
assert!(AmrMode::new(AmrVariant::NarrowBand, 7).is_ok());
assert!(AmrMode::new(AmrVariant::NarrowBand, 8).is_err());
assert!(AmrMode::new(AmrVariant::WideBand, 8).is_ok());
assert!(AmrMode::new(AmrVariant::WideBand, 9).is_err());
}
#[test]
fn frame_type_indices_follow_rfc4867() {
let nb = AmrVariant::NarrowBand;
let wb = AmrVariant::WideBand;
assert_eq!(AmrFrameType::Sid(nb).frame_type_index(), 8);
assert_eq!(AmrFrameType::Sid(wb).frame_type_index(), 9);
assert_eq!(AmrFrameType::NoData.frame_type_index(), 15);
assert_eq!(AmrFrameType::SpeechLost.frame_type_index(), 14);
assert_eq!(AmrFrameType::Sid(nb).bits(), 39);
assert_eq!(AmrFrameType::Sid(wb).bits(), 40);
assert_eq!(AmrFrameType::NoData.bits(), 0);
}
#[test]
fn reserved_frame_types_are_rejected_per_variant() {
for index in 9..=14u8 {
assert!(
AmrFrameType::from_index(AmrVariant::NarrowBand, index).is_err(),
"NB FT {index} should be reserved"
);
}
for index in 10..=13u8 {
assert!(
AmrFrameType::from_index(AmrVariant::WideBand, index).is_err(),
"WB FT {index} should be reserved"
);
}
assert_eq!(
AmrFrameType::from_index(AmrVariant::WideBand, 14).unwrap(),
AmrFrameType::SpeechLost
);
assert!(AmrFrameType::from_index(AmrVariant::NarrowBand, 14).is_err());
}
#[test]
fn frame_type_round_trips_through_its_index() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
let mut types = vec![AmrFrameType::Sid(variant), AmrFrameType::NoData];
types.extend(AmrMode::all(variant).into_iter().map(AmrFrameType::Speech));
if variant == AmrVariant::WideBand {
types.push(AmrFrameType::SpeechLost);
}
for frame_type in types {
let index = frame_type.frame_type_index();
assert_eq!(
AmrFrameType::from_index(variant, index).unwrap(),
frame_type,
"{variant} FT {index} did not round-trip"
);
}
}
}
#[test]
fn default_mode_set_contains_every_mode() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
let set = AmrModeSet::all(variant);
assert_eq!(set.modes().len(), variant.speech_mode_count() as usize);
for mode in AmrMode::all(variant) {
assert!(set.contains(mode));
}
}
}
#[test]
fn mode_set_never_matches_the_other_variant() {
let nb_set = AmrModeSet::all(AmrVariant::NarrowBand);
let wb_mode_0 = AmrMode::new(AmrVariant::WideBand, 0).unwrap();
assert!(!nb_set.contains(wb_mode_0));
}
#[test]
fn superset_test_is_what_offer_answer_needs() {
let variant = AmrVariant::WideBand;
let local = AmrModeSet::from_indices(variant, &[0, 1, 2, 3, 4]).unwrap();
assert!(local.is_superset_of(&AmrModeSet::from_indices(variant, &[0, 2]).unwrap()));
assert!(local.is_superset_of(&local));
assert!(!local.is_superset_of(&AmrModeSet::from_indices(variant, &[2, 8]).unwrap()));
assert!(!local.is_superset_of(&AmrModeSet::all(AmrVariant::NarrowBand)));
assert!(AmrModeSet::all(variant).is_superset_of(&local));
}
#[test]
fn mode_set_intersection_is_a_plain_set_operation() {
let variant = AmrVariant::WideBand;
let offer = AmrModeSet::from_indices(variant, &[0, 1, 2, 3]).unwrap();
let answer = AmrModeSet::from_indices(variant, &[2, 3, 4, 5]).unwrap();
let negotiated = offer.intersect(&answer).unwrap();
assert_eq!(negotiated.to_sdp_value(), "2,3");
assert_eq!(negotiated.highest().unwrap().index(), 3);
assert_eq!(negotiated.lowest().unwrap().index(), 2);
let disjoint = AmrModeSet::from_indices(variant, &[6, 7, 8]).unwrap();
assert!(offer.intersect(&disjoint).is_err());
let nb_set = AmrModeSet::all(AmrVariant::NarrowBand);
assert!(offer.intersect(&nb_set).is_err());
}
#[test]
fn empty_mode_set_is_rejected() {
assert!(AmrModeSet::from_indices(AmrVariant::NarrowBand, &[]).is_err());
assert!(AmrModeSet::from_indices(AmrVariant::NarrowBand, &[0, 9]).is_err());
}
#[test]
fn variant_constants_match_rfc4867() {
let nb = AmrVariant::NarrowBand;
assert_eq!(nb.sample_rate(), 8_000);
assert_eq!(nb.clock_rate(), 8_000);
assert_eq!(nb.frame_samples(), 160);
assert_eq!(nb.sdp_name(), "AMR");
assert_eq!(nb.storage_magic(), b"#!AMR\n");
let wb = AmrVariant::WideBand;
assert_eq!(wb.sample_rate(), 16_000);
assert_eq!(wb.clock_rate(), 16_000);
assert_eq!(wb.frame_samples(), 320);
assert_eq!(wb.sdp_name(), "AMR-WB");
assert_eq!(wb.storage_magic(), b"#!AMR-WB\n");
for variant in [nb, wb] {
assert_eq!(variant.frame_samples() * 50, variant.sample_rate() as usize);
}
}
}