Skip to main content

moq_mux/codec/aac/
mod.rs

1//! AAC.
2//!
3//! ISO 14496-3 AudioSpecificConfig parse and encode lives in [`Config`].
4//! [`Import`] publishes raw AAC frames (not ADTS) to a moq broadcast.
5
6mod import;
7
8pub use import::*;
9
10use bytes::{Buf, Bytes};
11
12/// AAC parsing errors.
13#[derive(Debug, Clone, thiserror::Error)]
14#[non_exhaustive]
15pub enum Error {
16	#[error("AudioSpecificConfig must be at least 2 bytes")]
17	ConfigTooShort,
18
19	#[error("extended audioObjectType requires 2 additional bytes")]
20	ExtendedConfigTooShort,
21
22	#[error("AudioSpecificConfig incomplete")]
23	IncompleteConfig,
24
25	#[error("explicit sample rate requires 3 additional bytes")]
26	ExplicitSampleRateTooShort,
27
28	#[error("unsupported sample rate index: {0}")]
29	UnsupportedSampleRateIndex(u8),
30}
31
32pub type Result<T> = std::result::Result<T, Error>;
33
34/// Typed AAC configuration mirroring the relevant fields of an
35/// AudioSpecificConfig.
36pub struct Config {
37	pub profile: u8,
38	pub sample_rate: u32,
39	pub channel_count: u32,
40}
41
42impl Config {
43	/// Parse an AudioSpecificConfig buffer.
44	///
45	/// Handles basic formats (object_type < 31), extended formats
46	/// (object_type == 31), and explicit sample rates (freq_index == 15). The
47	/// fields are bit-packed and not byte-aligned, so a bit reader is required:
48	/// with an explicit 24-bit rate the channelConfiguration lands mid-byte after
49	/// it. Any SBR/PS extension bits after the core fields are consumed.
50	pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
51		if buf.remaining() < 2 {
52			return Err(Error::ConfigTooShort);
53		}
54
55		let mut reader = BitReader::new(buf);
56
57		// audioObjectType: 5 bits, escaped to 6 more when it reads 31.
58		let mut object_type = reader.read(5, Error::ConfigTooShort)? as u8;
59		if object_type == 31 {
60			object_type = 32 + reader.read(6, Error::ExtendedConfigTooShort)? as u8;
61		}
62
63		// samplingFrequencyIndex: 4 bits; index 15 means an explicit 24-bit rate follows.
64		let freq_index = reader.read(4, Error::IncompleteConfig)? as u8;
65		let sample_rate = if freq_index == 15 {
66			reader.read(24, Error::ExplicitSampleRateTooShort)?
67		} else {
68			*SAMPLE_RATES
69				.get(freq_index as usize)
70				.ok_or(Error::UnsupportedSampleRateIndex(freq_index))?
71		};
72
73		// channelConfiguration: 4 bits, immediately after the (possibly explicit) rate.
74		let channel_config = reader.read(4, Error::IncompleteConfig)? as u8;
75		let channel_count = channel_count_from_config(channel_config);
76
77		// AudioSpecificConfig can carry variable-length extensions (SBR, PS, etc.).
78		// We've extracted the essential fields; drain the rest so the buffer is advanced.
79		if buf.remaining() > 0 {
80			buf.advance(buf.remaining());
81		}
82
83		Ok(Self {
84			profile: object_type,
85			sample_rate,
86			channel_count,
87		})
88	}
89
90	/// Encode this configuration as an AudioSpecificConfig (ISO 14496-3 §1.6.2.1).
91	///
92	/// Standard sample rates produce 2 bytes; non-standard rates fall back to
93	/// the 5-byte form with an explicit 24-bit frequency.
94	pub fn encode(&self) -> Bytes {
95		// audioObjectType is a 5-bit field; mask to prevent shift overflow.
96		let profile = self.profile & 0x1F;
97
98		let freq_index: u8 = match self.sample_rate {
99			96000 => 0,
100			88200 => 1,
101			64000 => 2,
102			48000 => 3,
103			44100 => 4,
104			32000 => 5,
105			24000 => 6,
106			22050 => 7,
107			16000 => 8,
108			12000 => 9,
109			11025 => 10,
110			8000 => 11,
111			7350 => 12,
112			_ => 0xF, // explicit 24-bit frequency follows
113		};
114
115		let channel_config = channel_config_from_count(self.channel_count) as u64;
116
117		if freq_index != 0xF {
118			// 5 + 4 + 4 = 13 bits → 2 bytes (3 bits padding)
119			let b0 = (profile << 3) | (freq_index >> 1);
120			let b1 = ((freq_index & 1) << 7) | ((channel_config as u8 & 0x0F) << 3);
121			Bytes::from(vec![b0, b1])
122		} else {
123			// 5 + 4 + 24 + 4 = 37 bits → 5 bytes (3 bits padding)
124			let mut bits: u64 = 0;
125			bits |= (profile as u64) << 35;
126			bits |= 0xF_u64 << 31;
127			bits |= (self.sample_rate as u64) << 7;
128			bits |= (channel_config & 0xF) << 3;
129			let all = bits.to_be_bytes();
130			Bytes::copy_from_slice(&all[3..8])
131		}
132	}
133}
134
135/// The 13 standard AAC sampling frequencies, indexed by samplingFrequencyIndex
136/// (ISO 14496-3 Table 1.18). Index 15 is the escape for an explicit 24-bit rate.
137const SAMPLE_RATES: [u32; 13] = [
138	96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
139];
140
141/// MSB-first bit reader that pulls bytes from a [`Buf`] on demand.
142///
143/// AudioSpecificConfig is bit-packed: an explicit 24-bit sample rate pushes the
144/// following channelConfiguration off byte boundaries, so the fields can't be
145/// read a whole byte at a time.
146struct BitReader<'a, T: Buf> {
147	buf: &'a mut T,
148	current: u8,
149	bits_left: u8,
150}
151
152impl<'a, T: Buf> BitReader<'a, T> {
153	fn new(buf: &'a mut T) -> Self {
154		Self {
155			buf,
156			current: 0,
157			bits_left: 0,
158		}
159	}
160
161	/// Read `n` bits (n <= 32) MSB-first, returning `short` if the buffer runs dry.
162	fn read(&mut self, n: u8, short: Error) -> Result<u32> {
163		let mut value = 0u32;
164		for _ in 0..n {
165			if self.bits_left == 0 {
166				if !self.buf.has_remaining() {
167					return Err(short);
168				}
169				self.current = self.buf.get_u8();
170				self.bits_left = 8;
171			}
172			self.bits_left -= 1;
173			value = (value << 1) | u32::from((self.current >> self.bits_left) & 1);
174		}
175		Ok(value)
176	}
177}
178
179/// Map an AAC `channel_config` (ISO 14496-3 Table 1.19) to its real channel count.
180/// Configs 1..=6 happen to be identity (5.1 has config=6 and 6 channels). Config
181/// 7 is 7.1 = 8 channels. Config 0 means "described elsewhere" — we default to
182/// stereo.
183fn channel_count_from_config(channel_config: u8) -> u32 {
184	match channel_config {
185		1..=6 => channel_config as u32,
186		7 => 8,
187		0 => {
188			tracing::warn!("channel_config=0 (program config element) unsupported, defaulting to stereo");
189			2
190		}
191		_ => {
192			tracing::warn!(channel_config, "unsupported channel config, defaulting to stereo");
193			2
194		}
195	}
196}
197
198/// Inverse of [`channel_count_from_config`]. Defaults to stereo for unsupported
199/// counts (channel configs > 7 are reserved).
200fn channel_config_from_count(channel_count: u32) -> u8 {
201	match channel_count {
202		1..=6 => channel_count as u8,
203		8 => 7,
204		_ => {
205			tracing::warn!(channel_count, "unsupported channel count, defaulting to stereo");
206			2
207		}
208	}
209}
210
211#[cfg(test)]
212mod tests {
213	use super::*;
214
215	#[test]
216	fn parses_standard_2_byte_config() {
217		// AAC-LC (profile=2), 44100 Hz (freq_index=4), stereo (channels=2).
218		// b0 = 0x12 (00010 0100b → object_type=2, freq_index high 3 bits=010)
219		// b1 = 0x10 (0001 0000b → freq_index low bit=0, channel_config=2, padding=000)
220		let buf = vec![0x12, 0x10];
221		let cfg = Config::parse(&mut buf.as_slice()).unwrap();
222		assert_eq!(cfg.profile, 2);
223		assert_eq!(cfg.sample_rate, 44100);
224		assert_eq!(cfg.channel_count, 2);
225	}
226
227	#[test]
228	fn round_trip_explicit_sample_rate() {
229		// A non-standard rate (no freq_index) forces the explicit 24-bit form, where
230		// channelConfiguration lands mid-byte after the rate. A byte-aligned parser
231		// misreads both fields; the bit reader round-trips them.
232		let cfg = Config {
233			profile: 2,
234			sample_rate: 44_056, // not in the standard table
235			channel_count: 2,
236		};
237		let encoded = cfg.encode();
238		assert_eq!(encoded.len(), 5, "explicit-rate config is 5 bytes");
239
240		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
241		assert_eq!(parsed.profile, 2);
242		assert_eq!(parsed.sample_rate, 44_056);
243		assert_eq!(parsed.channel_count, 2);
244	}
245
246	#[test]
247	fn parses_extended_object_type() {
248		// audioObjectType 31 escapes to a 6-bit extended type. Bytes encode
249		// AOT=31, ext=4 (-> object_type 36), freq_index=3 (48000), channel_config=2,
250		// which straddle byte boundaries: 11111 000100 0011 0010 + padding.
251		let buf: [u8; 3] = [0xF8, 0x86, 0x40];
252		let cfg = Config::parse(&mut buf.as_slice()).unwrap();
253		assert_eq!(cfg.profile, 36);
254		assert_eq!(cfg.sample_rate, 48_000);
255		assert_eq!(cfg.channel_count, 2);
256	}
257
258	#[test]
259	fn round_trip_5_1_channels() {
260		// 5.1 surround: config=6, 6 channels.
261		let cfg = Config {
262			profile: 2,
263			sample_rate: 48000,
264			channel_count: 6,
265		};
266		let encoded = cfg.encode();
267		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
268		assert_eq!(parsed.channel_count, 6);
269	}
270
271	#[test]
272	fn round_trip_7_1_channels() {
273		// 7.1 surround: config=7, but 8 channels.
274		let cfg = Config {
275			profile: 2,
276			sample_rate: 48000,
277			channel_count: 8,
278		};
279		let encoded = cfg.encode();
280		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
281		assert_eq!(parsed.channel_count, 8, "7.1 surround should round-trip as 8 channels");
282	}
283
284	#[test]
285	fn channel_config_zero_falls_back_to_stereo() {
286		// Config 0 means "described in PCE" which we don't implement.
287		assert_eq!(channel_count_from_config(0), 2);
288	}
289
290	#[test]
291	fn unsupported_channel_count_falls_back_to_stereo_config() {
292		assert_eq!(channel_config_from_count(9), 2);
293	}
294}