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	/// The channelConfiguration is a reserved value (8, 9, 10, or 15).
32	#[error("reserved channelConfiguration: {0}")]
33	ReservedChannelConfig(u8),
34
35	/// A program config element is used with an object type that has no GASpecificConfig.
36	#[error("channelConfiguration 0 is unsupported for audioObjectType {0}")]
37	ProgramConfigUnsupported(u8),
38
39	/// ADTS signals a program config element, but none leads the first raw data block.
40	#[error("channelConfiguration 0 without a program config element leading the first raw data block")]
41	ProgramConfigMissing,
42
43	/// The program config element ends before its declared fields.
44	#[error("program config element truncated")]
45	ProgramConfigTruncated,
46
47	/// The program config element declares zero channels.
48	#[error("program config element declares no channels")]
49	ProgramConfigEmpty,
50}
51
52pub type Result<T> = std::result::Result<T, Error>;
53
54/// Typed AAC configuration mirroring the relevant fields of an
55/// AudioSpecificConfig.
56pub struct Config {
57	pub profile: u8,
58	pub sample_rate: u32,
59	pub channel_count: u32,
60}
61
62impl Config {
63	/// Parse an AudioSpecificConfig buffer.
64	///
65	/// Handles basic formats (object_type < 31), extended formats
66	/// (object_type == 31), and explicit sample rates (freq_index == 15). The
67	/// fields are bit-packed and not byte-aligned, so a bit reader is required:
68	/// with an explicit 24-bit rate the channelConfiguration lands mid-byte after
69	/// it. A channelConfiguration of 0 takes the count from the program config element
70	/// that follows, and a reserved one is refused. Any SBR/PS extension bits after the
71	/// core fields are consumed.
72	pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
73		let mut reader = BitReader::new(buf);
74		let (object_type, sample_rate, channel_config) = read_header(&mut reader)?;
75		let channel_count = match channel_config {
76			0 => {
77				let core = read_core(&mut reader, object_type)?;
78				read_general_audio(&mut reader, core)?;
79				program_config(&mut reader)?
80			}
81			_ => channel_count_from_config(channel_config)?,
82		};
83
84		// AudioSpecificConfig can carry variable-length extensions (SBR, PS, etc.).
85		// We've extracted the essential fields; drain the rest so the buffer is advanced.
86		if buf.remaining() > 0 {
87			buf.advance(buf.remaining());
88		}
89
90		Ok(Self {
91			profile: object_type,
92			sample_rate,
93			channel_count,
94		})
95	}
96
97	/// Encode this configuration as an AudioSpecificConfig (ISO 14496-3 §1.6.2.1).
98	///
99	/// Standard sample rates produce 2 bytes; non-standard rates fall back to
100	/// the 5-byte form with an explicit 24-bit frequency.
101	pub fn encode(&self) -> Bytes {
102		// audioObjectType is a 5-bit field; mask to prevent shift overflow.
103		let profile = self.profile & 0x1F;
104
105		let freq_index: u8 = match self.sample_rate {
106			96000 => 0,
107			88200 => 1,
108			64000 => 2,
109			48000 => 3,
110			44100 => 4,
111			32000 => 5,
112			24000 => 6,
113			22050 => 7,
114			16000 => 8,
115			12000 => 9,
116			11025 => 10,
117			8000 => 11,
118			7350 => 12,
119			_ => 0xF, // explicit 24-bit frequency follows
120		};
121
122		let channel_config = channel_config_from_count(self.channel_count) as u64;
123
124		if freq_index != 0xF {
125			// 5 + 4 + 4 = 13 bits → 2 bytes (3 bits padding)
126			let b0 = (profile << 3) | (freq_index >> 1);
127			let b1 = ((freq_index & 1) << 7) | ((channel_config as u8 & 0x0F) << 3);
128			Bytes::from(vec![b0, b1])
129		} else {
130			// 5 + 4 + 24 + 4 = 37 bits → 5 bytes (3 bits padding)
131			let mut bits: u64 = 0;
132			bits |= (profile as u64) << 35;
133			bits |= 0xF_u64 << 31;
134			bits |= (self.sample_rate as u64) << 7;
135			bits |= (channel_config & 0xF) << 3;
136			let all = bits.to_be_bytes();
137			Bytes::copy_from_slice(&all[3..8])
138		}
139	}
140}
141
142/// Read an AudioSpecificConfig's leading fields: the audioObjectType, sample rate, and
143/// channelConfiguration.
144fn read_header<T: Buf>(reader: &mut BitReader<T>) -> Result<(u8, u32, u8)> {
145	if reader.buf.remaining() < 2 {
146		return Err(Error::ConfigTooShort);
147	}
148	let object_type = read_object_type(reader)?;
149
150	// samplingFrequencyIndex: 4 bits; index 15 means an explicit 24-bit rate follows.
151	let freq_index = reader.read(4, Error::IncompleteConfig)? as u8;
152	let sample_rate = if freq_index == 15 {
153		reader.read(24, Error::ExplicitSampleRateTooShort)?
154	} else {
155		*SAMPLE_RATES
156			.get(freq_index as usize)
157			.ok_or(Error::UnsupportedSampleRateIndex(freq_index))?
158	};
159
160	// channelConfiguration: 4 bits, immediately after the (possibly explicit) rate.
161	let channel_config = reader.read(4, Error::IncompleteConfig)? as u8;
162	Ok((object_type, sample_rate, channel_config))
163}
164
165/// Read the core audioObjectType, which explicit SBR and PS name after an extension rate. Any
166/// other object type is its own core, and the leading sample rate is always the core's.
167fn read_core<T: Buf>(reader: &mut BitReader<T>, object_type: u8) -> Result<u8> {
168	if !matches!(object_type, 5 | 29) {
169		return Ok(object_type);
170	}
171	if reader.read(4, Error::IncompleteConfig)? == 15 {
172		reader.read(24, Error::IncompleteConfig)?;
173	}
174	let core = read_object_type(reader)?;
175	if core == 22 {
176		// extensionChannelConfiguration, only for ER BSAC.
177		reader.read(4, Error::IncompleteConfig)?;
178	}
179	Ok(core)
180}
181
182/// Read a GASpecificConfig up to the program config element that a channelConfiguration of 0
183/// puts next, refusing a `core` object type whose specific config is something else.
184fn read_general_audio<T: Buf>(reader: &mut BitReader<T>, core: u8) -> Result<()> {
185	if !GENERAL_AUDIO.contains(&core) {
186		return Err(Error::ProgramConfigUnsupported(core));
187	}
188
189	// frameLengthFlag, dependsOnCoreCoder (then a 14-bit coreCoderDelay), and extensionFlag.
190	reader.read(1, Error::IncompleteConfig)?;
191	if reader.read(1, Error::IncompleteConfig)? == 1 {
192		reader.read(14, Error::IncompleteConfig)?;
193	}
194	reader.read(1, Error::IncompleteConfig)?;
195	Ok(())
196}
197
198/// Build the AudioSpecificConfig for a stream that signals its fields per frame, as ADTS does.
199///
200/// A `channel_config` of 0 means a program config element leads `block`, the stream's first raw
201/// data block; it moves into the config, so the config describes the channels on its own, and
202/// `block` advances past it. Any other value leaves `block` untouched. An element placed anywhere
203/// else is refused: reaching past the channel data needs a full Huffman decode.
204pub(crate) fn in_band_config(profile: u8, sample_rate: u32, channel_config: u8, block: &mut &[u8]) -> Result<Bytes> {
205	let mut out = BitWriter::default();
206	out.write(5, u32::from(profile & 0x1F));
207	match SAMPLE_RATES.iter().position(|&rate| rate == sample_rate) {
208		Some(index) => out.write(4, index as u32),
209		None => {
210			out.write(4, 15);
211			out.write(24, sample_rate);
212		}
213	}
214	out.write(4, u32::from(channel_config));
215
216	if channel_config == 0 {
217		// GASpecificConfig: frameLengthFlag, dependsOnCoreCoder, and extensionFlag, all clear.
218		out.write(3, 0);
219
220		// The element ends byte aligned to the block, so the reader stops exactly past it.
221		let mut reader = BitReader::new(block);
222		if reader.read(3, Error::ProgramConfigMissing)? != ID_PCE {
223			return Err(Error::ProgramConfigMissing);
224		}
225		reader.record = Some(out);
226		program_config(&mut reader)?;
227		out = reader.record.take().expect("recording set above");
228	}
229
230	Ok(Bytes::from(out.bytes))
231}
232
233/// The fields a stream that signals its config per frame, as ADTS does, carries in each header.
234#[derive(Clone, Debug, PartialEq)]
235pub(crate) struct InBand {
236	/// The core audioObjectType. Explicit SBR and PS name it under their own, and a decoder finds
237	/// them again in band, as it would in a stream that never signaled them (implicit signaling).
238	pub object_type: u8,
239	/// The core's sample rate, which SBR doubles on output.
240	pub sample_rate: u32,
241	pub channel_config: u8,
242	/// When `channel_config` is 0, the program config element framed to lead a raw data block:
243	/// its element ID, then the element aligned to the block.
244	pub program_config: Option<Bytes>,
245}
246
247/// Split an AudioSpecificConfig into what a stream that carries it per frame signals, as ADTS
248/// does; the inverse of [`in_band_config`].
249pub(crate) fn in_band(asc: &[u8]) -> Result<InBand> {
250	let mut asc = asc;
251	let mut reader = BitReader::new(&mut asc);
252	let (object_type, sample_rate, channel_config) = read_header(&mut reader)?;
253	let core = read_core(&mut reader, object_type)?;
254
255	let program_config = match channel_config {
256		0 => {
257			read_general_audio(&mut reader, core)?;
258			let mut out = BitWriter::default();
259			out.write(3, ID_PCE);
260			reader.record = Some(out);
261			program_config(&mut reader)?;
262			Some(Bytes::from(reader.record.take().expect("recording set above").bytes))
263		}
264		_ => None,
265	};
266
267	Ok(InBand {
268		object_type: core,
269		sample_rate,
270		channel_config,
271		program_config,
272	})
273}
274
275/// The raw data block element ID of a program config element (ISO 14496-3 Table 4.85).
276const ID_PCE: u32 = 5;
277
278/// The audioObjectTypes whose specific config is a GASpecificConfig (ISO 14496-3 §1.6.2.1), the
279/// only ones where channelConfiguration 0 means a program config element follows.
280const GENERAL_AUDIO: [u8; 12] = [1, 2, 3, 4, 6, 7, 17, 19, 20, 21, 22, 23];
281
282/// Read an audioObjectType: 5 bits, escaped to 6 more when it reads 31.
283fn read_object_type<T: Buf>(reader: &mut BitReader<T>) -> Result<u8> {
284	let object_type = reader.read(5, Error::ConfigTooShort)? as u8;
285	if object_type == 31 {
286		return Ok(32 + reader.read(6, Error::ExtendedConfigTooShort)? as u8);
287	}
288	Ok(object_type)
289}
290
291/// Walk a program_config_element (ISO 14496-3 Table 4.2) to the number of channels it outputs.
292///
293/// Its byte alignment is relative to where the reader started: the AudioSpecificConfig, or the
294/// raw data block.
295fn program_config<T: Buf>(reader: &mut BitReader<T>) -> Result<u32> {
296	let mut read = |n| reader.read(n, Error::ProgramConfigTruncated);
297
298	// element_instance_tag, object_type, sampling_frequency_index.
299	read(10)?;
300	let (front, side, back) = (read(4)?, read(4)?, read(4)?);
301	let (lfe, assoc, cc) = (read(2)?, read(3)?, read(4)?);
302	// Mono and stereo mixdowns each name an element; a matrix mixdown an index and a flag.
303	for bits in [4, 4, 3] {
304		if read(1)? == 1 {
305			read(bits)?;
306		}
307	}
308
309	let mut channels = lfe;
310	for _ in 0..front + side + back {
311		// A channel pair element carries two channels, a single channel element one.
312		channels += 1 + read(1)?;
313		read(4)?;
314	}
315	// Tags of the LFE and data elements, then each coupling channel element's switch flag and tag.
316	for _ in 0..lfe + assoc {
317		read(4)?;
318	}
319	for _ in 0..cc {
320		read(5)?;
321	}
322
323	reader.align();
324	let comment = reader.read(8, Error::ProgramConfigTruncated)?;
325	for _ in 0..comment {
326		reader.read(8, Error::ProgramConfigTruncated)?;
327	}
328
329	if channels == 0 {
330		return Err(Error::ProgramConfigEmpty);
331	}
332	Ok(channels)
333}
334
335/// The 13 standard AAC sampling frequencies, indexed by samplingFrequencyIndex
336/// (ISO 14496-3 Table 1.18). Index 15 is the escape for an explicit 24-bit rate.
337const SAMPLE_RATES: [u32; 13] = [
338	96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
339];
340
341/// MSB-first bit reader that pulls bytes from a [`Buf`] on demand.
342///
343/// AudioSpecificConfig is bit-packed: an explicit 24-bit sample rate pushes the
344/// following channelConfiguration off byte boundaries, so the fields can't be
345/// read a whole byte at a time.
346struct BitReader<'a, T: Buf> {
347	buf: &'a mut T,
348	current: u8,
349	bits_left: u8,
350	/// Every bit read is copied here when set, with alignment redone on the writer's own bytes.
351	record: Option<BitWriter>,
352}
353
354impl<'a, T: Buf> BitReader<'a, T> {
355	fn new(buf: &'a mut T) -> Self {
356		Self {
357			buf,
358			current: 0,
359			bits_left: 0,
360			record: None,
361		}
362	}
363
364	/// Skip to the next byte boundary.
365	fn align(&mut self) {
366		self.bits_left = 0;
367		if let Some(record) = &mut self.record {
368			record.align();
369		}
370	}
371
372	/// Read `n` bits (n <= 32) MSB-first, returning `short` if the buffer runs dry.
373	fn read(&mut self, n: u8, short: Error) -> Result<u32> {
374		let mut value = 0u32;
375		for _ in 0..n {
376			if self.bits_left == 0 {
377				if !self.buf.has_remaining() {
378					return Err(short);
379				}
380				self.current = self.buf.get_u8();
381				self.bits_left = 8;
382			}
383			self.bits_left -= 1;
384			value = (value << 1) | u32::from((self.current >> self.bits_left) & 1);
385		}
386		if let Some(record) = &mut self.record {
387			record.write(n, value);
388		}
389		Ok(value)
390	}
391}
392
393/// MSB-first bit writer, the inverse of [`BitReader`].
394#[derive(Default)]
395struct BitWriter {
396	bytes: Vec<u8>,
397	/// Bits used in the last byte; 0 when the next write starts a new one.
398	used: u8,
399}
400
401impl BitWriter {
402	/// Write the low `n` bits (n <= 32) of `value`, MSB-first.
403	fn write(&mut self, n: u8, value: u32) {
404		for i in (0..n).rev() {
405			if self.used == 0 {
406				self.bytes.push(0);
407			}
408			let bit = ((value >> i) & 1) as u8;
409			*self.bytes.last_mut().expect("pushed above") |= bit << (7 - self.used);
410			self.used = (self.used + 1) % 8;
411		}
412	}
413
414	/// Pad the last byte with zeros.
415	fn align(&mut self) {
416		self.used = 0;
417	}
418}
419
420/// Map an AAC `channel_config` (ISO 14496-3 Table 1.19) to its channel count, for every value but
421/// 0, which a program config element describes instead. Configs 1..=6 are identity. 7, 12, and 14
422/// are 8 channels (7.1 with wide fronts, rear surrounds, or front heights), 11 is 6.1, and 13 is
423/// 22.2. The rest are reserved.
424fn channel_count_from_config(channel_config: u8) -> Result<u32> {
425	match channel_config {
426		1..=6 => Ok(channel_config as u32),
427		7 | 12 | 14 => Ok(8),
428		11 => Ok(7),
429		13 => Ok(24),
430		_ => Err(Error::ReservedChannelConfig(channel_config)),
431	}
432}
433
434/// Inverse of [`channel_count_from_config`]. Defaults to stereo for unsupported
435/// counts (channel configs > 7 are reserved).
436fn channel_config_from_count(channel_count: u32) -> u8 {
437	match channel_count {
438		1..=6 => channel_count as u8,
439		8 => 7,
440		_ => {
441			tracing::warn!(channel_count, "unsupported channel count, defaulting to stereo");
442			2
443		}
444	}
445}
446
447#[cfg(test)]
448mod tests {
449	use super::*;
450
451	#[test]
452	fn parses_standard_2_byte_config() {
453		// AAC-LC (profile=2), 44100 Hz (freq_index=4), stereo (channels=2).
454		// b0 = 0x12 (00010 0100b → object_type=2, freq_index high 3 bits=010)
455		// b1 = 0x10 (0001 0000b → freq_index low bit=0, channel_config=2, padding=000)
456		let buf = vec![0x12, 0x10];
457		let cfg = Config::parse(&mut buf.as_slice()).unwrap();
458		assert_eq!(cfg.profile, 2);
459		assert_eq!(cfg.sample_rate, 44100);
460		assert_eq!(cfg.channel_count, 2);
461	}
462
463	#[test]
464	fn round_trip_explicit_sample_rate() {
465		// A non-standard rate (no freq_index) forces the explicit 24-bit form, where
466		// channelConfiguration lands mid-byte after the rate. A byte-aligned parser
467		// misreads both fields; the bit reader round-trips them.
468		let cfg = Config {
469			profile: 2,
470			sample_rate: 44_056, // not in the standard table
471			channel_count: 2,
472		};
473		let encoded = cfg.encode();
474		assert_eq!(encoded.len(), 5, "explicit-rate config is 5 bytes");
475
476		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
477		assert_eq!(parsed.profile, 2);
478		assert_eq!(parsed.sample_rate, 44_056);
479		assert_eq!(parsed.channel_count, 2);
480	}
481
482	#[test]
483	fn parses_extended_object_type() {
484		// audioObjectType 31 escapes to a 6-bit extended type. Bytes encode
485		// AOT=31, ext=4 (-> object_type 36), freq_index=3 (48000), channel_config=2,
486		// which straddle byte boundaries: 11111 000100 0011 0010 + padding.
487		let buf: [u8; 3] = [0xF8, 0x86, 0x40];
488		let cfg = Config::parse(&mut buf.as_slice()).unwrap();
489		assert_eq!(cfg.profile, 36);
490		assert_eq!(cfg.sample_rate, 48_000);
491		assert_eq!(cfg.channel_count, 2);
492	}
493
494	#[test]
495	fn round_trip_5_1_channels() {
496		// 5.1 surround: config=6, 6 channels.
497		let cfg = Config {
498			profile: 2,
499			sample_rate: 48000,
500			channel_count: 6,
501		};
502		let encoded = cfg.encode();
503		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
504		assert_eq!(parsed.channel_count, 6);
505	}
506
507	#[test]
508	fn round_trip_7_1_channels() {
509		// 7.1 surround: config=7, but 8 channels.
510		let cfg = Config {
511			profile: 2,
512			sample_rate: 48000,
513			channel_count: 8,
514		};
515		let encoded = cfg.encode();
516		let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
517		assert_eq!(parsed.channel_count, 8, "7.1 surround should round-trip as 8 channels");
518	}
519
520	/// The AudioSpecificConfig ffmpeg 9.0.1 writes for quad (`-af pan=quad|...`, native `aac`
521	/// encoder, `.m4a`): channelConfiguration 0 and a program config element with two channel
522	/// pair elements, front and back, then a "Lavc63.1.101" comment and an SBR sync extension.
523	const FFMPEG_QUAD_ASC: [u8; 24] = [
524		0x11, 0x80, 0x04, 0xC4, 0x04, 0x00, 0x21, 0x10, 0x0C, 0x4C, 0x61, 0x76, 0x63, 0x36, 0x33, 0x2E, 0x31, 0x2E,
525		0x31, 0x30, 0x31, 0x56, 0xE5, 0x00,
526	];
527
528	/// Write a program config element with `front`, `side`, and `back` elements (true for a
529	/// channel pair) and `lfe` LFE elements, starting at the writer's position.
530	fn write_pce(out: &mut BitWriter, front: &[bool], side: &[bool], back: &[bool], lfe: u32) {
531		// element_instance_tag, object_type (LC), sampling_frequency_index (48 kHz).
532		out.write(4, 0);
533		out.write(2, 1);
534		out.write(4, 3);
535		out.write(4, front.len() as u32);
536		out.write(4, side.len() as u32);
537		out.write(4, back.len() as u32);
538		out.write(2, lfe);
539		// No data or coupling elements, and no mixdowns.
540		out.write(3, 0);
541		out.write(4, 0);
542		out.write(3, 0);
543		for (tag, &cpe) in front.iter().chain(side).chain(back).enumerate() {
544			out.write(1, cpe.into());
545			out.write(4, tag as u32);
546		}
547		for tag in 0..lfe {
548			out.write(4, tag);
549		}
550		out.align();
551		// A one-byte comment.
552		out.write(8, 1);
553		out.write(8, b'x'.into());
554	}
555
556	/// An AudioSpecificConfig with channelConfiguration 0 around [`write_pce`]'s element.
557	fn pce_asc(object_type: u8, front: &[bool], side: &[bool], back: &[bool], lfe: u32) -> Vec<u8> {
558		let mut out = BitWriter::default();
559		out.write(5, object_type.into());
560		out.write(4, 3);
561		out.write(4, 0);
562		out.write(3, 0);
563		write_pce(&mut out, front, side, back, lfe);
564		out.bytes
565	}
566
567	#[test]
568	fn parses_ffmpeg_program_config_element() {
569		let cfg = Config::parse(&mut FFMPEG_QUAD_ASC.as_slice()).unwrap();
570		assert_eq!(cfg.profile, 2);
571		assert_eq!(cfg.sample_rate, 48_000);
572		assert_eq!(cfg.channel_count, 4, "two channel pair elements");
573	}
574
575	#[test]
576	fn parses_program_config_element_counts() {
577		// 5.1: a single and a pair in front, a pair in back, and an LFE.
578		let asc = pce_asc(2, &[false, true], &[], &[true], 1);
579		assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, 6);
580
581		// Side elements count too: 3/2/2 with no LFE.
582		let asc = pce_asc(2, &[false, true], &[true], &[true], 0);
583		assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, 7);
584	}
585
586	/// audioObjectType 5 (SBR), 24 kHz core, channelConfiguration 0, a 48 kHz extension rate,
587	/// then the core type (LC) whose GASpecificConfig carries a front and back pair.
588	fn sbr_pce_asc() -> Vec<u8> {
589		let mut out = BitWriter::default();
590		out.write(5, 5);
591		out.write(4, 6);
592		out.write(4, 0);
593		out.write(4, 3);
594		out.write(5, 2);
595		out.write(3, 0);
596		write_pce(&mut out, &[true], &[], &[true], 0);
597		out.bytes
598	}
599
600	#[test]
601	fn parses_program_config_element_behind_explicit_sbr() {
602		let cfg = Config::parse(&mut sbr_pce_asc().as_slice()).unwrap();
603		assert_eq!(cfg.profile, 5);
604		assert_eq!(cfg.channel_count, 4);
605	}
606
607	#[test]
608	fn refuses_bad_program_config_elements() {
609		let asc = pce_asc(2, &[], &[], &[], 0);
610		assert!(matches!(
611			Config::parse(&mut asc.as_slice()),
612			Err(Error::ProgramConfigEmpty)
613		));
614
615		let asc = pce_asc(2, &[true], &[], &[], 0);
616		assert!(matches!(
617			Config::parse(&mut &asc[..asc.len() - 1]),
618			Err(Error::ProgramConfigTruncated)
619		));
620
621		// ALS (36) describes its channels in its own specific config, not a PCE.
622		let mut out = BitWriter::default();
623		out.write(5, 31);
624		out.write(6, 4);
625		out.write(4, 3);
626		out.write(4, 0);
627		out.write(8, 0);
628		assert!(matches!(
629			Config::parse(&mut out.bytes.as_slice()),
630			Err(Error::ProgramConfigUnsupported(36))
631		));
632	}
633
634	#[test]
635	fn channel_config_dispositions() {
636		let expected = [1, 2, 3, 4, 5, 6, 8];
637		for (config, count) in (1..=7).zip(expected) {
638			let asc = [0x11, 0x80 | (config << 3)];
639			assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, count);
640		}
641
642		for (config, count) in [(11, 7), (12, 8), (13, 24), (14, 8)] {
643			let asc = [0x11, 0x80 | (config << 3)];
644			assert_eq!(Config::parse(&mut asc.as_slice()).unwrap().channel_count, count);
645		}
646
647		for config in [8, 9, 10, 15] {
648			let asc = [0x11, 0x80 | (config << 3)];
649			assert!(matches!(
650				Config::parse(&mut asc.as_slice()),
651				Err(Error::ReservedChannelConfig(c)) if c == config
652			));
653		}
654	}
655
656	#[test]
657	fn in_band_config_moves_the_program_config_element() {
658		// A raw data block leading with ID_PCE, which puts the element 3 bits off the byte grid
659		// the AudioSpecificConfig puts it on, so the copy has to redo the alignment.
660		let mut block = BitWriter::default();
661		block.write(3, ID_PCE);
662		write_pce(&mut block, &[false, true], &[], &[true], 1);
663		let pce = block.bytes.clone();
664		// The channel elements that follow are never read.
665		block.write(8, 0xFF);
666
667		let mut rest = block.bytes.as_slice();
668		let asc = in_band_config(2, 48_000, 0, &mut rest).unwrap();
669		assert_eq!(asc, pce_asc(2, &[false, true], &[], &[true], 1));
670		assert_eq!(Config::parse(&mut asc.as_ref()).unwrap().channel_count, 6);
671		assert_eq!(rest, [0xFF], "the element moves out of the block");
672
673		// And back: the element leads the block again, byte for byte.
674		let in_band = in_band(&asc).unwrap();
675		assert_eq!(in_band.channel_config, 0);
676		assert_eq!(in_band.program_config, Some(Bytes::from(pce)));
677	}
678
679	#[test]
680	fn in_band_of_ffmpeg_program_config_element() {
681		// Round trip ffmpeg's own element through a raw data block and back into a config. The
682		// trailing SBR sync extension is not part of the element, so it drops.
683		let in_band = in_band(&FFMPEG_QUAD_ASC).unwrap();
684		assert_eq!(in_band.channel_config, 0);
685		let pce = in_band.program_config.unwrap();
686		let mut block = pce.as_ref();
687		let asc = in_band_config(2, 48_000, 0, &mut block).unwrap();
688		assert!(block.is_empty(), "the element is all that was framed");
689		assert_eq!(asc, FFMPEG_QUAD_ASC[..FFMPEG_QUAD_ASC.len() - 3]);
690	}
691
692	#[test]
693	fn in_band_without_a_program_config_element() {
694		let stereo = in_band(&[0x11, 0x90]).unwrap();
695		assert_eq!((stereo.channel_config, stereo.program_config), (2, None));
696		assert_eq!(in_band(&[0x11, 0xE0]).unwrap().channel_config, 12);
697	}
698
699	#[test]
700	fn in_band_of_explicit_sbr_is_its_lc_core() {
701		// GStreamer 1.28 `fdkaacenc` at 48 kHz stereo: HE-AAC is SBR over a 24 kHz stereo LC core,
702		// and HE-AACv2 is PS over a mono one.
703		for (asc, channel_config) in [([0x2B, 0x11, 0x88, 0x00], 2), ([0xEB, 0x09, 0x88, 0x00], 1)] {
704			let expected = InBand {
705				object_type: 2,
706				sample_rate: 24_000,
707				channel_config,
708				program_config: None,
709			};
710			assert_eq!(in_band(&asc).unwrap(), expected);
711		}
712
713		// A config naming SBR but stopping before its core has no core to name.
714		assert!(matches!(in_band(&[0x2A, 0x10]), Err(Error::IncompleteConfig)));
715	}
716
717	#[test]
718	fn in_band_of_explicit_sbr_keeps_its_program_config_element() {
719		let in_band = in_band(&sbr_pce_asc()).unwrap();
720		assert_eq!(in_band.object_type, 2);
721		assert_eq!(in_band.sample_rate, 24_000);
722		assert_eq!(in_band.channel_config, 0);
723		let pce = in_band.program_config.expect("a program config element");
724		let mut block = pce.as_ref();
725		let asc = in_band_config(2, 24_000, 0, &mut block).unwrap();
726		assert_eq!(Config::parse(&mut asc.as_ref()).unwrap().channel_count, 4);
727	}
728
729	#[test]
730	fn in_band_config_refuses_a_block_without_a_leading_pce() {
731		// ID_CPE first: any element past the channel data is out of reach.
732		assert!(matches!(
733			in_band_config(2, 48_000, 0, &mut [0x20, 0x00].as_slice()),
734			Err(Error::ProgramConfigMissing)
735		));
736		assert!(matches!(
737			in_band_config(2, 48_000, 0, &mut [].as_slice()),
738			Err(Error::ProgramConfigMissing)
739		));
740	}
741
742	#[test]
743	fn in_band_config_matches_encode() {
744		// Without a PCE the block is unread and the config is the plain two-byte form.
745		let asc = in_band_config(2, 44_100, 2, &mut [].as_slice()).unwrap();
746		let encoded = Config {
747			profile: 2,
748			sample_rate: 44_100,
749			channel_count: 2,
750		}
751		.encode();
752		assert_eq!(asc, encoded);
753	}
754
755	#[test]
756	fn unsupported_channel_count_falls_back_to_stereo_config() {
757		assert_eq!(channel_config_from_count(9), 2);
758	}
759}