craft-audio: keep the SBR history when a new program config fails

The SBR decoders now record the layout they were built for and are rebuilt when the
decoder's layout differs, instead of being cleared in set_layout. An access unit whose new
program config fails puts the layout back, and with it the SBR decoders still match.

Refs #1121

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
alehelandClaude Opus 5.5 committed 2026-11-13 07:34:10 +01:00
1 parent 48f5283ec8
commit fe8c1f697a
1 file changed
+65 -32
+65 -32
View File
@@ -85,6 +85,9 @@ pub struct Decoder {
sbr_seen: bool,
/// The SBR decoder of each layout entry, when output is at twice the core rate.
sbr_out: Option<Vec<sbr::Element>>,
/// The layout `sbr_out` was built for. Kept apart from `layout`, so that an access unit whose
/// new program config fails (and whose layout is put back) leaves the SBR history alone.
sbr_layout: Vec<(ElementType, u8)>,
}
impl Decoder {
@@ -121,6 +124,7 @@ impl Decoder {
scratch: Scratch::new(),
sbr_seen: false,
sbr_out: None,
sbr_layout: Vec::new(),
};
d.set_layout(layout);
Ok(d)
@@ -135,10 +139,6 @@ impl Decoder {
}
self.layout = layout;
self.offsets = offsets;
// the SBR decoders' history belongs to the old element order: `apply_sbr` rebuilds them
if let Some(e) = self.sbr_out.as_mut() {
e.clear();
}
self.channels = vec![ChannelState { overlap: vec![0.0; 1024], prev_shape: WindowShape::Sine }; n];
}
@@ -214,10 +214,11 @@ impl Decoder {
let Some(elements) = self.sbr_out.as_mut() else {
return pcm;
};
// a new layout (a program config element) starts the SBR decoders afresh
let fits = elements.len() == self.layout.len() && elements.iter().zip(&self.layout).all(|(e, l)| e.channels() == l.0.channels());
if !fits {
// a new layout (a program config element) starts the SBR decoders afresh: their history
// belongs to the old element order, even where the channel counts match
if elements.len() != self.layout.len() || self.sbr_layout != self.layout {
*elements = self.layout.iter().map(|l| sbr::Element::new(l.0.channels(), rate)).collect();
self.sbr_layout.clone_from(&self.layout);
}
let mut out = Vec::with_capacity(pcm.len());
for (i, e) in elements.iter_mut().enumerate() {
@@ -704,26 +705,7 @@ mod tests {
/// its SBR fill element describes is there; with SBR off, the core alone (silence).
#[test]
fn sbr_fill_elements_rebuild_the_high_band() {
let payload = sbr::test_payload(44_100, |_| 40, |_| 0).unwrap();
let mut bw = BitWriter::new();
bw.write_bits(ID_SCE, 3);
bw.write_bits(0, 4);
bw.write_bits(0, 8 + 11 + 3); // global_gain, ics_info (max_sfb 0), no pulse/TNS/gain control
bw.write_bits(ID_FIL, 3);
let count = (4 + 8 * payload.len()).div_ceil(8);
if count < 15 {
bw.write_bits(count as u32, 4);
} else {
bw.write_bits(15, 4);
bw.write_bits((count - 14) as u32, 8);
}
bw.write_bits(EXT_SBR_DATA, 4);
for &b in &payload {
bw.write_bits(u32::from(b), 8);
}
bw.write_bits(0, 4);
bw.write_bits(ID_END, 3);
let au = bw.finish();
let au = sbr_au(0);
// AAC-LC, 22.05 kHz, mono
let asc = AudioSpecificConfig::lc(7, 1);
let mut dec = Decoder::from_config(asc.clone()).unwrap();
@@ -740,11 +722,62 @@ mod tests {
assert_eq!(out.len(), 4 * 2048);
let rms = (out[2048..].iter().map(|s| s * s).sum::<f32>() / (out.len() - 2048) as f32).sqrt();
assert!(rms > 1e-4, "{rms}");
// a new layout drops the SBR decoders (their history is the old element order's) and
// keeps SBR on
dec.set_layout(vec![(ElementType::Sce, 0)]);
assert_eq!(dec.sbr_out.as_ref().map(Vec::len), Some(0));
assert_eq!(dec.decode(&au).unwrap()[0].len(), 2048);
}
/// A program config that changes the layout restarts the SBR decoders, even where the
/// channel counts match, and one in an access unit that fails leaves their history alone: the
/// access units after it decode as if it had not been there.
#[test]
fn a_new_layout_restarts_sbr_and_a_failed_one_keeps_it() {
let au = sbr_au(0);
let one = ProgramConfig { front: vec![(false, 0)], ..Default::default() };
let six = ProgramConfig { front: vec![(false, 0), (true, 0)], back: vec![(true, 1)], lfe: vec![0], ..Default::default() };
let decoder = || {
let mut d = Decoder::from_config(AudioSpecificConfig { pce: Some(one.clone()), ..AudioSpecificConfig::lc(7, 0) }).unwrap();
d.set_sbr(true);
d
};
let (mut kept, mut failed) = (decoder(), decoder());
for _ in 0..3 {
assert_eq!(kept.decode(&au).unwrap(), failed.decode(&au).unwrap());
}
// the new layout's elements are missing from this access unit
assert!(failed.decode(&silent_au(Some(&six), &[(ID_SCE, 0)])).is_err());
for _ in 0..2 {
assert_eq!(kept.decode(&au).unwrap(), failed.decode(&au).unwrap());
}
// the same channel count under another tag is a new layout: the SBR decoder starts over,
// as in a decoder that had that layout from the start
let tag1 = ProgramConfig { front: vec![(false, 1)], ..Default::default() };
let mut fresh = Decoder::from_config(AudioSpecificConfig { pce: Some(tag1.clone()), ..AudioSpecificConfig::lc(7, 0) }).unwrap();
fresh.set_sbr(true);
kept.set_layout(tag1.layout());
assert_eq!(kept.decode(&sbr_au(1)).unwrap(), fresh.decode(&sbr_au(1)).unwrap());
}
/// One access unit of SCE `tag` with an SBR fill element of [`sbr::test_payload`] (44.1 kHz
/// output).
fn sbr_au(tag: u32) -> Vec<u8> {
let payload = sbr::test_payload(44_100, |_| 40, |_| 0).unwrap();
let mut bw = BitWriter::new();
bw.write_bits(ID_SCE, 3);
bw.write_bits(tag, 4);
bw.write_bits(0, 8 + 11 + 3); // global_gain, ics_info (max_sfb 0), no pulse/TNS/gain control
bw.write_bits(ID_FIL, 3);
let count = (4 + 8 * payload.len()).div_ceil(8);
if count < 15 {
bw.write_bits(count as u32, 4);
} else {
bw.write_bits(15, 4);
bw.write_bits((count - 14) as u32, 8);
}
bw.write_bits(EXT_SBR_DATA, 4);
for &b in &payload {
bw.write_bits(u32::from(b), 8);
}
bw.write_bits(0, 4);
bw.write_bits(ID_END, 3);
bw.finish()
}
/// An access unit whose elements are all empty: `elements` are (id, tag), then ID_END.