use anyhow::Context;
use buf_list::BufList;
use bytes::Buf;
const MAX_GROUP_DURATION: hang::container::Timestamp = hang::container::Timestamp::from_millis_unchecked(100);
pub struct AacConfig {
pub profile: u8,
pub sample_rate: u32,
pub channel_count: u32,
}
impl AacConfig {
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 mut 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 {
freq_index = (b0 & 0x07) << 1;
anyhow::ensure!(buf.remaining() >= 1, "AudioSpecificConfig incomplete");
let b1 = buf.get_u8();
freq_index |= (b1 >> 7) & 0x01;
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 struct Aac {
catalog: crate::CatalogProducer,
track: hang::container::OrderedProducer,
zero: Option<tokio::time::Instant>,
}
impl Aac {
pub fn new(
mut broadcast: moq_lite::BroadcastProducer,
mut catalog: crate::CatalogProducer,
config: AacConfig,
) -> anyhow::Result<Self> {
let track = broadcast.unique_track(".aac")?;
let frame_duration_us = 1024u64 * 1_000_000 / config.sample_rate as u64;
let jitter = moq_lite::Time::from_micros(frame_duration_us).ok();
let audio_config = hang::catalog::AudioConfig {
codec: hang::catalog::AAC {
profile: config.profile,
}
.into(),
sample_rate: config.sample_rate,
channel_count: config.channel_count,
bitrate: None,
description: None,
container: hang::catalog::Container::Legacy,
jitter,
};
catalog.lock().audio.insert(&track.info.name, audio_config.clone())?;
tracing::debug!(name = ?track.info.name, config = ?audio_config, "starting track");
Ok(Self {
catalog,
track: hang::container::OrderedProducer::new(track).with_max_group_duration(MAX_GROUP_DURATION),
zero: None,
})
}
pub fn track(&self) -> &moq_lite::TrackProducer {
&self.track
}
pub fn finish(&mut self) -> anyhow::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn decode<T: Buf>(&mut self, buf: &mut T, pts: Option<hang::container::Timestamp>) -> anyhow::Result<()> {
let pts = self.pts(pts)?;
let mut payload = BufList::new();
while !buf.chunk().is_empty() {
payload.push_chunk(buf.copy_to_bytes(buf.chunk().len()));
}
let frame = hang::container::Frame {
timestamp: pts,
payload,
};
self.track.write(frame)?;
Ok(())
}
fn pts(&mut self, hint: Option<hang::container::Timestamp>) -> anyhow::Result<hang::container::Timestamp> {
if let Some(pts) = hint {
return Ok(pts);
}
let zero = self.zero.get_or_insert_with(tokio::time::Instant::now);
Ok(hang::container::Timestamp::from_micros(
zero.elapsed().as_micros() as u64
)?)
}
}
impl Drop for Aac {
fn drop(&mut self) {
tracing::debug!(name = ?self.track.info.name, "ending track");
self.catalog.lock().audio.remove(&self.track.info.name);
}
}
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 {
if channel_config == 0 {
2
} else if channel_config <= 7 {
channel_config as u32
} else {
tracing::warn!(channel_config, "unsupported channel config, defaulting to stereo");
2
}
}