use super::mode::{AmrFrameType, AmrVariant};
use super::payload::AmrPayloadFrame;
use crate::error::{CodecError, Result};
const MULTICHANNEL_MAGICS: [&[u8]; 2] = [b"#!AMR_MC1.0\n", b"#!AMR-WB_MC1.0\n"];
pub fn write(variant: AmrVariant, frames: &[AmrPayloadFrame]) -> Result<Vec<u8>> {
let mut out = Vec::from(variant.storage_magic());
for frame in frames {
let expected = frame.frame_type.octet_aligned_bytes();
if frame.data.len() != expected {
return Err(CodecError::InvalidFrameSize {
expected,
actual: frame.data.len(),
});
}
if let Some(frame_variant) = frame_variant(frame.frame_type) {
if frame_variant != variant {
return Err(CodecError::invalid_format(format!(
"cannot store a {frame_variant} frame in a {variant} file"
)));
}
}
out.push(frame.frame_type.frame_type_index() << 3 | u8::from(frame.quality_ok) << 2);
out.extend_from_slice(&frame.data);
}
Ok(out)
}
pub fn read(bytes: &[u8]) -> Result<(AmrVariant, Vec<AmrPayloadFrame>)> {
let mut reader = AmrStorageReader::new(bytes)?;
let variant = reader.variant();
let mut frames = Vec::new();
while let Some(frame) = reader.next_frame()? {
frames.push(frame);
}
Ok((variant, frames))
}
#[derive(Debug)]
pub struct AmrStorageReader<'a> {
variant: AmrVariant,
rest: &'a [u8],
}
impl<'a> AmrStorageReader<'a> {
pub fn new(bytes: &'a [u8]) -> Result<Self> {
for magic in MULTICHANNEL_MAGICS {
if bytes.starts_with(magic) {
return Err(CodecError::invalid_format(format!(
"multi-channel AMR storage ({}) is not supported",
String::from_utf8_lossy(&magic[..magic.len() - 1])
)));
}
}
for variant in [AmrVariant::WideBand, AmrVariant::NarrowBand] {
let magic = variant.storage_magic();
if bytes.starts_with(magic) {
return Ok(Self {
variant,
rest: &bytes[magic.len()..],
});
}
}
Err(CodecError::invalid_format(
"not an AMR storage file: expected a \"#!AMR\\n\" or \"#!AMR-WB\\n\" magic number",
))
}
#[must_use]
pub const fn variant(&self) -> AmrVariant {
self.variant
}
pub fn next_frame(&mut self) -> Result<Option<AmrPayloadFrame>> {
let Some((&header, rest)) = self.rest.split_first() else {
return Ok(None);
};
let ft_index = header >> 3 & 0x0F;
let quality_ok = header >> 2 & 1 == 1;
let frame_type = AmrFrameType::from_index(self.variant, ft_index)?;
let len = frame_type.octet_aligned_bytes();
if rest.len() < len {
return Err(CodecError::InvalidPayload {
details: format!(
"truncated AMR storage record: frame type {ft_index} needs {len} octets, \
{} remain",
rest.len()
),
});
}
let (data, tail) = rest.split_at(len);
self.rest = tail;
Ok(Some(AmrPayloadFrame {
frame_type,
quality_ok,
data: data.to_vec(),
}))
}
}
const fn frame_variant(frame_type: AmrFrameType) -> Option<AmrVariant> {
match frame_type {
AmrFrameType::Speech(mode) => Some(mode.variant()),
AmrFrameType::Sid(variant) => Some(variant),
AmrFrameType::NoData => None,
AmrFrameType::SpeechLost => Some(AmrVariant::WideBand),
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
use crate::codecs::amr::mode::AmrMode;
fn frame_data(bits: usize, seed: u8) -> Vec<u8> {
let len = bits.div_ceil(8);
let mut data: Vec<u8> = (0..len)
.map(|i| {
u8::try_from(i)
.unwrap_or(0)
.wrapping_mul(29)
.wrapping_add(seed)
| 0x21
})
.collect();
let tail = bits % 8;
if tail != 0 {
let last = len - 1;
data[last] &= 0xFFu8 << (8 - tail);
}
data
}
fn speech(variant: AmrVariant, index: u8) -> AmrPayloadFrame {
let mode = AmrMode::new(variant, index).unwrap();
AmrPayloadFrame::new(
AmrFrameType::Speech(mode),
true,
frame_data(mode.bits(), index),
)
.unwrap()
}
#[test]
fn round_trips_every_mode_for_both_variants() {
for variant in [AmrVariant::NarrowBand, AmrVariant::WideBand] {
let mut frames: Vec<_> = AmrMode::all(variant)
.into_iter()
.map(|mode| speech(variant, mode.index()))
.collect();
frames.push(
AmrPayloadFrame::new(
AmrFrameType::Sid(variant),
true,
frame_data(variant.sid_bits(), 1),
)
.unwrap(),
);
frames.push(AmrPayloadFrame::no_data());
if variant == AmrVariant::WideBand {
frames.push(AmrPayloadFrame::speech_lost());
}
let bytes = write(variant, &frames).unwrap();
let (got_variant, got_frames) = read(&bytes).unwrap();
assert_eq!(got_variant, variant);
assert_eq!(got_frames, frames);
}
}
#[test]
fn magic_numbers_match_rfc4867() {
let nb = write(AmrVariant::NarrowBand, &[]).unwrap();
assert_eq!(nb, b"#!AMR\n");
let wb = write(AmrVariant::WideBand, &[]).unwrap();
assert_eq!(wb, b"#!AMR-WB\n");
}
#[test]
fn record_layout_matches_rfc4867() {
let frame = speech(AmrVariant::NarrowBand, 7);
let bytes = write(AmrVariant::NarrowBand, &[frame]).unwrap();
assert_eq!(bytes.len(), b"#!AMR\n".len() + 1 + 31);
assert_eq!(bytes[b"#!AMR\n".len()], 0b0_0111_1_00);
}
#[test]
fn quality_bit_round_trips() {
let mut frame = speech(AmrVariant::WideBand, 4);
frame.quality_ok = false;
let bytes = write(AmrVariant::WideBand, &[frame.clone()]).unwrap();
let (_, frames) = read(&bytes).unwrap();
assert!(!frames[0].quality_ok);
assert_eq!(frames[0], frame);
}
#[test]
fn an_empty_file_is_a_magic_number_and_no_frames() {
let bytes = write(AmrVariant::WideBand, &[]).unwrap();
let (variant, frames) = read(&bytes).unwrap();
assert_eq!(variant, AmrVariant::WideBand);
assert!(frames.is_empty());
}
#[test]
fn the_variants_are_distinguished_by_magic() {
let nb_bytes = write(AmrVariant::NarrowBand, &[speech(AmrVariant::NarrowBand, 0)]).unwrap();
assert_eq!(read(&nb_bytes).unwrap().0, AmrVariant::NarrowBand);
let wb_bytes = write(AmrVariant::WideBand, &[speech(AmrVariant::WideBand, 0)]).unwrap();
assert_eq!(read(&wb_bytes).unwrap().0, AmrVariant::WideBand);
}
#[test]
fn rejects_unknown_and_multichannel_magics() {
assert!(read(b"").is_err());
assert!(read(b"#!AMR").is_err());
assert!(read(b"#!OPUS\n").is_err());
assert!(read(b"not an amr file at all").is_err());
for magic in MULTICHANNEL_MAGICS {
let err = read(magic).unwrap_err();
let text = err.to_string();
assert!(text.contains("multi-channel"), "{text}");
}
}
#[test]
fn rejects_a_truncated_final_record() {
let frame = speech(AmrVariant::WideBand, 8);
let full = write(AmrVariant::WideBand, &[frame]).unwrap();
for cut in b"#!AMR-WB\n".len() + 1..full.len() {
assert!(read(&full[..cut]).is_err(), "{cut}-byte prefix decoded");
}
assert!(read(&full).is_ok());
}
#[test]
fn rejects_reserved_frame_types() {
for ft in 9..=14u8 {
let mut bytes = Vec::from(&b"#!AMR\n"[..]);
bytes.push(ft << 3 | 0b100);
assert!(read(&bytes).is_err(), "NB FT {ft} should be rejected");
}
for ft in 10..=13u8 {
let mut bytes = Vec::from(&b"#!AMR-WB\n"[..]);
bytes.push(ft << 3 | 0b100);
assert!(read(&bytes).is_err(), "WB FT {ft} should be rejected");
}
let mut bytes = Vec::from(&b"#!AMR-WB\n"[..]);
bytes.push(14 << 3 | 0b100);
assert!(read(&bytes).is_ok());
}
#[test]
fn rejects_cross_variant_frames_on_write() {
let wb_frame = speech(AmrVariant::WideBand, 2);
assert!(write(AmrVariant::NarrowBand, &[wb_frame]).is_err());
}
#[test]
fn incremental_reader_matches_the_whole_file_reader() {
let variant = AmrVariant::WideBand;
let frames: Vec<_> = (0..9).map(|i| speech(variant, i)).collect();
let bytes = write(variant, &frames).unwrap();
let mut reader = AmrStorageReader::new(&bytes).unwrap();
assert_eq!(reader.variant(), variant);
let mut streamed = Vec::new();
while let Some(frame) = reader.next_frame().unwrap() {
streamed.push(frame);
}
assert_eq!(streamed, frames);
assert!(reader.next_frame().unwrap().is_none());
}
#[test]
fn storage_and_rtp_frames_share_a_representation() {
use crate::codecs::amr::payload::{AmrPacket, AmrPayloadCodec, AmrPayloadConfig};
let variant = AmrVariant::WideBand;
let frames: Vec<_> = (0..9).map(|i| speech(variant, i)).collect();
let file = write(variant, &frames).unwrap();
let (_, decoded) = read(&file).unwrap();
let codec = AmrPayloadCodec::new(AmrPayloadConfig::bandwidth_efficient(variant)).unwrap();
for frame in decoded {
let packet = AmrPacket::single(frame);
let bytes = codec.pack(&packet).unwrap();
assert_eq!(codec.unpack(&bytes).unwrap(), packet);
}
}
const REFERENCE_FILES: [(&[u8], u8); 9] = [
(include_bytes!("testdata/amrwb_mode0.amr"), 0),
(include_bytes!("testdata/amrwb_mode1.amr"), 1),
(include_bytes!("testdata/amrwb_mode2.amr"), 2),
(include_bytes!("testdata/amrwb_mode3.amr"), 3),
(include_bytes!("testdata/amrwb_mode4.amr"), 4),
(include_bytes!("testdata/amrwb_mode5.amr"), 5),
(include_bytes!("testdata/amrwb_mode6.amr"), 6),
(include_bytes!("testdata/amrwb_mode7.amr"), 7),
(include_bytes!("testdata/amrwb_mode8.amr"), 8),
];
const REFERENCE_NB_FILES: [(&[u8], u8); 8] = [
(include_bytes!("testdata/amrnb_mode0.amr"), 0),
(include_bytes!("testdata/amrnb_mode1.amr"), 1),
(include_bytes!("testdata/amrnb_mode2.amr"), 2),
(include_bytes!("testdata/amrnb_mode3.amr"), 3),
(include_bytes!("testdata/amrnb_mode4.amr"), 4),
(include_bytes!("testdata/amrnb_mode5.amr"), 5),
(include_bytes!("testdata/amrnb_mode6.amr"), 6),
(include_bytes!("testdata/amrnb_mode7.amr"), 7),
];
#[test]
fn reads_reference_narrowband_output_for_every_mode() {
for (bytes, expected_mode) in REFERENCE_NB_FILES {
let (variant, frames) = read(bytes)
.unwrap_or_else(|e| panic!("NB mode {expected_mode} failed to parse: {e}"));
assert_eq!(variant, AmrVariant::NarrowBand, "mode {expected_mode}");
assert_eq!(frames.len(), 25, "NB mode {expected_mode}");
let mode = AmrMode::new(AmrVariant::NarrowBand, expected_mode).unwrap();
let expected_len = AmrVariant::NarrowBand.storage_magic().len()
+ 25 * (1 + mode.octet_aligned_bytes());
assert_eq!(
bytes.len(),
expected_len,
"NB mode {expected_mode} file length"
);
for frame in &frames {
let AmrFrameType::Speech(got) = frame.frame_type else {
panic!("NB mode {expected_mode}: {:?}", frame.frame_type);
};
assert_eq!(got.index(), expected_mode);
}
assert_eq!(
write(variant, &frames).unwrap(),
bytes,
"NB mode {expected_mode}"
);
}
}
#[test]
fn reference_settles_the_narrowband_6_70_and_7_40_frame_sizes() {
let len = |bytes: &[u8]| bytes.len();
let mode3 = len(REFERENCE_NB_FILES[3].0);
let mode4 = len(REFERENCE_NB_FILES[4].0);
let magic = AmrVariant::NarrowBand.storage_magic().len();
assert_eq!(
(mode3 - magic) / 25,
1 + 17,
"6.70 is 134 bits => 17 octets"
);
assert_eq!(
(mode4 - magic) / 25,
1 + 19,
"7.40 is 148 bits => 19 octets"
);
assert!(mode3 < mode4, "6.70 must be the smaller of the two");
}
#[test]
fn reads_reference_encoder_output_for_every_mode() {
for (bytes, expected_mode) in REFERENCE_FILES {
let (variant, frames) =
read(bytes).unwrap_or_else(|e| panic!("mode {expected_mode} failed to parse: {e}"));
assert_eq!(variant, AmrVariant::WideBand, "mode {expected_mode}");
assert_eq!(frames.len(), 25, "mode {expected_mode}: frame count");
for (index, frame) in frames.iter().enumerate() {
let AmrFrameType::Speech(mode) = frame.frame_type else {
panic!("mode {expected_mode} frame {index}: {:?}", frame.frame_type);
};
assert_eq!(mode.index(), expected_mode, "frame {index}");
assert!(frame.quality_ok, "mode {expected_mode} frame {index}");
assert_eq!(frame.data.len(), mode.octet_aligned_bytes());
}
}
}
#[test]
fn our_frame_sizes_match_the_reference_encoder() {
for (bytes, expected_mode) in REFERENCE_FILES {
let mode = AmrMode::new(AmrVariant::WideBand, expected_mode).unwrap();
let expected_len =
AmrVariant::WideBand.storage_magic().len() + 25 * (1 + mode.octet_aligned_bytes());
assert_eq!(
bytes.len(),
expected_len,
"mode {expected_mode}: {} bits => {} octets",
mode.bits(),
mode.octet_aligned_bytes()
);
}
}
#[test]
fn reference_frames_repack_to_the_original_file() {
for (bytes, expected_mode) in REFERENCE_FILES {
let (variant, frames) = read(bytes).unwrap();
let rewritten = write(variant, &frames).unwrap();
assert_eq!(rewritten, bytes, "mode {expected_mode} did not round-trip");
}
}
#[test]
fn reference_frames_survive_the_rtp_payload_format() {
use crate::codecs::amr::payload::{AmrPacket, AmrPayloadCodec, AmrPayloadConfig};
for (bytes, expected_mode) in REFERENCE_FILES {
let (variant, frames) = read(bytes).unwrap();
for config in [
AmrPayloadConfig::bandwidth_efficient(variant),
AmrPayloadConfig::octet_aligned(variant),
] {
let codec = AmrPayloadCodec::new(config).unwrap();
for frame in &frames {
let packet = AmrPacket::single(frame.clone());
let wire = codec.pack(&packet).unwrap();
assert_eq!(codec.unpack(&wire).unwrap(), packet, "mode {expected_mode}");
}
let packet = AmrPacket {
cmr: None,
interleaving: None,
frames: frames.clone(),
};
let wire = codec.pack(&packet).unwrap();
assert_eq!(
codec.unpack(&wire).unwrap(),
packet,
"mode {expected_mode} bulk"
);
}
}
}
#[test]
fn never_panics_on_arbitrary_input() {
for byte in 0u16..=u16::from(u8::MAX) {
let b = u8::try_from(byte).unwrap_or(0);
for len in 0..6usize {
let mut bytes = Vec::from(&b"#!AMR-WB\n"[..]);
bytes.extend(std::iter::repeat_n(b, len));
let _ = read(&bytes);
let mut bytes = Vec::from(&b"#!AMR\n"[..]);
bytes.extend(std::iter::repeat_n(b, len));
let _ = read(&bytes);
}
}
}
}