use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsAudioMode, TsAudioStream};
const AAC_ID: [&str; 2] = ["MPEG-4", "MPEG-2"];
const AAC_SAMPLE_RATES: [i32; 16] = [
96_000, 88_200, 64_000, 48_000, 44_100, 32_000, 24_000, 22_050, 16_000, 12_000, 11_025, 8_000,
7_350, 0, 0, 0,
];
const AAC_CHANNELS: [i32; 8] = [0, 1, 2, 3, 4, 5, 6, 8];
const AAC_CHANNEL_MODES: [TsAudioMode; 8] = [
TsAudioMode::Unknown,
TsAudioMode::Mono,
TsAudioMode::Stereo,
TsAudioMode::Extended,
TsAudioMode::Surround,
TsAudioMode::Surround,
TsAudioMode::Surround,
TsAudioMode::Surround,
];
const fn get_aac_profile(profile_type: u16) -> &'static str {
match profile_type {
0 => "AAC Main",
1 => "AAC LC",
2 => "AAC SSR",
3 => "AAC LTP",
16 => "ER AAC LC",
18 => "ER AAC LTP",
36 => "SLS",
_ => "",
}
}
pub fn scan(stream: &mut TsAudioStream, buffer: &mut TsStreamBuffer, tag: &mut Option<String>) {
let _ = tag;
if stream.base.is_initialized {
return;
}
let sync_word = buffer.read_bits2(12, false);
if sync_word != 0b1111_1111_1111 {
return;
}
let audio_version_id = buffer.read_bits2(1, false);
let _ = buffer.read_bits2(2, false); let _ = buffer.read_bool(false); let profile_object_type = buffer.read_bits2(2, false);
let sampling_rate_index = buffer.read_bits2(4, false);
let _ = buffer.read_bool(false); let channel_mode = buffer.read_bits2(3, false);
let _ = buffer.read_bool(false); let _ = buffer.read_bool(false);
stream.sample_rate =
AAC_SAMPLE_RATES.get(usize::from(sampling_rate_index)).copied().unwrap_or(0);
stream.audio_mode =
AAC_CHANNEL_MODES.get(usize::from(channel_mode)).copied().unwrap_or(TsAudioMode::Unknown);
stream.channel_count = AAC_CHANNELS.get(usize::from(channel_mode)).copied().unwrap_or(0);
if channel_mode == 7 {
stream.channel_count = stream.channel_count.wrapping_sub(1);
stream.lfe = 1;
} else {
stream.lfe = 0;
}
let id = AAC_ID.get(usize::from(audio_version_id)).copied().unwrap_or("");
stream.ext_data = Some(format!("{id} {}", get_aac_profile(profile_object_type)));
stream.base.is_vbr = true;
stream.base.is_initialized = true;
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, proptest};
use super::{get_aac_profile, scan};
use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsAudioMode, TsAudioStream, TsStreamType};
fn buf(data: &[u8]) -> TsStreamBuffer {
let mut b = TsStreamBuffer::new();
b.add(data, 0, data.len());
b.begin_read();
b
}
fn pack(fields: &[(u64, u32)]) -> Vec<u8> {
let mut bytes = Vec::new();
let mut cur: u8 = 0;
let mut nbits: u32 = 0;
for &(val, width) in fields {
let mut b = width;
while b > 0 {
b = b.wrapping_sub(1);
let bit = u8::try_from(val.wrapping_shr(b) & 1).unwrap_or(0);
cur = cur.wrapping_shl(1).wrapping_add(bit);
nbits = nbits.wrapping_add(1);
if nbits == 8 {
bytes.push(cur);
cur = 0;
nbits = 0;
}
}
}
if nbits > 0 {
bytes.push(cur.wrapping_shl(8_u32.wrapping_sub(nbits)));
}
bytes
}
fn aac_frame(version: u64, profile: u64, sr_idx: u64, chan: u64) -> Vec<u8> {
pack(&[
(0xFFF, 12), (version, 1), (0, 2), (0, 1), (profile, 2), (sr_idx, 4), (0, 1), (chan, 3), (0, 1), (0, 1), (0, 16), ])
}
fn run(
stream_type: TsStreamType,
version: u64,
profile: u64,
sr_idx: u64,
chan: u64,
) -> TsAudioStream {
let mut s = TsAudioStream::default();
s.base.stream_type = stream_type;
let mut b = buf(&aac_frame(version, profile, sr_idx, chan));
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
assert_eq!(tag, None, "AAC never sets the tag");
s
}
#[test]
fn decodes_mpeg4_aac_lc_stereo() {
let s = run(TsStreamType::Mpeg4AacAudio, 0, 1, 3, 2);
assert_eq!(s.sample_rate, 48_000);
assert_eq!(s.channel_count, 2);
assert_eq!(s.lfe, 0);
assert_eq!(s.audio_mode, TsAudioMode::Stereo);
assert_eq!(s.ext_data.as_deref(), Some("MPEG-4 AAC LC"));
assert!(s.base.is_vbr);
assert!(s.base.is_initialized);
assert_eq!(s.codec_short_name(), "MPEG-4 AAC");
assert_eq!(s.codec_name(), "MPEG-4 AAC LC");
assert_eq!(s.description(), "2.0 / 48 kHz");
}
#[test]
fn audio_version_and_profile_compose_extended_data() {
assert_eq!(
run(TsStreamType::Mpeg2AacAudio, 1, 0, 3, 2).ext_data.as_deref(),
Some("MPEG-2 AAC Main")
);
assert_eq!(
run(TsStreamType::Mpeg4AacAudio, 0, 0, 3, 2).ext_data.as_deref(),
Some("MPEG-4 AAC Main")
);
assert_eq!(
run(TsStreamType::Mpeg4AacAudio, 0, 2, 3, 2).ext_data.as_deref(),
Some("MPEG-4 AAC SSR")
);
assert_eq!(
run(TsStreamType::Mpeg4AacAudio, 0, 3, 3, 2).ext_data.as_deref(),
Some("MPEG-4 AAC LTP")
);
assert_eq!(run(TsStreamType::Mpeg2AacAudio, 1, 1, 3, 2).codec_short_name(), "MPEG-2 AAC");
}
#[test]
fn channel_mode_maps_count_mode_and_lfe() {
let cases = [
(0_u64, 0_i32, 0_i32, TsAudioMode::Unknown),
(1, 1, 0, TsAudioMode::Mono),
(2, 2, 0, TsAudioMode::Stereo),
(3, 3, 0, TsAudioMode::Extended),
(4, 4, 0, TsAudioMode::Surround),
(5, 5, 0, TsAudioMode::Surround),
(6, 6, 0, TsAudioMode::Surround),
(7, 7, 1, TsAudioMode::Surround),
];
for (chan, count, lfe, mode) in cases {
let s = run(TsStreamType::Mpeg4AacAudio, 0, 1, 3, chan);
assert_eq!((s.channel_count, s.lfe), (count, lfe), "channel mode {chan}");
assert_eq!(s.audio_mode, mode, "channel mode {chan}");
}
}
#[test]
fn sample_rate_index_folds_reserved_codes_to_zero() {
assert_eq!(run(TsStreamType::Mpeg4AacAudio, 0, 1, 0, 2).sample_rate, 96_000);
assert_eq!(run(TsStreamType::Mpeg4AacAudio, 0, 1, 12, 2).sample_rate, 7_350);
assert_eq!(run(TsStreamType::Mpeg4AacAudio, 0, 1, 13, 2).sample_rate, 0);
assert_eq!(run(TsStreamType::Mpeg4AacAudio, 0, 1, 14, 2).sample_rate, 0);
assert_eq!(run(TsStreamType::Mpeg4AacAudio, 0, 1, 15, 2).sample_rate, 0);
}
#[test]
fn get_aac_profile_covers_every_arm() {
assert_eq!(get_aac_profile(0), "AAC Main");
assert_eq!(get_aac_profile(1), "AAC LC");
assert_eq!(get_aac_profile(2), "AAC SSR");
assert_eq!(get_aac_profile(3), "AAC LTP");
assert_eq!(get_aac_profile(16), "ER AAC LC");
assert_eq!(get_aac_profile(18), "ER AAC LTP");
assert_eq!(get_aac_profile(36), "SLS");
assert_eq!(get_aac_profile(99), "");
}
#[test]
fn rejects_bad_sync_and_initialized_streams() {
let s = run(TsStreamType::Mpeg4AacAudio, 0, 1, 3, 2);
assert!(s.base.is_initialized); let mut s = TsAudioStream::default();
s.base.stream_type = TsStreamType::Mpeg4AacAudio;
let mut b = buf(&pack(&[(0xFFE, 12), (0, 28)])); scan(&mut s, &mut b, &mut None);
assert!(!s.base.is_initialized);
assert_eq!(s.ext_data, None);
let mut s = TsAudioStream::default();
s.base.stream_type = TsStreamType::Mpeg4AacAudio;
s.base.is_initialized = true;
let mut b = buf(&aac_frame(0, 1, 3, 2));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.channel_count, 0); assert_eq!(s.ext_data, None);
}
proptest! {
#[test]
fn scan_never_panics_on_arbitrary_bytes(data in any::<Vec<u8>>()) {
for ty in [TsStreamType::Mpeg2AacAudio, TsStreamType::Mpeg4AacAudio] {
let mut s = TsAudioStream::default();
s.base.stream_type = ty;
let mut b = buf(&data);
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
}
}
}
}