mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 07:46:08 +00:00
craft-audio: an AAC program config element shaped like 3.0–5.1 gets those speakers (#964)
* craft-audio: a program config element in the shape of a channel configuration gets its speakers An AAC stream with channel configuration 0 whose program config element lists a centre, a front pair and then exactly the elements of configuration 3 to 6 decoded as C L R Ls Rs LFE and was reported as an unordered layout, so a caller reading it in WAV order sent the centre to the left speaker. Such a stream now has the configuration's layout and WAV order. Any other element list keeps the decoded order, as before. Ported from storytold/filmcraft#4280 (aac_stream_order in filmcraft-codecs). * craft-audio: the ADTS program config oracle expects 5.1 in WAV order ffmpeg 9 writes its 5.1 program config element centre first, so the stream now has 5.1's speakers: each decoded channel must match ffmpeg's channel of the same index. An element list in another order keeps the old check. --------- Co-authored-by: mpratsaki <110930793+mpratsaki@users.noreply.github.com>
This commit is contained in:
co-authored by
mpratsaki
parent
53e1717523
commit
2b35d24324
@@ -113,6 +113,26 @@ pub fn wav_order(channel_config: u8) -> Option<&'static [usize]> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The channel configuration whose speakers a stream has: its own, or for configuration 0 the
|
||||
/// configuration 3–6 that its program config element spells out. A program config element lists
|
||||
/// front, side, back and LFE elements in that order, so one that opens with a centre and a front
|
||||
/// pair and goes on with exactly the elements of a configuration has that configuration's speakers
|
||||
/// (and [`wav_order`]). Any other list has no speaker order that can be told, and gives 0: ffmpeg's
|
||||
/// 5.1(side), for one, lists the pair first and already decodes in WAV order.
|
||||
#[cfg(feature = "aac-decode")]
|
||||
pub fn speaker_config(asc: &AudioSpecificConfig) -> u8 {
|
||||
if asc.channel_config != 0 {
|
||||
return asc.channel_config;
|
||||
}
|
||||
let centre_then_pair = asc.pce.as_ref().is_some_and(|p| p.front.iter().map(|e| e.0).eq([false, true]));
|
||||
if !centre_then_pair {
|
||||
return 0;
|
||||
}
|
||||
let elements = asc.layout();
|
||||
let same = |config: &u8| config_layout(*config).is_some_and(|l| l.iter().map(|e| e.0).eq(elements.iter().map(|e| e.0)));
|
||||
(3..=6).find(same).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// The order the [`Encoder`] takes its input in, from WAV order: for each encoder input channel
|
||||
/// (AAC element order, see [`EncoderConfig::channels`]), the WAV-order channel to feed it. The
|
||||
/// inverse of [`wav_order`] for 3 to 7 channels; `None` for mono and stereo (already in order) and
|
||||
@@ -279,6 +299,30 @@ mod tests {
|
||||
mod order_tests {
|
||||
use super::*;
|
||||
|
||||
/// A program config element in the shape of a channel configuration has that configuration's
|
||||
/// speakers, with the centre first. ffmpeg's 5.1(side) lists its pair first and already decodes
|
||||
/// in WAV order, so it stays as it is.
|
||||
#[cfg(feature = "aac-decode")]
|
||||
#[test]
|
||||
fn a_program_config_element_takes_the_configuration_it_matches() {
|
||||
let with = |pce| AudioSpecificConfig { pce: Some(pce), ..AudioSpecificConfig::lc(3, 0) };
|
||||
let centre_first = ProgramConfig { front: vec![(false, 0), (true, 0)], back: vec![(true, 1)], lfe: vec![0], ..Default::default() };
|
||||
assert_eq!(speaker_config(&with(centre_first)), 6);
|
||||
let three = ProgramConfig { front: vec![(false, 0), (true, 0)], ..Default::default() };
|
||||
assert_eq!(speaker_config(&with(three)), 3);
|
||||
let ffmpeg = ProgramConfig { front: vec![(true, 0), (false, 0)], side: vec![(false, 1)], back: vec![(true, 1)], ..Default::default() };
|
||||
assert_eq!(speaker_config(&with(ffmpeg)), 0);
|
||||
// the same element types with no front pair: the side pair is not left and right
|
||||
let no_front_pair =
|
||||
ProgramConfig { front: vec![(false, 0)], side: vec![(true, 0)], back: vec![(true, 1)], lfe: vec![0], ..Default::default() };
|
||||
assert_eq!(speaker_config(&with(no_front_pair)), 0);
|
||||
// 7.1 in a program config element is not one of the unambiguous configurations
|
||||
let wide =
|
||||
ProgramConfig { front: vec![(false, 0), (true, 0)], side: vec![(true, 1)], back: vec![(true, 2)], lfe: vec![0], ..Default::default() };
|
||||
assert_eq!(speaker_config(&with(wide)), 0);
|
||||
assert_eq!(speaker_config(&AudioSpecificConfig::lc(3, 6)), 6);
|
||||
}
|
||||
|
||||
/// `aac_order` undoes `wav_order` for each channel count the encoder maps to a configuration
|
||||
/// (3–6 channels: configurations 3–6; 7: configuration 11).
|
||||
#[test]
|
||||
|
||||
@@ -156,7 +156,8 @@ impl TrackDecoder {
|
||||
let codec = if up.is_some() { CodecId::HeAac } else { CodecId::Aac };
|
||||
// Only twice the core rate is upsampled to; any other signalled rate plays the core.
|
||||
let out = if up.is_some() { out } else { dec.sample_rate() };
|
||||
let cfg = dec.config().channel_config;
|
||||
// a program config element that spells out a configuration has its speakers
|
||||
let cfg = aac::speaker_config(dec.config());
|
||||
let layout = aac_layout(cfg, dec.channels());
|
||||
let order = layout.is_ordered().then(|| aac::wav_order(cfg)).flatten();
|
||||
(Inner::Aac { dec: Box::new(dec), up, order, adts_format }, codec, out, layout, SampleFormat::Compressed)
|
||||
@@ -1079,6 +1080,8 @@ mod tests {
|
||||
let mut dec = TrackDecoder::new(&aac_params(), &[&frame]).unwrap();
|
||||
assert_eq!(dec.channels(), 3);
|
||||
assert_eq!(dec.decode(&frame).unwrap().channels.len(), 3);
|
||||
// its element lists a centre and a front pair, as configuration 3 does: L R C, in WAV order
|
||||
assert_eq!(dec.layout(), ChannelLayout::SURROUND_3_0);
|
||||
// no program config element where one is needed
|
||||
let bare = aac::AdtsHeader::write(2, 3, 0, 1, 0x7FF).unwrap();
|
||||
let bare = [&bare[..], &[0xE0]].concat();
|
||||
|
||||
@@ -669,10 +669,14 @@ fn adts_with_a_program_config_element_decodes() {
|
||||
assert_eq!(bytes[3] >> 6 | (bytes[2] & 1) << 2, 0, "the fixture signals its layout in a PCE");
|
||||
let (info, buf) = decode(&bytes, None).unwrap();
|
||||
assert_eq!((info.container, info.codec, buf.channels.len()), (c.container, c.codec, c.channels));
|
||||
assert_eq!(info.layout, c.layout);
|
||||
// A program config element that lists a centre, a front pair and then the elements of 5.1 has
|
||||
// 5.1's speakers, in WAV order like ffmpeg's decode; any other list keeps the decoded order,
|
||||
// which ffmpeg versions write differently.
|
||||
let ordered = info.layout.is_ordered();
|
||||
assert_eq!(info.layout, if ordered { ChannelLayout::SURROUND_5_1_SIDE } else { c.layout });
|
||||
assert_eq!(buf.frames(), reference(&ff, &path).len() / c.channels);
|
||||
// A program config's channels come in its own order, and which order ffmpeg writes differs by
|
||||
// version: each of ours must match a different one of ffmpeg's.
|
||||
// Each of our channels must match a different one of ffmpeg's: the same one when the layout is
|
||||
// ordered.
|
||||
let refr = reference(&ff, &path);
|
||||
let mut matched = Vec::new();
|
||||
for plane in &buf.channels {
|
||||
@@ -686,7 +690,9 @@ fn adts_with_a_program_config_element_decodes() {
|
||||
assert!(s >= c.min_snr, "channel {} matches ffmpeg's {best} at only {s:.1} dB", matched.len());
|
||||
matched.push(best);
|
||||
}
|
||||
matched.sort_unstable();
|
||||
if !ordered {
|
||||
matched.sort_unstable();
|
||||
}
|
||||
assert_eq!(matched, (0..c.channels).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user