use crate::error::PacketSinkError;
pub(crate) struct AacRuntime {
asc: Vec<u8>,
audio_object_type: u32,
pce_channels: Option<u32>,
ps_doubles_mono_core: bool,
}
impl AacRuntime {
pub(crate) fn from_extradata(
extradata: &[u8],
stream_index: usize,
) -> Result<Self, PacketSinkError> {
let prefix = parse_required_asc_prefix(extradata).map_err(|reason| {
PacketSinkError::InvalidExtradata {
stream_index,
reason,
}
})?;
Ok(Self {
asc: extradata.to_vec(),
audio_object_type: prefix.audio_object_type,
pce_channels: prefix.pce_channels,
ps_doubles_mono_core: prefix.ps_doubles_mono_core,
})
}
pub(crate) fn check_new_extradata(
&self,
bytes: &[u8],
stream_index: usize,
) -> Result<(), PacketSinkError> {
if bytes != self.asc.as_slice() {
return Err(PacketSinkError::ConfigChange {
stream_index,
what: "NEW_EXTRADATA differs from the stream configuration".to_string(),
});
}
Ok(())
}
pub(crate) fn codec_string(&self) -> String {
format!("mp4a.40.{}", self.audio_object_type)
}
pub(crate) fn pce_channel_count(&self) -> Option<u32> {
self.pce_channels
}
pub(crate) fn ps_doubles_mono_core(&self) -> bool {
self.ps_doubles_mono_core
}
}
struct AscBits<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> AscBits<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn bit_len(&self) -> u64 {
self.data.len() as u64 * 8
}
fn remaining(&self) -> i64 {
self.bit_len() as i64 - self.pos as i64
}
fn peek(&self, count: usize) -> u32 {
let mut value = 0u32;
for offset in 0..count {
let pos = self.pos as u64 + offset as u64;
let bit = if pos < self.bit_len() {
(self.data[(pos / 8) as usize] >> (7 - pos % 8)) & 1
} else {
0
};
value = (value << 1) | bit as u32;
}
value
}
fn read(&mut self, count: usize, field: &str) -> Result<u32, String> {
debug_assert!(count <= 32);
if self.pos as u64 + count as u64 > self.bit_len() {
return Err(format!(
"AudioSpecificConfig truncated inside {field} ({} bits present, {} required)",
self.bit_len(),
self.pos as u64 + count as u64
));
}
let mut value = 0u32;
for _ in 0..count {
let byte = self.data[self.pos / 8];
let bit = (byte >> (7 - self.pos % 8)) & 1;
value = (value << 1) | bit as u32;
self.pos += 1;
}
Ok(value)
}
fn read_padded(&mut self, count: usize) -> u32 {
debug_assert!(count <= 32);
let value = self.peek(count);
self.pos += count;
value
}
}
fn read_object_type(bits: &mut AscBits<'_>, field: &str, ext_field: &str) -> Result<u32, String> {
let aot = bits.read(5, field)?;
if aot == 31 {
return Ok(32 + bits.read(6, ext_field)?);
}
Ok(aot)
}
const SAMPLE_RATES: [u32; 16] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0,
0,
];
fn read_sampling_frequency(
bits: &mut AscBits<'_>,
field: &str,
explicit_field: &str,
) -> Result<(u32, u32), String> {
let index = bits.read(4, field)?;
if index == 13 || index == 14 {
return Err(format!("reserved {field} {index}"));
}
let rate = if index == 15 {
bits.read(24, explicit_field)?
} else {
SAMPLE_RATES[index as usize]
};
Ok((index, rate))
}
fn is_ga_object_type(aot: u32) -> bool {
matches!(aot, 1..=4 | 6 | 7 | 17 | 19..=23)
}
fn read_ga_specific_config(
bits: &mut AscBits<'_>,
aot: u32,
channel_config: u32,
sampling_index: u32,
) -> Result<Option<PceLayout>, String> {
bits.read(1, "frameLengthFlag")?;
if bits.read(1, "dependsOnCoreCoder")? == 1 {
bits.read(14, "coreCoderDelay")?;
}
let extension_flag = bits.read(1, "extensionFlag")?;
let pce = if channel_config == 0 {
bits.read(4, "element_instance_tag")?;
Some(read_program_config_element(bits, sampling_index)?)
} else {
None
};
if aot == 6 || aot == 20 {
bits.read(3, "layerNr")?;
}
if extension_flag == 1 {
if aot == 22 {
bits.read(5, "numOfSubFrame")?;
bits.read(11, "layer_length")?;
}
if matches!(aot, 17 | 19 | 20 | 23) {
let section = bits.read(1, "aacSectionDataResilienceFlag")?;
let scalefactor = bits.read(1, "aacScalefactorDataResilienceFlag")?;
let spectral = bits.read(1, "aacSpectralDataResilienceFlag")?;
let res_flags = (section << 2) | (scalefactor << 1) | spectral;
if matches!(aot, 17 | 23) && res_flags != 0 {
return Err(format!(
"data resilience flags {res_flags:#x} select \
error-resilient payload syntax FFmpeg's decoder \
refuses as unimplemented (decode_ga_specific_config, \
libavcodec/aac/aacdec.c)"
));
}
}
bits.read(1, "extensionFlag3")?;
}
Ok(pce)
}
struct PceLayout {
channels: u32,
sole_element_is_sce: bool,
}
fn read_program_config_element(
bits: &mut AscBits<'_>,
asc_sampling_index: u32,
) -> Result<PceLayout, String> {
bits.read(2, "PCE object_type")?;
let pce_sampling_index = bits.read(4, "PCE sampling_frequency_index")?;
if pce_sampling_index == 13 || pce_sampling_index == 14 {
return Err(format!(
"reserved PCE sampling_frequency_index {pce_sampling_index}"
));
}
if pce_sampling_index != asc_sampling_index {
return Err(format!(
"PCE sampling_frequency_index {pce_sampling_index} contradicts \
samplingFrequencyIndex {asc_sampling_index}"
));
}
let num_front = bits.read(4, "num_front_channel_elements")?;
let num_side = bits.read(4, "num_side_channel_elements")?;
let num_back = bits.read(4, "num_back_channel_elements")?;
let num_lfe = bits.read(2, "num_lfe_channel_elements")?;
let num_assoc_data = bits.read(3, "num_assoc_data_elements")?;
let num_cc = bits.read(4, "num_valid_cc_elements")?;
if bits.read(1, "mono_mixdown_present")? == 1 {
bits.read(4, "mono_mixdown_element_number")?;
}
if bits.read(1, "stereo_mixdown_present")? == 1 {
bits.read(4, "stereo_mixdown_element_number")?;
}
if bits.read(1, "matrix_mixdown_idx_present")? == 1 {
bits.read(2, "matrix_mixdown_idx")?;
bits.read(1, "pseudo_surround_enable")?;
}
let mut channels = 0u32;
let mut sce_elements = 0u32;
for _ in 0..num_front {
let is_cpe = bits.read(1, "front_element_is_cpe")?;
channels += 1 + is_cpe;
sce_elements += 1 - is_cpe;
bits.read(4, "front_element_tag_select")?;
}
for _ in 0..num_side {
let is_cpe = bits.read(1, "side_element_is_cpe")?;
channels += 1 + is_cpe;
sce_elements += 1 - is_cpe;
bits.read(4, "side_element_tag_select")?;
}
for _ in 0..num_back {
let is_cpe = bits.read(1, "back_element_is_cpe")?;
channels += 1 + is_cpe;
sce_elements += 1 - is_cpe;
bits.read(4, "back_element_tag_select")?;
}
for _ in 0..num_lfe {
channels += 1;
bits.read(4, "lfe_element_tag_select")?;
}
for _ in 0..num_assoc_data {
bits.read(4, "assoc_data_element_tag_select")?;
}
for _ in 0..num_cc {
bits.read(1, "cc_element_is_ind_sw")?;
bits.read(4, "valid_cc_element_tag_select")?;
}
let misalignment = bits.pos % 8;
if misalignment != 0 {
bits.read(8 - misalignment, "PCE byte alignment")?;
}
let comment_bytes = bits.read(8, "comment_field_bytes")?;
for _ in 0..comment_bytes {
bits.read(8, "comment_field_data")?;
}
if channels == 0 {
return Err("program_config_element declares zero output channels".to_string());
}
Ok(PceLayout {
channels,
sole_element_is_sce: channels == 1 && sce_elements == 1,
})
}
struct AscPrefix {
audio_object_type: u32,
pce_channels: Option<u32>,
ps_doubles_mono_core: bool,
}
fn read_padded_object_type(bits: &mut AscBits<'_>) -> u32 {
let aot = bits.read_padded(5);
if aot == 31 {
return 32 + bits.read_padded(6);
}
aot
}
fn read_padded_sampling_frequency(bits: &mut AscBits<'_>) -> u32 {
let index = bits.read_padded(4);
if index == 15 {
bits.read_padded(24)
} else {
SAMPLE_RATES[index as usize]
}
}
fn read_tail_sync_extension(
bits: &mut AscBits<'_>,
sample_rate: u32,
sbr: &mut Option<bool>,
ps: &mut Option<bool>,
) {
while bits.remaining() > 15 {
if bits.peek(11) != 0x2b7 {
bits.read_padded(1);
continue;
}
bits.read_padded(11);
let extension_aot = read_padded_object_type(bits);
if extension_aot == 5 {
*sbr = Some(bits.read_padded(1) == 1);
if *sbr == Some(true) {
let extension_rate = read_padded_sampling_frequency(bits);
if extension_rate == sample_rate {
*sbr = None;
}
}
}
if bits.remaining() > 11 && bits.read_padded(11) == 0x548 {
*ps = Some(bits.read_padded(1) == 1);
}
break;
}
}
fn check_channel_config(value: u32, field: &str) -> Result<(), String> {
match value {
8..=10 => Err(format!("reserved {field} {value}")),
15 => Err(format!("{field} 15 is outside the channel table")),
_ => Ok(()),
}
}
fn parse_required_asc_prefix(asc: &[u8]) -> Result<AscPrefix, String> {
let mut bits = AscBits::new(asc);
let aot = read_object_type(&mut bits, "audioObjectType", "audioObjectTypeExt")?;
if aot == 0 {
return Err("AudioSpecificConfig declares the null audio object type".to_string());
}
let (sampling_index, sample_rate) = read_sampling_frequency(
&mut bits,
"samplingFrequencyIndex",
"explicit samplingFrequency",
)?;
let channel_config = bits.read(4, "channelConfiguration")?;
check_channel_config(channel_config, "channelConfiguration")?;
let parse_extension = match aot {
5 => true,
29 => !(bits.peek(3) & 0x03 != 0 && bits.peek(9) & 0x3F == 0),
_ => false,
};
let mut sbr: Option<bool> = None;
let mut ps: Option<bool> = None;
let mut base_object_type = aot;
if parse_extension {
if aot == 29 {
ps = Some(true);
}
sbr = Some(true);
read_sampling_frequency(
&mut bits,
"extensionSamplingFrequencyIndex",
"explicit extensionSamplingFrequency",
)?;
let secondary = read_object_type(
&mut bits,
"extension audioObjectType",
"extension audioObjectTypeExt",
)?;
if secondary == 0 {
return Err("extension audioObjectType is the null object type".to_string());
}
if secondary == 22 {
let extension_channel_config = bits.read(4, "extensionChannelConfiguration")?;
check_channel_config(extension_channel_config, "extensionChannelConfiguration")?;
}
base_object_type = secondary;
}
let ga_config_start = bits.pos;
let pce = if is_ga_object_type(base_object_type) {
let pce =
read_ga_specific_config(&mut bits, base_object_type, channel_config, sampling_index)?;
if matches!(base_object_type, 17 | 19..=23) {
let ep_config = bits.read(2, "epConfig")?;
if ep_config >= 2 {
return Err(format!(
"epConfig {ep_config} is followed by an \
ErrorProtectionSpecificConfig this parser cannot bound"
));
}
}
pce
} else if channel_config == 0 {
return Err(
"channelConfiguration 0 defers the layout to a program_config_element, \
which only a General Audio configuration carries"
.to_string(),
);
} else {
None
};
let pce_channels = pce.as_ref().map(|layout| layout.channels);
let mono_sce_core = pce
.as_ref()
.is_some_and(|layout| layout.sole_element_is_sce);
if !parse_extension && mono_sce_core {
let frame_length_flag_bits = if matches!(base_object_type, 1..=4 | 17 | 23) {
1
} else {
0
};
let mut scan_bits = AscBits::new(asc);
scan_bits.pos = ga_config_start + frame_length_flag_bits;
read_tail_sync_extension(&mut scan_bits, sample_rate, &mut sbr, &mut ps);
}
if sbr == Some(false) {
ps = Some(false);
}
if ps.is_none() && base_object_type != 2 {
ps = Some(false);
}
let ps_doubles_mono_core =
mono_sce_core && (ps == Some(true) || (sbr == Some(true) && ps.is_none()));
Ok(AscPrefix {
audio_object_type: aot,
pce_channels,
ps_doubles_mono_core,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codec_string_reads_the_audio_object_type() {
let runtime = AacRuntime::from_extradata(&[0x12, 0x10], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
let runtime = AacRuntime::from_extradata(&[0x12, 0x12, 0x00, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
let runtime = AacRuntime::from_extradata(&[0x29, 0x94, 0x08, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.5");
let runtime =
AacRuntime::from_extradata(&[0x29, 0x94, 0x58, 0x48, 0x00, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.5");
let runtime = AacRuntime::from_extradata(&[0x12, 0x58], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
let runtime = AacRuntime::from_extradata(&[0x32, 0x10, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.6");
let runtime = AacRuntime::from_extradata(&[0x8A, 0x09, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.17");
let runtime = AacRuntime::from_extradata(&[0x8A, 0x10, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.17");
let runtime = AacRuntime::from_extradata(&[0x29, 0x94, 0x58, 0x40], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.5");
let runtime = AacRuntime::from_extradata(&[0xEA, 0x0A, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.29");
let runtime = AacRuntime::from_extradata(&[0xEA, 0x0A], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.29");
let runtime = AacRuntime::from_extradata(&[0xF8, 0x48, 0x20], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.34");
}
#[test]
fn per_field_truncation_is_rejected_typed() {
let cases: [(&[u8], &str); 17] = [
(&[], "audioObjectType"),
(&[0x12], "samplingFrequencyIndex"),
(&[0xF8], "audioObjectTypeExt"),
(&[0xF8, 0x40], "channelConfiguration"),
(&[0x17, 0x80], "explicit samplingFrequency"),
(&[0x28, 0x10], "extensionSamplingFrequencyIndex"),
(&[0x29, 0x94, 0x7C], "extension audioObjectTypeExt"),
(&[0x29, 0x94, 0x58], "extensionChannelConfiguration"),
(&[0x12, 0x12], "coreCoderDelay"),
(&[0x29, 0x94, 0x08], "extensionFlag"),
(&[0x29, 0x94, 0x09, 0x00], "coreCoderDelay"),
(&[0x12, 0x11], "extensionFlag3"),
(&[0x32, 0x10], "layerNr"),
(&[0x8A, 0x09], "aacSectionDataResilienceFlag"),
(&[0x8A, 0x08], "epConfig"),
(&[0xB2, 0x0A, 0x00, 0x04, 0x00, 0x00], "epConfig"),
(&[0x29, 0x94, 0x58, 0x48], "numOfSubFrame"),
];
for (bad, field) in cases {
match AacRuntime::from_extradata(bad, 3) {
Err(PacketSinkError::InvalidExtradata {
stream_index: 3,
reason,
}) => {
assert!(
reason.contains(field),
"{bad:02X?}: expected truncation inside {field:?}, got {reason:?}"
);
}
Err(other) => panic!("{bad:02X?}: expected truncation, got {other:?}"),
Ok(_) => panic!("{bad:02X?}: expected InvalidExtradata, got a valid runtime"),
}
}
match AacRuntime::from_extradata(&[0x28, 0x17, 0x80], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("explicit extensionSamplingFrequency"));
}
Err(other) => panic!("expected truncation, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
assert!(matches!(
AacRuntime::from_extradata(&[0x00, 0x10], 3),
Err(PacketSinkError::InvalidExtradata { .. })
));
match AacRuntime::from_extradata(&[0x29, 0x94, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("null object type"), "got {reason:?}");
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
}
#[test]
fn reserved_and_out_of_table_field_values_are_rejected() {
match AacRuntime::from_extradata(&[0x16, 0x90], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("reserved samplingFrequencyIndex 13"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0x12, 0x78], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("channelConfiguration 15"), "got {reason:?}");
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
for (bad, value) in [
(&[0x12u8, 0x40][..], 8),
(&[0x12, 0x48][..], 9),
(&[0x12, 0x50][..], 10),
] {
match AacRuntime::from_extradata(bad, 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains(&format!("reserved channelConfiguration {value}")),
"{bad:02X?}: got {reason:?}"
);
}
Err(other) => panic!("{bad:02X?}: expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("{bad:02X?}: expected InvalidExtradata, got a valid runtime"),
}
}
match AacRuntime::from_extradata(&[0x29, 0x94, 0x5B, 0xC0], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("extensionChannelConfiguration 15"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0x29, 0x94, 0x5A, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("reserved extensionChannelConfiguration 8"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0x29, 0x96, 0x88], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("reserved extensionSamplingFrequencyIndex 13"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0x8A, 0x09, 0x08], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("epConfig 2"), "got {reason:?}");
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
}
#[test]
fn nonzero_resilience_flags_are_rejected_for_decodable_er_types() {
let runtime = AacRuntime::from_extradata(&[0x8A, 0x09, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.17");
let runtime = AacRuntime::from_extradata(&[0xBA, 0x09, 0x00], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.23");
let rejected: [(&[u8], &str); 5] = [
(&[0x8A, 0x09, 0x80], "0x4"),
(&[0x8A, 0x09, 0x40], "0x2"),
(&[0x8A, 0x09, 0x20], "0x1"),
(&[0x8A, 0x09, 0xE0], "0x7"),
(&[0xBA, 0x09, 0x20], "0x1"),
];
for (bad, flags) in rejected {
match AacRuntime::from_extradata(bad, 3) {
Err(PacketSinkError::InvalidExtradata {
stream_index: 3,
reason,
}) => {
assert!(
reason.contains(&format!("data resilience flags {flags}"))
&& reason.contains("aacdec.c"),
"{bad:02X?}: got {reason:?}"
);
}
Err(other) => panic!("{bad:02X?}: expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("{bad:02X?}: expected InvalidExtradata, got a valid runtime"),
}
}
let runtime = AacRuntime::from_extradata(&[0x9A, 0x09, 0xE0], 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.19");
}
#[test]
fn channel_configuration_zero_takes_the_layout_from_the_pce() {
let ffmpeg_quad: [u8; 16] = [
0x11, 0x80, 0x04, 0xC4, 0x04, 0x00, 0x21, 0x10, 0x04, 0x4C, 0x61, 0x76, 0x63, 0x56,
0xE5, 0x00,
];
let runtime = AacRuntime::from_extradata(&ffmpeg_quad, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
assert_eq!(runtime.pce_channel_count(), Some(4));
let minimal_stereo: [u8; 8] = [0x12, 0x00, 0x05, 0x04, 0x00, 0x00, 0x20, 0x00];
let runtime = AacRuntime::from_extradata(&minimal_stereo, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
assert_eq!(runtime.pce_channel_count(), Some(2));
let scalable_pce: [u8; 9] = [0x32, 0x00, 0x05, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00];
let runtime = AacRuntime::from_extradata(&scalable_pce, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.6");
assert_eq!(runtime.pce_channel_count(), Some(2));
match AacRuntime::from_extradata(&[0x12, 0x00, 0x04, 0xC4, 0x00, 0x00, 0x20, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("PCE sampling_frequency_index 3")
&& reason.contains("samplingFrequencyIndex 4"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0x12, 0x00, 0x07, 0x44, 0x00, 0x00, 0x20, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("reserved PCE sampling_frequency_index 13"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
let runtime = AacRuntime::from_extradata(&[0x12, 0x10], 0).unwrap();
assert_eq!(runtime.pce_channel_count(), None);
match AacRuntime::from_extradata(&[0x12, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("element_instance_tag"), "got {reason:?}");
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
let zero_channels: [u8; 8] = [0x12, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00];
match AacRuntime::from_extradata(&zero_channels, 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(reason.contains("zero output channels"), "got {reason:?}");
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
match AacRuntime::from_extradata(&[0xF8, 0x48, 0x00], 3) {
Err(PacketSinkError::InvalidExtradata { reason, .. }) => {
assert!(
reason.contains("channelConfiguration 0")
&& reason.contains("program_config_element"),
"got {reason:?}"
);
}
Err(other) => panic!("expected InvalidExtradata, got {other:?}"),
Ok(_) => panic!("expected InvalidExtradata, got a valid runtime"),
}
}
#[test]
fn parametric_stereo_doubles_the_mono_core_pce() {
let direct_ps: [u8; 9] = [0xEB, 0x82, 0x08, 0x02, 0xE2, 0x00, 0x00, 0x00, 0x00];
let runtime = AacRuntime::from_extradata(&direct_ps, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.29");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
let sync_extension_ps: [u8; 13] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x00, 0x56, 0xE5, 0x9D, 0x48, 0x80,
];
let runtime = AacRuntime::from_extradata(&sync_extension_ps, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
let sync_extension_no_ps: [u8; 13] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x00, 0x56, 0xE5, 0x9D, 0x48, 0x00,
];
let runtime = AacRuntime::from_extradata(&sync_extension_no_ps, 0).unwrap();
assert!(!runtime.ps_doubles_mono_core());
let comment_embedded_ps: [u8; 13] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x05, 0x56, 0xE5, 0x9D, 0x48, 0x80,
];
let runtime = AacRuntime::from_extradata(&comment_embedded_ps, 0).unwrap();
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
let comment_preempts_tail: [u8; 17] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x04, 0x56, 0xE5, 0x00, 0x00, 0x56, 0xE5,
0x9D, 0x48, 0x80,
];
let runtime = AacRuntime::from_extradata(&comment_preempts_tail, 0).unwrap();
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(!runtime.ps_doubles_mono_core());
let sbr_mono: [u8; 9] = [0x2B, 0x01, 0x88, 0x02, 0xC2, 0x00, 0x00, 0x00, 0x00];
let runtime = AacRuntime::from_extradata(&sbr_mono, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.5");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
let ps_stereo_core: [u8; 9] = [0xEB, 0x82, 0x08, 0x02, 0xE2, 0x00, 0x00, 0x10, 0x00];
let runtime = AacRuntime::from_extradata(&ps_stereo_core, 0).unwrap();
assert_eq!(runtime.pce_channel_count(), Some(2));
assert!(!runtime.ps_doubles_mono_core());
let plain_mono: [u8; 8] = [0x12, 0x00, 0x05, 0x04, 0x00, 0x00, 0x00, 0x00];
let runtime = AacRuntime::from_extradata(&plain_mono, 0).unwrap();
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(!runtime.ps_doubles_mono_core());
let ps_lfe_core: [u8; 9] = [0xEB, 0x82, 0x08, 0x02, 0xE0, 0x00, 0x80, 0x00, 0x00];
let runtime = AacRuntime::from_extradata(&ps_lfe_core, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.29");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(!runtime.ps_doubles_mono_core());
}
#[test]
fn sync_extension_scan_reads_zero_padded_and_never_fails() {
let explicit_frequency_past_end: [u8; 11] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x03, 0x56, 0xE5, 0xF8,
];
let runtime = AacRuntime::from_extradata(&explicit_frequency_past_end, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
let reserved_extension_index: [u8; 11] = [
0x13, 0x00, 0x05, 0x84, 0x00, 0x00, 0x00, 0x03, 0x56, 0xE5, 0xE8,
];
let runtime = AacRuntime::from_extradata(&reserved_extension_index, 0).unwrap();
assert_eq!(runtime.codec_string(), "mp4a.40.2");
assert_eq!(runtime.pce_channel_count(), Some(1));
assert!(runtime.ps_doubles_mono_core());
}
#[test]
fn redundant_asc_passes_and_change_errors() {
let runtime = AacRuntime::from_extradata(&[0x12, 0x10], 1).unwrap();
assert!(runtime.check_new_extradata(&[0x12, 0x10], 1).is_ok());
assert!(matches!(
runtime.check_new_extradata(&[0x11, 0x90], 1),
Err(PacketSinkError::ConfigChange { stream_index: 1, .. })
));
}
}