mod import;
pub use import::*;
use bytes::{Buf, Bytes};
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("AudioSpecificConfig must be at least 2 bytes")]
ConfigTooShort,
#[error("extended audioObjectType requires 2 additional bytes")]
ExtendedConfigTooShort,
#[error("AudioSpecificConfig incomplete")]
IncompleteConfig,
#[error("explicit sample rate requires 3 additional bytes")]
ExplicitSampleRateTooShort,
#[error("unsupported sample rate index: {0}")]
UnsupportedSampleRateIndex(u8),
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Config {
pub profile: u8,
pub sample_rate: u32,
pub channel_count: u32,
}
impl Config {
pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
if buf.remaining() < 2 {
return Err(Error::ConfigTooShort);
}
let mut reader = BitReader::new(buf);
let mut object_type = reader.read(5, Error::ConfigTooShort)? as u8;
if object_type == 31 {
object_type = 32 + reader.read(6, Error::ExtendedConfigTooShort)? as u8;
}
let freq_index = reader.read(4, Error::IncompleteConfig)? as u8;
let sample_rate = if freq_index == 15 {
reader.read(24, Error::ExplicitSampleRateTooShort)?
} else {
*SAMPLE_RATES
.get(freq_index as usize)
.ok_or(Error::UnsupportedSampleRateIndex(freq_index))?
};
let channel_config = reader.read(4, Error::IncompleteConfig)? as u8;
let channel_count = channel_count_from_config(channel_config);
if buf.remaining() > 0 {
buf.advance(buf.remaining());
}
Ok(Self {
profile: object_type,
sample_rate,
channel_count,
})
}
pub fn encode(&self) -> Bytes {
let profile = self.profile & 0x1F;
let freq_index: u8 = match self.sample_rate {
96000 => 0,
88200 => 1,
64000 => 2,
48000 => 3,
44100 => 4,
32000 => 5,
24000 => 6,
22050 => 7,
16000 => 8,
12000 => 9,
11025 => 10,
8000 => 11,
7350 => 12,
_ => 0xF, };
let channel_config = channel_config_from_count(self.channel_count) as u64;
if freq_index != 0xF {
let b0 = (profile << 3) | (freq_index >> 1);
let b1 = ((freq_index & 1) << 7) | ((channel_config as u8 & 0x0F) << 3);
Bytes::from(vec![b0, b1])
} else {
let mut bits: u64 = 0;
bits |= (profile as u64) << 35;
bits |= 0xF_u64 << 31;
bits |= (self.sample_rate as u64) << 7;
bits |= (channel_config & 0xF) << 3;
let all = bits.to_be_bytes();
Bytes::copy_from_slice(&all[3..8])
}
}
}
const SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
struct BitReader<'a, T: Buf> {
buf: &'a mut T,
current: u8,
bits_left: u8,
}
impl<'a, T: Buf> BitReader<'a, T> {
fn new(buf: &'a mut T) -> Self {
Self {
buf,
current: 0,
bits_left: 0,
}
}
fn read(&mut self, n: u8, short: Error) -> Result<u32> {
let mut value = 0u32;
for _ in 0..n {
if self.bits_left == 0 {
if !self.buf.has_remaining() {
return Err(short);
}
self.current = self.buf.get_u8();
self.bits_left = 8;
}
self.bits_left -= 1;
value = (value << 1) | u32::from((self.current >> self.bits_left) & 1);
}
Ok(value)
}
}
fn channel_count_from_config(channel_config: u8) -> u32 {
match channel_config {
1..=6 => channel_config as u32,
7 => 8,
0 => {
tracing::warn!("channel_config=0 (program config element) unsupported, defaulting to stereo");
2
}
_ => {
tracing::warn!(channel_config, "unsupported channel config, defaulting to stereo");
2
}
}
}
fn channel_config_from_count(channel_count: u32) -> u8 {
match channel_count {
1..=6 => channel_count as u8,
8 => 7,
_ => {
tracing::warn!(channel_count, "unsupported channel count, defaulting to stereo");
2
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_standard_2_byte_config() {
let buf = vec![0x12, 0x10];
let cfg = Config::parse(&mut buf.as_slice()).unwrap();
assert_eq!(cfg.profile, 2);
assert_eq!(cfg.sample_rate, 44100);
assert_eq!(cfg.channel_count, 2);
}
#[test]
fn round_trip_explicit_sample_rate() {
let cfg = Config {
profile: 2,
sample_rate: 44_056, channel_count: 2,
};
let encoded = cfg.encode();
assert_eq!(encoded.len(), 5, "explicit-rate config is 5 bytes");
let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
assert_eq!(parsed.profile, 2);
assert_eq!(parsed.sample_rate, 44_056);
assert_eq!(parsed.channel_count, 2);
}
#[test]
fn parses_extended_object_type() {
let buf: [u8; 3] = [0xF8, 0x86, 0x40];
let cfg = Config::parse(&mut buf.as_slice()).unwrap();
assert_eq!(cfg.profile, 36);
assert_eq!(cfg.sample_rate, 48_000);
assert_eq!(cfg.channel_count, 2);
}
#[test]
fn round_trip_5_1_channels() {
let cfg = Config {
profile: 2,
sample_rate: 48000,
channel_count: 6,
};
let encoded = cfg.encode();
let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
assert_eq!(parsed.channel_count, 6);
}
#[test]
fn round_trip_7_1_channels() {
let cfg = Config {
profile: 2,
sample_rate: 48000,
channel_count: 8,
};
let encoded = cfg.encode();
let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
assert_eq!(parsed.channel_count, 8, "7.1 surround should round-trip as 8 channels");
}
#[test]
fn channel_config_zero_falls_back_to_stereo() {
assert_eq!(channel_count_from_config(0), 2);
}
#[test]
fn unsupported_channel_count_falls_back_to_stereo_config() {
assert_eq!(channel_config_from_count(9), 2);
}
}