container/aac_asc.rs
1//! AudioSpecificConfig (ASC) parser for AAC family streams
2//! (Squad-25, HE-AAC + multichannel AAC passthrough).
3//!
4//! Reference: ISO/IEC 14496-3 §1.6.2 — `AudioSpecificConfig()` syntax,
5//! and §1.6.5 — backward-compatible explicit signaling for SBR / PS.
6//!
7//! ## Why this exists separately from `decode_asc_*`
8//!
9//! `demux.rs` already has tiny `decode_asc_sample_rate` /
10//! `decode_asc_channels` helpers that handle the **core** ASC layer only.
11//! That was sufficient for AAC-LC stereo passthrough (Squad-18) but loses
12//! the SBR / PS extension layer that distinguishes:
13//! - AAC-LC (AOT=2)
14//! - HE-AAC v1 (AOT=2 core wrapped by AOT=5 SBR extension)
15//! - HE-AAC v2 (AOT=2 core wrapped by AOT=29 PS extension carrying AOT=5 SBR)
16//!
17//! and the **implicit** vs **explicit** signaling forms (ISO 14496-3 §1.6.5):
18//! - Implicit: the ASC contains only `audioObjectType=2` (LC). The actual
19//! stream may still carry SBR/PS payloads in the AAC bitstream, but the
20//! ASC does not advertise them. **Apple Core Audio (and AVFoundation)
21//! silently downgrade implicit-signaled HE-AAC to mono 22.05 kHz core**
22//! — playback is technically correct against the LC layer but loses the
23//! stereo upmix and the high-frequency band, so listeners hear quiet,
24//! muffled audio.
25//! - Explicit: the ASC starts with `audioObjectType=5` (SBR), then carries
26//! the `extensionSamplingFrequencyIndex` followed by an inner
27//! `audioObjectType=2` for the LC core. This is the form Apple players
28//! require to honour the full HE-AAC output.
29//!
30//! ## What this module exports
31//!
32//! - `parse_aac_asc` — parse a 2..16-byte ASC, return a structured
33//! `ParsedAsc { aot, sample_rate, channels, sbr_present, ps_present,
34//! sbr_sample_rate, signaling }`. Pure; no allocations beyond the
35//! returned struct.
36//! - `effective_output_channels` — apply the PS upmix rule (HE-AAC v2
37//! PS: 1-channel core → 2-channel output) so the demuxer can surface
38//! the post-decoder channel count rather than the pre-decoder one.
39//! - `upgrade_to_explicit_signaling` — rewrite an implicit-signaled
40//! HE-AAC ASC (`AOT=2 + sfi + chan + ...`) into the explicit form
41//! (`AOT=5 + sbr_sfi + AOT=2 + ...`) so Apple players honour the
42//! SBR / PS extension. Returns `None` for ASCs that are already
43//! explicit, ASCs that aren't HE-AAC eligible (e.g. AAC-LC > 24 kHz
44//! has no SBR upgrade because SBR doubles the rate to >48 kHz,
45//! which the AAC profile already covers natively at 32/44.1/48 kHz),
46//! or ASCs we can't safely rewrite.
47//!
48//! ## What this module does NOT do
49//!
50//! - It does not parse PCE (Programme Config Element). The synthetic
51//! `channelConfiguration=0` pathway is handled by callers that fall
52//! back to a sane default (`AudioInfo.channels=2`) — explicit PCE
53//! support is out of scope for HE-AAC + 5.1/7.1 passthrough.
54//! - It does not parse the GASpecificConfig body beyond the
55//! `frameLengthFlag`/`dependsOnCoreCoder`/`extensionFlag` field
56//! prefix needed to find the SBR-extension trailer. Most fields
57//! downstream of GASpecificConfig don't affect the SBR/PS detection.
58
59/// Sampling frequency table per ISO/IEC 14496-3 Table 1.16.
60/// Index 0xF means "24-bit explicit rate follows inline".
61pub const SFI_FREQS: [u32; 13] = [
62 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
63];
64
65/// Per ISO 14496-3 §1.6.5: how the SBR / PS extension layer was signaled
66/// inside the ASC, if at all. Drives Apple-compat handling.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum AscSignaling {
69 /// Pure AAC-LC (or other non-HE AOT). No extension layer signaled.
70 NoExtension,
71 /// AAC-LC core with the SBR / PS layer signaled implicitly — that is,
72 /// the ASC starts with `AOT=2` and **does not** advertise SBR or PS,
73 /// but the audio bitstream itself contains SBR / PS payloads. Apple
74 /// players silently downgrade to mono 22.05 kHz. Must be upgraded to
75 /// `Explicit*` before muxing for Apple-compat or rejected.
76 ImplicitMaybe,
77 /// SBR / PS layer signaled explicitly via the `AOT=5` (SBR) or
78 /// `AOT=29` (PS) leading byte. Apple-compatible.
79 ExplicitSbr,
80 /// PS extension explicitly signaled (`AOT=29` leading byte). PS implies
81 /// SBR (PS rides on top of SBR per ISO 14496-3 §6).
82 ExplicitPs,
83}
84
85/// Parsed AudioSpecificConfig fields.
86#[derive(Debug, Clone)]
87pub struct ParsedAsc {
88 /// Core `audioObjectType` (the LC profile in HE-AAC bitstreams).
89 /// Value range after escape decoding: 2 (AAC-LC), 5 (SBR — only if
90 /// the explicit-signaling form puts SBR up front), 29 (PS — same
91 /// caveat), 42 (xHE-AAC USAC), etc.
92 pub aot: u8,
93 /// Sampling rate of the AAC core in Hz. For HE-AAC this is the
94 /// half-rate core; the SBR-extended output rate (typically 2×) is
95 /// in `sbr_sample_rate` when present.
96 pub sample_rate: u32,
97 /// Channel configuration as advertised in the ASC's `channelConfiguration`
98 /// field per ISO 14496-3 Table 1.19. Values 0..7 map directly to channel
99 /// counts (1=mono, 2=stereo, 3=3.0, 4=4.0, 5=5.0, 6=5.1, 7=7.1; value 0
100 /// means "consult the PCE", which we don't parse here — caller's choice
101 /// to default).
102 pub channels: u16,
103 /// True when the ASC explicitly signals an SBR layer (AOT=5 leading
104 /// byte) or has the implicit form's heuristic backbone (always false
105 /// for `Implicit*` because we can only confirm SBR by parsing the
106 /// AAC bitstream, which lives in samples, not the ASC).
107 pub sbr_present: bool,
108 /// True when the ASC explicitly signals a PS layer (AOT=29 leading
109 /// byte). PS implies SBR.
110 pub ps_present: bool,
111 /// Output sample rate of the SBR-doubled stream when `sbr_present`.
112 /// For explicit signaling this comes from `extensionSamplingFrequencyIndex`;
113 /// it equals `2 × sample_rate` for the canonical HE-AAC profile.
114 pub sbr_sample_rate: Option<u32>,
115 /// How the SBR / PS layer was signaled. See [`AscSignaling`] for the
116 /// Apple-compat consequences.
117 pub signaling: AscSignaling,
118}
119
120/// Parse an AudioSpecificConfig per ISO/IEC 14496-3 §1.6.2.1.
121///
122/// On success returns a [`ParsedAsc`] describing the AAC family layer
123/// stack. Returns `None` for malformed / truncated input or for ASCs
124/// whose AOT we don't model (the caller should reject in that case).
125///
126/// Recognized AOTs:
127/// - 2 (AAC-LC) — most common; HE-AAC uses this as the core layer.
128/// - 5 (SBR) — used as the leading AOT in HE-AAC v1 explicit signaling.
129/// - 29 (PS) — used as the leading AOT in HE-AAC v2 explicit signaling.
130///
131/// Other AOTs are surfaced as-is in `aot` with `signaling=NoExtension`
132/// so the caller can decide whether to accept (e.g. xHE-AAC=42) or
133/// reject.
134///
135/// ASC bit layout for the relevant AOTs:
136///
137/// ```text
138/// AOT (5 bits, escape via 31+6) | SFI (4 bits, 0xF→24-bit inline) |
139/// channelConfiguration (4 bits) |
140/// if (AOT == 5 || AOT == 29) {
141/// extensionSamplingFrequencyIndex (4 bits, 0xF→24-bit) |
142/// inner_AOT (5 bits, escape) |
143/// [GASpecificConfig follows for core inner AOT]
144/// } else {
145/// [GASpecificConfig follows]
146/// }
147/// ```
148///
149/// Per ISO 14496-3 §1.6.5 / §4.5.1.1, when explicit signaling is used:
150/// - The **outer** `samplingFrequencyIndex` = the **SBR-output** rate
151/// (typically 2× the AAC core rate). E.g. 48000 for an HE-AAC v1
152/// stream whose core operates at 24000.
153/// - `extensionSamplingFrequencyIndex` = the **SBR rate** = the same
154/// value as the outer SFI per spec recommendation.
155/// - The inner core AAC operates at **half** the SBR rate.
156pub fn parse_aac_asc(asc: &[u8]) -> Option<ParsedAsc> {
157 // ASC needs at least 2 bytes to carry AOT (5b) + SFI (4b) + channelConfig (4b).
158 if asc.len() < 2 {
159 return None;
160 }
161 let mut br = BitReader::new(asc);
162 let leading_aot = read_aot(&mut br)?;
163 let leading_sfi = br.bits(4)? as usize;
164 let leading_sample_rate = decode_sfi(leading_sfi, &mut br)?;
165 let leading_chan_cfg = br.bits(4)? as u16;
166
167 // Explicit-signaling form: the leading AOT is SBR (5) or PS (29). The
168 // *outer* SFI is the SBR-output (extended) rate per ISO 14496-3 §1.6.5.
169 // Next we read `extensionSamplingFrequencyIndex` (typically the same
170 // value, expressed redundantly per spec) and then the inner core AOT
171 // (typically AOT=2 for LC).
172 if leading_aot == 5 || leading_aot == 29 {
173 let ext_sfi = br.bits(4)? as usize;
174 let _sbr_rate_redundant = decode_sfi(ext_sfi, &mut br)?;
175 let core_aot = read_aot(&mut br)?;
176 // Outer SFI is the SBR/output rate; core operates at half that.
177 let sbr_output_rate = leading_sample_rate;
178 let core_rate = sbr_output_rate / 2;
179 return Some(ParsedAsc {
180 aot: core_aot,
181 sample_rate: core_rate,
182 channels: leading_chan_cfg,
183 sbr_present: true,
184 ps_present: leading_aot == 29,
185 sbr_sample_rate: Some(sbr_output_rate),
186 signaling: if leading_aot == 29 {
187 AscSignaling::ExplicitPs
188 } else {
189 AscSignaling::ExplicitSbr
190 },
191 });
192 }
193
194 // Plain / core AOT path. AAC-LC (2) is the common case; xHE-AAC (42),
195 // ER-AAC-LC (17) etc. surface here too. We don't try to chase the
196 // GASpecificConfig tail to find a back-door SBR signal — that's the
197 // implicit form, and we mark it `ImplicitMaybe` ONLY for the AOT=2 +
198 // ≤24 kHz core combination that's the canonical HE-AAC implicit shape.
199 let signaling = if leading_aot == 2 && leading_sample_rate <= 24_000 {
200 AscSignaling::ImplicitMaybe
201 } else {
202 AscSignaling::NoExtension
203 };
204
205 Some(ParsedAsc {
206 aot: leading_aot,
207 sample_rate: leading_sample_rate,
208 channels: leading_chan_cfg,
209 sbr_present: false,
210 ps_present: false,
211 sbr_sample_rate: None,
212 signaling,
213 })
214}
215
216/// Effective decoded-output channel count for an HE-AAC family stream.
217/// Per ISO/IEC 14496-3 §6 ("Parametric Stereo"), a 1-channel core wrapped
218/// in PS upmixes to 2-channel output. SBR alone does not change the
219/// channel count.
220///
221/// This is the value the demuxer surfaces in `AudioInfo.channels`, NOT
222/// the raw `channelConfiguration` from the ASC. Players honour the
223/// effective count for buffer allocation and downstream renderers.
224pub fn effective_output_channels(parsed: &ParsedAsc) -> u16 {
225 let raw = parsed.channels;
226 // PS upmix: mono LC + PS → stereo output.
227 if parsed.ps_present && raw == 1 {
228 return 2;
229 }
230 // channelConfiguration=0 is "consult PCE" — we default to 2 (matches
231 // the existing demux fallback) so callers never see 0.
232 if raw == 0 { 2 } else { raw }
233}
234
235/// Rewrite an implicit-signaled HE-AAC ASC into explicit form. Returns
236/// `None` when no rewrite is needed or possible:
237/// - The ASC is already explicit (`AOT=5` or `AOT=29` leading byte).
238/// - The ASC isn't HE-AAC-eligible (core sample rate > 24 kHz, since SBR
239/// would push it past the canonical 48 kHz output rate).
240/// - The ASC is malformed.
241///
242/// On success returns the explicit-form ASC bytes. Layout produced per
243/// ISO 14496-3 §1.6.2.1 / §1.6.5:
244///
245/// `outerAOT=5 (5b) | outerSFI=SBR_rate (4b) | channelConfiguration (4b) |
246/// extensionSamplingFrequencyIndex=SBR_rate (4b) | innerAOT=2 (5b) |
247/// GASpecificConfig tail bits...`
248///
249/// Where `SBR_rate = 2 × original_core_rate` (per the canonical HE-AAC
250/// rate doubling). If `2×core` matches a known SFI we use the index; else
251/// we emit the 24-bit explicit rate (`sfi=0xF` + `samplingFrequency u24`).
252///
253/// The original ASC's `channelConfiguration` and post-channelConfiguration
254/// `GASpecificConfig` tail bits are copied verbatim (after re-shifting to
255/// the new positions in the bit stream).
256pub fn upgrade_to_explicit_signaling(asc: &[u8]) -> Option<Vec<u8>> {
257 let parsed = parse_aac_asc(asc)?;
258 if parsed.signaling != AscSignaling::ImplicitMaybe {
259 return None;
260 }
261 if parsed.aot != 2 {
262 return None;
263 }
264 // SBR doubles the sample rate; for AAC-LC ≤24 kHz this lands at ≤48 kHz,
265 // which is the supported HE-AAC profile range.
266 if parsed.sample_rate > 24_000 {
267 return None;
268 }
269 let sbr_rate = parsed.sample_rate * 2;
270 let sbr_sfi = sfi_for_rate(sbr_rate);
271
272 // We need the original ASC bit layout as raw bits so we can copy the
273 // post-channelConfiguration GASpecificConfig tail verbatim. Build a bit
274 // reader, skip the 5+4(+24)+4 prefix, and read the rest as a tail.
275 let mut br = BitReader::new(asc);
276 br.bits(5)?; // AOT=2
277 let leading_sfi = br.bits(4)? as usize;
278 if leading_sfi == 0xF {
279 br.bits(24)?; // skip inline 24-bit core rate (we re-derive)
280 }
281 br.bits(4)?; // channelConfiguration (we already have it in `parsed.channels`)
282
283 // Drain remaining bits into a tail-bit-buffer (the GASpecificConfig).
284 let tail_bits: Vec<u8> = drain_remaining_bits(&mut br);
285
286 // Build the explicit-form bitstream.
287 let mut bw = BitWriter::new();
288 bw.bits(5, 5); // outer AOT=5 (SBR)
289 match sbr_sfi {
290 Some(idx) => bw.bits(idx, 4), // outer SFI = SBR rate
291 None => {
292 bw.bits(0xF, 4); // 0xF → 24-bit inline
293 bw.bits(sbr_rate, 24);
294 }
295 }
296 bw.bits(parsed.channels as u32, 4); // channelConfiguration
297 // extensionSamplingFrequencyIndex = same SBR rate (per spec recommendation).
298 match sbr_sfi {
299 Some(idx) => bw.bits(idx, 4),
300 None => {
301 bw.bits(0xF, 4);
302 bw.bits(sbr_rate, 24);
303 }
304 }
305 bw.bits(2, 5); // inner AOT=2 (LC core)
306 for bit in &tail_bits {
307 bw.bits(*bit as u32, 1);
308 }
309 Some(bw.into_bytes())
310}
311
312/// Reverse-lookup an SFI table index for the given Hz rate. Returns `None`
313/// when no canonical index matches (caller falls back to the 0xF inline form).
314fn sfi_for_rate(rate: u32) -> Option<u32> {
315 SFI_FREQS.iter().position(|r| *r == rate).map(|p| p as u32)
316}
317
318/// Read the (possibly extended) audioObjectType field per ISO 14496-3 §1.6.2.1:
319/// if audioObjectType == 31:
320/// audioObjectType = 32 + audioObjectTypeExt (6 bits)
321fn read_aot(br: &mut BitReader<'_>) -> Option<u8> {
322 let raw = br.bits(5)? as u8;
323 if raw == 31 {
324 let ext = br.bits(6)? as u8;
325 Some(32 + ext)
326 } else {
327 Some(raw)
328 }
329}
330
331/// Decode a samplingFrequencyIndex into the corresponding rate. Index 0xF
332/// triggers a 24-bit inline rate field per ISO 14496-3 Table 1.16.
333fn decode_sfi(sfi: usize, br: &mut BitReader<'_>) -> Option<u32> {
334 if sfi == 0xF {
335 let r = br.bits(24)? as u32;
336 if r == 0 { None } else { Some(r) }
337 } else {
338 SFI_FREQS.get(sfi).copied()
339 }
340}
341
342/// Read remaining bits from the bit-reader as a flat Vec<u8> of {0,1}.
343fn drain_remaining_bits(br: &mut BitReader<'_>) -> Vec<u8> {
344 let mut out = Vec::new();
345 while let Some(b) = br.bits(1) {
346 out.push(b as u8);
347 }
348 out
349}
350
351/// MSB-first bit reader over a byte slice. Mirrors the AscBitReader in
352/// `demux.rs` but stays in this module so we don't tangle the parser
353/// with the rest of the demux state.
354struct BitReader<'a> {
355 data: &'a [u8],
356 pos: usize,
357}
358
359impl<'a> BitReader<'a> {
360 fn new(data: &'a [u8]) -> Self {
361 Self { data, pos: 0 }
362 }
363
364 fn bits(&mut self, n: u32) -> Option<u64> {
365 let mut v: u64 = 0;
366 for _ in 0..n {
367 let byte = *self.data.get(self.pos / 8)?;
368 let bit = (byte >> (7 - (self.pos % 8))) & 1;
369 v = (v << 1) | bit as u64;
370 self.pos += 1;
371 }
372 Some(v)
373 }
374}
375
376/// MSB-first bit writer, byte-padded with zeros at the end.
377struct BitWriter {
378 buf: Vec<u8>,
379 bit_pos: u32,
380}
381
382impl BitWriter {
383 fn new() -> Self {
384 Self {
385 buf: Vec::new(),
386 bit_pos: 0,
387 }
388 }
389
390 fn bits(&mut self, value: u32, n: u32) {
391 for i in (0..n).rev() {
392 let bit = ((value >> i) & 1) as u8;
393 if self.bit_pos.is_multiple_of(8) {
394 self.buf.push(0);
395 }
396 let byte_idx = (self.bit_pos / 8) as usize;
397 let bit_offset = 7 - (self.bit_pos % 8) as u8;
398 self.buf[byte_idx] |= bit << bit_offset;
399 self.bit_pos += 1;
400 }
401 }
402
403 fn into_bytes(self) -> Vec<u8> {
404 self.buf
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 /// AAC-LC stereo @ 48 kHz: AOT=2, SFI=3 (48000), chan=2.
413 /// 00010 0011 0010 000 = 0001 0001 1001 0000 = 0x11 0x90.
414 #[test]
415 fn parse_aac_lc_stereo_48k() {
416 let asc = vec![0x11, 0x90];
417 let p = parse_aac_asc(&asc).expect("parse should succeed");
418 assert_eq!(p.aot, 2, "AOT");
419 assert_eq!(p.sample_rate, 48_000, "sample_rate");
420 assert_eq!(p.channels, 2, "channels");
421 assert!(!p.sbr_present);
422 assert!(!p.ps_present);
423 assert_eq!(p.signaling, AscSignaling::NoExtension);
424 assert_eq!(effective_output_channels(&p), 2);
425 }
426
427 /// AAC-LC stereo @ 44.1 kHz: AOT=2, SFI=4, chan=2.
428 /// 00010 0100 0010 000 = 0001 0010 0001 0000 = 0x12 0x10.
429 #[test]
430 fn parse_aac_lc_stereo_44_1k() {
431 let asc = vec![0x12, 0x10];
432 let p = parse_aac_asc(&asc).expect("parse should succeed");
433 assert_eq!(p.aot, 2);
434 assert_eq!(p.sample_rate, 44_100);
435 assert_eq!(p.channels, 2);
436 assert_eq!(p.signaling, AscSignaling::NoExtension);
437 }
438
439 /// HE-AAC v1 5.1 explicit signaling at 48 kHz output (24 kHz LC core).
440 /// Per ISO 14496-3 §1.6.5 the *outer* SFI = SBR-output rate (48000),
441 /// `extensionSamplingFrequencyIndex` = same value, and the inner core
442 /// AAC operates at half (24000).
443 /// AOT=5 (00101)
444 /// outer SFI = 3 → 48000 (0011)
445 /// channelConfiguration = 6 → 5.1 (0110)
446 /// extensionSamplingFrequencyIndex = 3 → 48000 (0011)
447 /// inner AOT = 2 → LC (00010)
448 /// Bits: 00101 0011 0110 0011 00010 = 22 bits.
449 /// 00101001 10110001 10001000 = 0x29 0xB1 0x88.
450 #[test]
451 fn parse_he_aac_v1_5_1_explicit() {
452 let asc = vec![0x29, 0xB1, 0x88];
453 let p = parse_aac_asc(&asc).expect("parse should succeed");
454 // The reported `aot` is the inner core (LC=2). The leading AOT=5
455 // disappears into `signaling=ExplicitSbr` + `sbr_present`.
456 assert_eq!(p.aot, 2, "core aot should be LC");
457 assert_eq!(p.sample_rate, 24_000, "LC core rate (half of SBR)");
458 assert_eq!(p.channels, 6, "5.1 channel config");
459 assert!(p.sbr_present, "SBR layer must be present");
460 assert!(!p.ps_present, "PS not present in v1");
461 assert_eq!(p.sbr_sample_rate, Some(48_000), "SBR output rate");
462 assert_eq!(p.signaling, AscSignaling::ExplicitSbr);
463 assert_eq!(effective_output_channels(&p), 6);
464 }
465
466 /// HE-AAC v2 mono PS explicit signaling at 44.1 kHz output (22.05 kHz LC core):
467 /// AOT=29 (11101)
468 /// outer SFI = 4 → 44100 (0100) ← SBR-output rate
469 /// channelConfiguration = 1 → mono (0001)
470 /// extensionSamplingFrequencyIndex = 4 → 44100 (0100)
471 /// inner AOT = 2 → LC (00010)
472 /// Bits: 11101 0100 0001 0100 00010 = 22 bits.
473 /// 11101010 00001010 00001000 = 0xEA 0x0A 0x08.
474 #[test]
475 fn parse_he_aac_v2_mono_ps_explicit() {
476 let asc = vec![0xEA, 0x0A, 0x08];
477 let p = parse_aac_asc(&asc).expect("parse should succeed");
478 assert_eq!(p.aot, 2, "core aot is LC");
479 assert_eq!(p.sample_rate, 22_050, "core rate (half of SBR)");
480 assert_eq!(p.channels, 1, "mono channel config (PS upmixes at decode)");
481 assert!(p.sbr_present, "PS implies SBR");
482 assert!(p.ps_present, "PS layer signaled");
483 assert_eq!(p.sbr_sample_rate, Some(44_100));
484 assert_eq!(p.signaling, AscSignaling::ExplicitPs);
485 // PS upmix: 1-channel core → 2-channel effective output.
486 assert_eq!(effective_output_channels(&p), 2);
487 }
488
489 /// HE-AAC implicit signaling: a plain AAC-LC ASC at low core rate
490 /// (≤24 kHz) is the canonical implicit-HE shape. parse_aac_asc must
491 /// flag `ImplicitMaybe` so the caller can decide to upgrade or reject.
492 /// AOT=2, SFI=6 (24000), chan=1 → 00010 0110 0001 000
493 /// = 0001 0011 0000 1000 = 0x13 0x08.
494 #[test]
495 fn parse_implicit_he_aac_flagged() {
496 let asc = vec![0x13, 0x08];
497 let p = parse_aac_asc(&asc).expect("parse");
498 assert_eq!(p.aot, 2);
499 assert_eq!(p.sample_rate, 24_000);
500 assert_eq!(
501 p.signaling,
502 AscSignaling::ImplicitMaybe,
503 "low-rate AAC-LC must be flagged as implicit-HE candidate"
504 );
505 }
506
507 /// upgrade_to_explicit_signaling rewrites a 24 kHz mono AAC-LC ASC into
508 /// the explicit HE-AAC v1 form: leading AOT=5, outer SFI=SBR rate (48 kHz),
509 /// then channelConfig + extSFI + inner AOT=2.
510 #[test]
511 fn upgrade_24k_mono_lc_to_explicit_he_aac_v1() {
512 let asc = vec![0x13, 0x08]; // AOT=2 SFI=6 (24000) chan=1
513 let upgraded =
514 upgrade_to_explicit_signaling(&asc).expect("upgrade should succeed for ≤24 kHz LC");
515 let reparsed = parse_aac_asc(&upgraded).expect("upgraded ASC parses");
516 assert_eq!(
517 reparsed.signaling,
518 AscSignaling::ExplicitSbr,
519 "upgraded ASC must be explicit-SBR"
520 );
521 // After upgrade: outer SFI = 48000 (SBR), inner core = 24000 (half).
522 assert_eq!(reparsed.sample_rate, 24_000, "core rate is half of SBR");
523 assert_eq!(
524 reparsed.sbr_sample_rate,
525 Some(48_000),
526 "SBR rate is 2× core"
527 );
528 assert_eq!(reparsed.channels, 1);
529 }
530
531 /// upgrade_to_explicit_signaling refuses to upgrade ASCs whose core
532 /// rate is too high to plausibly be HE-AAC (>24 kHz core would push
533 /// SBR output > 48 kHz, off-spec for the canonical HE profile).
534 #[test]
535 fn upgrade_rejects_high_rate_lc() {
536 let asc = vec![0x11, 0x90]; // AOT=2 SFI=3 (48000) chan=2
537 // 48 kHz core → 96 kHz SBR. Off-spec; refuse the upgrade.
538 // (Actually our gate is "core rate <= 24 kHz" so 48 kHz is rejected
539 // because signaling is NoExtension, not because of the rate gate;
540 // either way the function must return None.)
541 assert!(upgrade_to_explicit_signaling(&asc).is_none());
542 }
543
544 /// upgrade_to_explicit_signaling refuses to re-upgrade an already-explicit ASC.
545 #[test]
546 fn upgrade_rejects_already_explicit() {
547 let asc = vec![0x29, 0x89, 0x98]; // HE-AAC v1 5.1 explicit
548 assert!(upgrade_to_explicit_signaling(&asc).is_none());
549 }
550
551 /// AOT escape: AOT=31 → 6-bit extension. AOT=42 (xHE-AAC USAC) is the
552 /// canonical case. Bit pattern: 11111 (raw=31) | 001010 (ext=10) → 32+10=42.
553 /// Then SFI=3 (0011) chan=2 (0010), pad. 17 bits.
554 /// 11111 001010 0011 0010 0 = 1111 1001 0100 0110 0100 = 0xF9 0x46 0x40.
555 #[test]
556 fn parse_xhe_aac_aot_escape() {
557 let asc = vec![0xF9, 0x46, 0x40];
558 let p = parse_aac_asc(&asc).expect("parse");
559 assert_eq!(p.aot, 42, "xHE-AAC USAC AOT=42 via escape");
560 assert_eq!(p.sample_rate, 48_000);
561 assert_eq!(p.channels, 2);
562 }
563
564 /// 24-bit inline sample rate: SFI=0xF then 24-bit rate. Use 88200 just
565 /// to differ from the table values for variety.
566 /// AOT=2, SFI=0xF, rate=88200 (0x015888), chan=2.
567 /// Bits: 00010 1111 [88200 in 24 bits] 0010
568 /// 88200 = 0000 0000 0000 0001 0101 1000 1000 1000 → low 24 bits
569 /// = 0000 0001 0101 1000 1000 1000.
570 /// Concatenation: 00010 1111 000000010101100010001000 0010
571 /// = 0001 0111 1000 0000 1010 1100 0100 0100 0010 = 5 bytes
572 /// 17807ac442 first 4 bytes... let's just compute.
573 #[test]
574 fn parse_inline_24bit_sample_rate() {
575 // Construct via BitWriter to avoid hand-bit-fiddling errors.
576 let mut bw = BitWriter::new();
577 bw.bits(2, 5); // AOT=2
578 bw.bits(0xF, 4); // SFI=0xF
579 bw.bits(88_200, 24); // inline rate
580 bw.bits(2, 4); // chan=2
581 let asc = bw.into_bytes();
582 let p = parse_aac_asc(&asc).expect("parse");
583 assert_eq!(p.aot, 2);
584 assert_eq!(p.sample_rate, 88_200);
585 assert_eq!(p.channels, 2);
586 }
587
588 /// Bit writer round-trip smoke test.
589 #[test]
590 fn bit_writer_roundtrip() {
591 let mut bw = BitWriter::new();
592 bw.bits(0b10101, 5);
593 bw.bits(0b1100, 4);
594 let bytes = bw.into_bytes();
595 // 10101 1100 0 = 0xAE 0x00; total written = 9 bits → padded to 2 bytes.
596 // Wait: 10101 1100 = 9 bits. Layout: 1010 1110 0000 0000 = 0xAE 0x00.
597 assert_eq!(bytes, vec![0xAE, 0x00]);
598 let mut br = BitReader::new(&bytes);
599 assert_eq!(br.bits(5), Some(0b10101));
600 assert_eq!(br.bits(4), Some(0b1100));
601 }
602
603 /// 5.1 ASC with channelConfiguration=6 (the standard MPEG L/R/C/LFE/Ls/Rs ordering).
604 /// Plain AAC-LC core at 48 kHz: AOT=2 SFI=3 chan=6.
605 /// 00010 0011 0110 000 = 0001 0001 1011 0000 = 0x11 0xB0.
606 #[test]
607 fn parse_aac_lc_5_1_at_48k() {
608 let asc = vec![0x11, 0xB0];
609 let p = parse_aac_asc(&asc).expect("parse");
610 assert_eq!(p.aot, 2);
611 assert_eq!(p.sample_rate, 48_000);
612 assert_eq!(p.channels, 6, "5.1 channel config");
613 assert_eq!(
614 p.signaling,
615 AscSignaling::NoExtension,
616 "48 kHz LC has no implicit HE-AAC interpretation"
617 );
618 assert_eq!(effective_output_channels(&p), 6);
619 }
620
621 /// 7.1 ASC with channelConfiguration=7. Plain AAC-LC at 48 kHz.
622 /// 00010 0011 0111 000 = 0001 0001 1011 1000 = 0x11 0xB8.
623 #[test]
624 fn parse_aac_lc_7_1_at_48k() {
625 let asc = vec![0x11, 0xB8];
626 let p = parse_aac_asc(&asc).expect("parse");
627 assert_eq!(p.channels, 7, "7.1 channel config");
628 assert_eq!(effective_output_channels(&p), 7);
629 }
630
631 /// Empty / truncated input must return None.
632 #[test]
633 fn parse_rejects_truncated() {
634 assert!(parse_aac_asc(&[]).is_none());
635 assert!(parse_aac_asc(&[0x12]).is_none());
636 }
637}