From 799303ad68bcaac91bc197ecc7335d371efe23d5 Mon Sep 17 00:00:00 2001 From: tylerjenningsw Date: Thu, 29 Oct 2026 18:20:09 -0400 Subject: [PATCH] Media tests: read ffmpeg 4.2's PCE fixtures with their real channel count (#1701) ffmpeg 4.2 writes 2 in the sample entry of an AAC PCE layout and FilmCraft decodes as many channels as that names, so the 6.1 check now reads a copy naming all seven instead of skipping. Configuration 12 moves to the synthetic layout tests. --- crates/media/tests/it/downmix.rs | 44 +++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/crates/media/tests/it/downmix.rs b/crates/media/tests/it/downmix.rs index c139676d6..05205ab6a 100644 --- a/crates/media/tests/it/downmix.rs +++ b/crates/media/tests/it/downmix.rs @@ -168,22 +168,23 @@ fn each_channel_reaches_stereo_at_its_bs775_gain() { } } -/// AAC 7.1 in channel configuration 12 (back surrounds), which FilmCraft reorders where it -/// leaves ffmpeg's configuration 7 as coded: ffmpeg's 7.1 with its configuration rewritten (both -/// code the same elements). -#[test] -fn seven_one_aac_in_configuration_12() { - let c = cases().into_iter().find(|c| c.file == "dm71.m4a").expect("the 7.1 AAC case"); - let freqs: Vec = FREQS.iter().copied().take(c.names.len()).collect(); - let Some(p) = fixture(c.file, c.layout, c.names, &freqs, c.codec) else { return }; - let mut bytes = std::fs::read(&p).expect("read"); - let (asc, _) = aac_entry(&bytes).expect("an AAC track"); - assert_eq!(asc.get(..2), Some(&[0x11, 0xB8][..]), "AAC-LC 48 kHz, configuration 7"); - let at = bytes.windows(asc.len()).position(|w| w == asc).expect("the configuration in the file"); - bytes[at + 1] = 0xE0; - let p12 = fixtures().join("dm71c12.m4a"); - std::fs::write(&p12, bytes).expect("write"); - check(&Case { file: "dm71c12.m4a", ..c }, &p12); +/// `p`, or a copy whose AAC sample entry names all `channels` of its PCE layout: ffmpeg 4.2 writes +/// 2 there for a PCE layout (8.1 the real count), and FilmCraft decodes as many channels as the +/// sample entry names, so the other five of a 6.1 would never reach the mix. +fn whole_pce_entry(p: &Path, channels: usize) -> PathBuf { + let mut bytes = std::fs::read(p).expect("read"); + let Some((asc, 2)) = aac_entry(&bytes) else { return p.to_path_buf() }; + if channels <= 2 || filmcraft_aac::AudioSpecificConfig::parse(&asc).map(|a| a.channel_config).ok() != Some(0) { + return p.to_path_buf(); + } + // the `mp4a` entry's channelcount follows its 6 reserved bytes, data reference index and + // 8 reserved bytes + let at = bytes.windows(4).position(|w| w == b"mp4a").expect("an mp4a sample entry") + 4 + 6 + 2 + 8; + bytes[at..at + 2].copy_from_slice(&(channels as u16).to_be_bytes()); + eprintln!("{}: its sample entry names 2 of {channels} channels (ffmpeg 4.2): reading a copy naming all", p.display()); + let out = p.with_extension("entry.m4a"); + std::fs::write(&out, bytes).expect("write"); + out } /// The AudioSpecificConfig and sample entry channel count of an MP4's first AAC track. @@ -196,16 +197,7 @@ fn aac_entry(bytes: &[u8]) -> Option<(Vec, u32)> { } fn check(c: &Case, p: &Path) { - let pce_as_stereo = aac_entry(&std::fs::read(p).expect("read")).is_some_and(|(asc, entry)| { - let config = filmcraft_aac::AudioSpecificConfig::parse(&asc).map(|a| a.channel_config); - config.ok() == Some(0) && entry == 2 - }); - if pce_as_stereo && c.names.len() > 2 { - // ffmpeg 4.2 writes 2 in the sample entry of an AAC PCE layout (8.1 the real count), and - // that is all FilmCraft decodes. - eprintln!("{}: FilmCraft reads 2 of its {} channels: skipping", c.file, c.names.len()); - return; - } + let p = &whole_pce_entry(p, c.names.len()); let s = ours(p); let freqs: Vec = FREQS.iter().copied().take(c.names.len()).collect(); let sine = 0.125 * 1.0; // peak of ffmpeg's sine source (0.125 = 2^-3)