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),
#[error("reserved channelConfiguration: {0}")]
ReservedChannelConfig(u8),
#[error("channelConfiguration 0 is unsupported for audioObjectType {0}")]
ProgramConfigUnsupported(u8),
#[error("channelConfiguration 0 without a program config element leading the first raw data block")]
ProgramConfigMissing,
#[error("program config element truncated")]
ProgramConfigTruncated,
#[error("program config element declares no channels")]
ProgramConfigEmpty,
}
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> {
let mut reader = BitReader::new(buf);
let (object_type, sample_rate, channel_config) = read_header(&mut reader)?;
let channel_count = match channel_config {
0 => {
let core = read_core(&mut reader, object_type)?;
read_general_audio(&mut reader, core)?;
program_config(&mut reader)?
}
_ => 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])
}
}
}
fn read_header<T: Buf>(reader: &mut BitReader<T>) -> Result<(u8, u32, u8)> {
if reader.buf.remaining() < 2 {
return Err(Error::ConfigTooShort);
}
let object_type = read_object_type(reader)?;
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;
Ok((object_type, sample_rate, channel_config))
}
fn read_core<T: Buf>(reader: &mut BitReader<T>, object_type: u8) -> Result<u8> {
if !matches!(object_type, 5 | 29) {
return Ok(object_type);
}
if reader.read(4, Error::IncompleteConfig)? == 15 {
reader.read(24, Error::IncompleteConfig)?;
}
let core = read_object_type(reader)?;
if core == 22 {
reader.read(4, Error::IncompleteConfig)?;
}
Ok(core)
}
fn read_general_audio<T: Buf>(reader: &mut BitReader<T>, core: u8) -> Result<()> {
if !GENERAL_AUDIO.contains(&core) {
return Err(Error::ProgramConfigUnsupported(core));
}
reader.read(1, Error::IncompleteConfig)?;
if reader.read(1, Error::IncompleteConfig)? == 1 {
reader.read(14, Error::IncompleteConfig)?;
}
reader.read(1, Error::IncompleteConfig)?;
Ok(())
}
pub(crate) fn in_band_config(profile: u8, sample_rate: u32, channel_config: u8, block: &mut &[u8]) -> Result<Bytes> {
let mut out = BitWriter::default();
out.write(5, u32::from(profile & 0x1F));
match SAMPLE_RATES.iter().position(|&rate| rate == sample_rate) {
Some(index) => out.write(4, index as u32),
None => {
out.write(4, 15);
out.write(24, sample_rate);
}
}
out.write(4, u32::from(channel_config));
if channel_config == 0 {
out.write(3, 0);
let mut reader = BitReader::new(block);
if reader.read(3, Error::ProgramConfigMissing)? != ID_PCE {
return Err(Error::ProgramConfigMissing);
}
reader.record = Some(out);
program_config(&mut reader)?;
out = reader.record.take().expect("recording set above");
}
Ok(Bytes::from(out.bytes))
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct InBand {
pub object_type: u8,
pub sample_rate: u32,
pub channel_config: u8,
pub program_config: Option<Bytes>,
}
pub(crate) fn in_band(asc: &[u8]) -> Result<InBand> {
let mut asc = asc;
let mut reader = BitReader::new(&mut asc);
let (object_type, sample_rate, channel_config) = read_header(&mut reader)?;
let core = read_core(&mut reader, object_type)?;
let program_config = match channel_config {
0 => {
read_general_audio(&mut reader, core)?;
let mut out = BitWriter::default();
out.write(3, ID_PCE);
reader.record = Some(out);
program_config(&mut reader)?;
Some(Bytes::from(reader.record.take().expect("recording set above").bytes))
}
_ => None,
};
Ok(InBand {
object_type: core,
sample_rate,
channel_config,
program_config,
})
}
const ID_PCE: u32 = 5;
const GENERAL_AUDIO: [u8; 12] = [1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23];
fn read_object_type<T: Buf>(reader: &mut BitReader<T>) -> Result<u8> {
let object_type = reader.read(5, Error::ConfigTooShort)? as u8;
if object_type == 31 {
return Ok(32 + reader.read(6, Error::ExtendedConfigTooShort)? as u8);
}
Ok(object_type)
}
fn program_config<T: Buf>(reader: &mut BitReader<T>) -> Result<u32> {
let mut read = |n| reader.read(n, Error::ProgramConfigTruncated);
read(10)?;
let (front, side, back) = (read(4)?, read(4)?, read(4)?);
let (lfe, assoc, cc) = (read(2)?, read(3)?, read(4)?);
for bits in [4, 4, 3] {
if read(1)? == 1 {
read(bits)?;
}
}
let mut channels = lfe;
for _ in 0..front + side + back {
channels += 1 + read(1)?;
read(4)?;
}
for _ in 0..lfe + assoc {
read(4)?;
}
for _ in 0..cc {
read(5)?;
}
reader.align();
let comment = reader.read(8, Error::ProgramConfigTruncated)?;
for _ in 0..comment {
reader.read(8, Error::ProgramConfigTruncated)?;
}
if channels == 0 {
return Err(Error::ProgramConfigEmpty);
}
Ok(channels)
}
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,
record: Option<BitWriter>,
}
impl<'a, T: Buf> BitReader<'a, T> {
fn new(buf: &'a mut T) -> Self {
Self {
buf,
current: 0,
bits_left: 0,
record: None,
}
}
fn align(&mut self) {
self.bits_left = 0;
if let Some(record) = &mut self.record {
record.align();
}
}
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);
}
if let Some(record) = &mut self.record {
record.write(n, value);
}
Ok(value)
}
}
#[derive(Default)]
struct BitWriter {
bytes: Vec<u8>,
used: u8,
}
impl BitWriter {
fn write(&mut self, n: u8, value: u32) {
for i in (0..n).rev() {
if self.used == 0 {
self.bytes.push(0);
}
let bit = ((value >> i) & 1) as u8;
*self.bytes.last_mut().expect("pushed above") |= bit << (7 - self.used);
self.used = (self.used + 1) % 8;
}
}
fn align(&mut self) {
self.used = 0;
}
}
fn channel_count_from_config(channel_config: u8) -> Result<u32> {
match channel_config {
1..=6 => Ok(channel_config as u32),
7 | 12 | 14 => Ok(8),
11 => Ok(7),
13 => Ok(24),
_ => Err(Error::ReservedChannelConfig(channel_config)),
}
}
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");
}
const FFMPEG_QUAD_ASC: [u8; 24] = [
0x11, 0x80, 0x04, 0xC4, 0x04, 0x00, 0x21, 0x10, 0x0C, 0x4C, 0x61, 0x76, 0x63, 0x36, 0x33, 0x2E, 0x31, 0x2E,
0x31, 0x30, 0x31, 0x56, 0xE5, 0x00,
];
fn write_pce(out: &mut BitWriter, front: &[bool], side: &[bool], back: &[bool], lfe: u32) {
out.write(4, 0);
out.write(2, 1);
out.write(4, 3);
out.write(4, front.len() as u32);
out.write(4, side.len() as u32);
out.write(4, back.len() as u32);
out.write(2, lfe);
out.write(3, 0);
out.write(4, 0);
out.write(3, 0);
for (tag, &cpe) in front.iter().chain(side).chain(back).enumerate() {
out.write(1, cpe.into());
out.write(4, tag as u32);
}
for tag in 0..lfe {
out.write(4, tag);
}
out.align();
out.write(8, 1);
out.write(8, b'x'.into());
}
fn pce_asc(object_type: u8, front: &[bool], side: &[bool], back: &[bool], lfe: u32) -> Vec<u8> {
let mut out = BitWriter::default();
out.write(5, object_type.into());
out.write(4, 3);
out.write(4, 0);
out.write(3, 0);
write_pce(&mut out, front, side, back, lfe);
out.bytes
}
#[test]
fn parses_ffmpeg_program_config_element() {
let cfg = Config::parse(&mut FFMPEG_QUAD_ASC.as_slice()).unwrap();
assert_eq!(cfg.profile, 2);
assert_eq!(cfg.sample_rate, 48_000);
assert_eq!(cfg.channel_count, 4, "two channel pair elements");
}
#[test]
fn parses_program_config_element_counts() {
let asc = pce_asc(2, &[false, true], &[], &[true], 1);
assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, 6);
let asc = pce_asc(2, &[false, true], &[true], &[true], 0);
assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, 7);
}
fn sbr_pce_asc() -> Vec<u8> {
let mut out = BitWriter::default();
out.write(5, 5);
out.write(4, 6);
out.write(4, 0);
out.write(4, 3);
out.write(5, 2);
out.write(3, 0);
write_pce(&mut out, &[true], &[], &[true], 0);
out.bytes
}
#[test]
fn parses_program_config_element_behind_explicit_sbr() {
let cfg = Config::parse(&mut sbr_pce_asc().as_slice()).unwrap();
assert_eq!(cfg.profile, 5);
assert_eq!(cfg.channel_count, 4);
}
#[test]
fn refuses_bad_program_config_elements() {
let asc = pce_asc(2, &[], &[], &[], 0);
assert!(matches!(
Config::parse(&mut asc.as_slice()),
Err(Error::ProgramConfigEmpty)
));
let asc = pce_asc(2, &[true], &[], &[], 0);
assert!(matches!(
Config::parse(&mut &asc[..asc.len() - 1]),
Err(Error::ProgramConfigTruncated)
));
let mut out = BitWriter::default();
out.write(5, 31);
out.write(6, 4);
out.write(4, 3);
out.write(4, 0);
out.write(8, 0);
assert!(matches!(
Config::parse(&mut out.bytes.as_slice()),
Err(Error::ProgramConfigUnsupported(36))
));
}
#[test]
fn channel_config_dispositions() {
let expected = [1, 2, 3, 4, 5, 6, 8];
for (config, count) in (1..=7).zip(expected) {
let asc = [0x11, 0x80 | (config << 3)];
assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, count);
}
for (config, count) in [(11, 7), (12, 8), (13, 24), (14, 8)] {
let asc = [0x11, 0x80 | (config << 3)];
assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, count);
}
for config in [8, 9, 10, 15] {
let asc = [0x11, 0x80 | (config << 3)];
assert!(matches!(
Config::parse(&mut asc.as_slice()),
Err(Error::ReservedChannelConfig(c)) if c == config
));
}
}
#[test]
fn in_band_config_moves_the_program_config_element() {
let mut block = BitWriter::default();
block.write(3, ID_PCE);
write_pce(&mut block, &[false, true], &[], &[true], 1);
let pce = block.bytes.clone();
block.write(8, 0xFF);
let mut rest = block.bytes.as_slice();
let asc = in_band_config(2, 48_000, 0, &mut rest).unwrap();
assert_eq!(asc, pce_asc(2, &[false, true], &[], &[true], 1));
assert_eq!(Config::parse(&mut asc.as_ref()).unwrap().channel_count, 6);
assert_eq!(rest, [0xFF], "the element moves out of the block");
let in_band = in_band(&asc).unwrap();
assert_eq!(in_band.channel_config, 0);
assert_eq!(in_band.program_config, Some(Bytes::from(pce)));
}
#[test]
fn in_band_of_ffmpeg_program_config_element() {
let in_band = in_band(&FFMPEG_QUAD_ASC).unwrap();
assert_eq!(in_band.channel_config, 0);
let pce = in_band.program_config.unwrap();
let mut block = pce.as_ref();
let asc = in_band_config(2, 48_000, 0, &mut block).unwrap();
assert!(block.is_empty(), "the element is all that was framed");
assert_eq!(asc, FFMPEG_QUAD_ASC[..FFMPEG_QUAD_ASC.len() - 3]);
}
#[test]
fn in_band_without_a_program_config_element() {
let stereo = in_band(&[0x11, 0x90]).unwrap();
assert_eq!((stereo.channel_config, stereo.program_config), (2, None));
assert_eq!(in_band(&[0x11, 0xE0]).unwrap().channel_config, 12);
}
#[test]
fn in_band_of_explicit_sbr_is_its_lc_core() {
for (asc, channel_config) in [([0x2B, 0x11, 0x88, 0x00], 2), ([0xEB, 0x09, 0x88, 0x00], 1)] {
let expected = InBand {
object_type: 2,
sample_rate: 24_000,
channel_config,
program_config: None,
};
assert_eq!(in_band(&asc).unwrap(), expected);
}
assert!(matches!(in_band(&[0x2A, 0x10]), Err(Error::IncompleteConfig)));
}
#[test]
fn in_band_of_explicit_sbr_keeps_its_program_config_element() {
let in_band = in_band(&sbr_pce_asc()).unwrap();
assert_eq!(in_band.object_type, 2);
assert_eq!(in_band.sample_rate, 24_000);
assert_eq!(in_band.channel_config, 0);
let pce = in_band.program_config.expect("a program config element");
let mut block = pce.as_ref();
let asc = in_band_config(2, 24_000, 0, &mut block).unwrap();
assert_eq!(Config::parse(&mut asc.as_ref()).unwrap().channel_count, 4);
}
#[test]
fn in_band_config_refuses_a_block_without_a_leading_pce() {
assert!(matches!(
in_band_config(2, 48_000, 0, &mut [0x20, 0x00].as_slice()),
Err(Error::ProgramConfigMissing)
));
assert!(matches!(
in_band_config(2, 48_000, 0, &mut [].as_slice()),
Err(Error::ProgramConfigMissing)
));
}
#[test]
fn in_band_config_matches_encode() {
let asc = in_band_config(2, 44_100, 2, &mut [].as_slice()).unwrap();
let encoded = Config {
profile: 2,
sample_rate: 44_100,
channel_count: 2,
}
.encode();
assert_eq!(asc, encoded);
}
#[test]
fn unsupported_channel_count_falls_back_to_stereo_config() {
assert_eq!(channel_config_from_count(9), 2);
}
}