mod import;
pub use import::*;
use anyhow::Context;
use bytes::{Buf, Bytes};
pub struct Config {
pub profile: u8,
pub sample_rate: u32,
pub channel_count: u32,
}
impl Config {
pub fn parse<T: Buf>(buf: &mut T) -> anyhow::Result<Self> {
anyhow::ensure!(buf.remaining() >= 2, "AudioSpecificConfig must be at least 2 bytes");
let b0 = buf.get_u8();
let mut object_type = b0 >> 3;
let freq_index;
let (profile, sample_rate, channel_count) = if object_type == 31 {
anyhow::ensure!(
buf.remaining() >= 2,
"extended audioObjectType requires 2 additional bytes"
);
let b_ext = buf.get_u8();
let audio_object_type_ext = ((b0 & 0x07) << 3) | ((b_ext >> 5) & 0x07);
object_type = 32 + audio_object_type_ext;
freq_index = (b_ext >> 1) & 0x0F;
let channel_config_high = b_ext & 0x01;
anyhow::ensure!(buf.remaining() >= 1, "AudioSpecificConfig incomplete");
let b1 = buf.get_u8();
let channel_config = (channel_config_high << 3) | ((b1 >> 5) & 0x07);
let sample_rate = sample_rate_from_index(freq_index, buf)?;
let channel_count = channel_count_from_config(channel_config);
if buf.remaining() > 0 {
buf.advance(buf.remaining());
}
(object_type, sample_rate, channel_count)
} else {
let mut freq_index_local = (b0 & 0x07) << 1;
anyhow::ensure!(buf.remaining() >= 1, "AudioSpecificConfig incomplete");
let b1 = buf.get_u8();
freq_index_local |= (b1 >> 7) & 0x01;
freq_index = freq_index_local;
let channel_config = (b1 >> 3) & 0x0F;
let sample_rate = sample_rate_from_index(freq_index, buf)?;
let channel_count = channel_count_from_config(channel_config);
if buf.remaining() > 0 {
buf.advance(buf.remaining());
}
(object_type, sample_rate, channel_count)
};
Ok(Self {
profile,
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])
}
}
}
fn sample_rate_from_index<T: Buf>(freq_index: u8, buf: &mut T) -> anyhow::Result<u32> {
const SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
if freq_index == 15 {
anyhow::ensure!(buf.remaining() >= 3, "explicit sample rate requires 3 additional bytes");
let rate_bytes = [buf.get_u8(), buf.get_u8(), buf.get_u8()];
return Ok(((rate_bytes[0] as u32) << 16) | ((rate_bytes[1] as u32) << 8) | (rate_bytes[2] as u32));
}
SAMPLE_RATES
.get(freq_index as usize)
.copied()
.context("unsupported sample rate index")
}
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_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);
}
}