use super::super::audio::{AdtsHeader, decode_sample_rate_index, parse_adts_header, synthesize_asc};
use super::super::{demux_ts, STREAM_TYPE_AAC_ADTS, STREAM_TYPE_MPEG2_VIDEO};
use super::{build_adts_header_7, build_adts_header_9, ts_pkt};
#[test]
fn adts_parser_decodes_canonical_lc_stereo_7byte_header() {
let h = build_adts_header_7(1, 3, 2, 107);
let parsed = parse_adts_header(&h).expect("must parse 7-byte ADTS header");
assert_eq!(parsed.profile, 1, "ADTS profile=1 LC");
assert_eq!(parsed.sampling_frequency_index, 3, "sr_idx=3 → 48kHz");
assert_eq!(parsed.channel_configuration, 2, "ch_cfg=2 stereo");
assert_eq!(parsed.frame_length, 107);
assert_eq!(parsed.header_len, 7, "protection_absent=1 → 7-byte header");
assert_eq!(
decode_sample_rate_index(parsed.sampling_frequency_index),
Some(48000)
);
}
#[test]
fn adts_parser_decodes_9byte_header_with_crc() {
let h = build_adts_header_9(1, 4, 2, 109);
let parsed = parse_adts_header(&h).expect("must parse 9-byte ADTS header");
assert_eq!(parsed.profile, 1);
assert_eq!(parsed.sampling_frequency_index, 4, "sr_idx=4 → 44.1kHz");
assert_eq!(parsed.channel_configuration, 2);
assert_eq!(parsed.frame_length, 109);
assert_eq!(
parsed.header_len, 9,
"protection_absent=0 → 9-byte header (incl CRC)"
);
assert_eq!(
decode_sample_rate_index(parsed.sampling_frequency_index),
Some(44100)
);
}
#[test]
fn adts_parser_decodes_aac_profile_bits_full_range() {
for profile in 0u8..=3 {
let h = build_adts_header_7(profile, 3, 2, 32);
let parsed =
parse_adts_header(&h).unwrap_or_else(|| panic!("must parse profile={profile}"));
assert_eq!(parsed.profile, profile);
}
}
#[test]
fn adts_parser_rejects_missing_sync() {
let mut h = build_adts_header_7(1, 3, 2, 32);
h[0] = 0x00;
assert!(parse_adts_header(&h).is_none());
}
#[test]
fn adts_parser_rejects_short_buffer() {
let h = build_adts_header_7(1, 3, 2, 32);
assert!(
parse_adts_header(&h[..6]).is_none(),
"<7 bytes can't carry a complete ADTS header"
);
}
#[test]
fn synthesize_asc_lc_stereo_48k_emits_0x1190() {
let adts = AdtsHeader {
profile: 1,
sampling_frequency_index: 3,
channel_configuration: 2,
frame_length: 0,
header_len: 7,
};
let asc = synthesize_asc(&adts);
assert_eq!(asc, [0x11, 0x90], "LC/48k/stereo → ASC 0x11 0x90");
}
#[test]
fn synthesize_asc_lc_mono_44k() {
let adts = AdtsHeader {
profile: 1,
sampling_frequency_index: 4,
channel_configuration: 1,
frame_length: 0,
header_len: 7,
};
assert_eq!(synthesize_asc(&adts), [0x12, 0x08]);
}
#[test]
fn synthesize_asc_main_aot_at_44k_5p1_rejected_at_channel_layer() {
let adts = AdtsHeader {
profile: 0,
sampling_frequency_index: 4,
channel_configuration: 6,
frame_length: 0,
header_len: 7,
};
assert_eq!(synthesize_asc(&adts), [0x0A, 0x30]);
}
#[test]
fn adts_strip_7byte_header_yields_payload_only() {
let mut frame = Vec::with_capacity(107);
frame.extend_from_slice(&build_adts_header_7(1, 3, 2, 107));
frame.extend_from_slice(&[0x42u8; 100]);
let header = parse_adts_header(&frame).unwrap();
assert_eq!(header.frame_length, 107);
let payload = &frame[header.header_len..header.frame_length];
assert_eq!(payload.len(), 100);
assert!(payload.iter().all(|b| *b == 0x42));
}
#[test]
fn adts_sample_rate_table_covers_documented_indices() {
assert_eq!(decode_sample_rate_index(0), Some(96000));
assert_eq!(decode_sample_rate_index(3), Some(48000));
assert_eq!(decode_sample_rate_index(4), Some(44100));
assert_eq!(decode_sample_rate_index(12), Some(7350));
assert!(decode_sample_rate_index(13).is_none(), "13 is reserved");
assert!(
decode_sample_rate_index(15).is_none(),
"15 (escape) not supported"
);
}
#[test]
fn demux_ts_yields_audio_track_when_pmt_advertises_aac() {
let mut pat = vec![0x00];
let pat_section_len: usize = 5 + 4 + 4;
pat.push(0xB0 | ((pat_section_len >> 8) & 0x0F) as u8);
pat.push((pat_section_len & 0xFF) as u8);
pat.extend_from_slice(&[0x00, 0x01, 0xC1, 0x00, 0x00]);
pat.extend_from_slice(&[0x00, 0x01, 0xE1, 0x00, 0u8, 0u8, 0u8, 0u8]);
let mut pat_payload = vec![0u8];
pat_payload.extend_from_slice(&pat);
let pat_pkt = ts_pkt(0x0000, true, 0b01, &pat_payload);
let mut pmt = vec![0x02];
let pmt_section_len: usize = 9 + 5 + 5 + 4; pmt.push(0xB0 | ((pmt_section_len >> 8) & 0x0F) as u8);
pmt.push((pmt_section_len & 0xFF) as u8);
pmt.extend_from_slice(&[0x00, 0x01, 0xC1, 0x00, 0x00]);
pmt.extend_from_slice(&[0xE2, 0x00]); pmt.extend_from_slice(&[0xF0, 0x00]); pmt.extend_from_slice(&[STREAM_TYPE_MPEG2_VIDEO, 0xE2, 0x00, 0xF0, 0x00]);
pmt.extend_from_slice(&[STREAM_TYPE_AAC_ADTS, 0xE3, 0x00, 0xF0, 0x00]);
pmt.extend_from_slice(&[0u8; 4]); let mut pmt_payload = vec![0u8];
pmt_payload.extend_from_slice(&pmt);
let pmt_pkt = ts_pkt(0x0100, true, 0b01, &pmt_payload);
let video_pes = {
let mut pes = vec![
0u8, 0u8, 1u8, 0xE0, 0u8, 0u8, 0x80, 0x80, 5, 0x21, 0x00, 0x01, 0x00, 0x01,
];
pes.extend_from_slice(&[0xAAu8; 16]);
pes
};
let video_pkt = ts_pkt(0x0200, true, 0b01, &video_pes);
let mut adts_stream = Vec::new();
for fill in [0xCCu8, 0xDDu8] {
adts_stream.extend_from_slice(&build_adts_header_7(1, 3, 2, 39));
adts_stream.extend_from_slice(&[fill; 32]);
}
let audio_pes = {
let mut pes = vec![
0u8, 0u8, 1u8, 0xC0, 0u8, 0u8, 0x80, 0x80, 5, 0x21, 0x00, 0x01, 0x00, 0x01,
];
pes.extend_from_slice(&adts_stream);
pes
};
let audio_pkt = ts_pkt(0x0300, true, 0b01, &audio_pes);
let mut buf = Vec::new();
buf.extend_from_slice(&pat_pkt);
buf.extend_from_slice(&pmt_pkt);
buf.extend_from_slice(&video_pkt);
buf.extend_from_slice(&audio_pkt);
buf.extend_from_slice(&ts_pkt(0x1FFF, false, 0b01, &[]));
let d = demux_ts(&buf).expect("demux must succeed");
assert_eq!(d.codec, "mpeg2");
let audio = d.audio.expect("AAC audio track must be surfaced");
assert_eq!(audio.codec, "aac");
assert_eq!(audio.channels, 2, "ch_cfg=2 stereo");
assert_eq!(audio.sample_rate, 48000, "sr_idx=3 → 48k");
assert_eq!(audio.timescale, 48000, "AAC timescale = sample_rate");
assert_eq!(
audio.asc,
vec![0x11, 0x90],
"synthesized ASC for LC/48k/stereo"
);
assert_eq!(audio.samples.len(), 2, "two ADTS frames → two samples");
assert_eq!(
audio.samples[0].len(),
32,
"32-byte payload after 7-byte header strip"
);
assert!(audio.samples[0].iter().all(|b| *b == 0xCC));
assert!(audio.samples[1].iter().all(|b| *b == 0xDD));
assert_eq!(
audio.durations,
vec![1024, 1024],
"AAC-LC frame duration = 1024 ticks @ sample-rate timescale"
);
}
#[test]
fn demux_ts_emits_audio_none_when_no_aac_stream_in_pmt() {
let mut buf = Vec::new();
let mut pat = vec![0x00];
let pat_section_len: usize = 5 + 4 + 4;
pat.push(0xB0 | ((pat_section_len >> 8) & 0x0F) as u8);
pat.push((pat_section_len & 0xFF) as u8);
pat.extend_from_slice(&[0x00, 0x01, 0xC1, 0x00, 0x00]);
pat.extend_from_slice(&[0x00, 0x01, 0xE1, 0x00, 0u8, 0u8, 0u8, 0u8]);
let mut pat_payload = vec![0u8];
pat_payload.extend_from_slice(&pat);
buf.extend_from_slice(&ts_pkt(0x0000, true, 0b01, &pat_payload));
let mut pmt = vec![0x02];
let pmt_section_len: usize = 9 + 5 + 4;
pmt.push(0xB0 | ((pmt_section_len >> 8) & 0x0F) as u8);
pmt.push((pmt_section_len & 0xFF) as u8);
pmt.extend_from_slice(&[0x00, 0x01, 0xC1, 0x00, 0x00]);
pmt.extend_from_slice(&[0xE2, 0x00, 0xF0, 0x00]);
pmt.extend_from_slice(&[STREAM_TYPE_MPEG2_VIDEO, 0xE2, 0x00, 0xF0, 0x00]);
pmt.extend_from_slice(&[0u8; 4]);
let mut pmt_payload = vec![0u8];
pmt_payload.extend_from_slice(&pmt);
buf.extend_from_slice(&ts_pkt(0x0100, true, 0b01, &pmt_payload));
let video_pes = {
let mut pes = vec![
0u8, 0u8, 1u8, 0xE0, 0u8, 0u8, 0x80, 0x80, 5, 0x21, 0x00, 0x01, 0x00, 0x01,
];
pes.extend_from_slice(&[0xAAu8; 16]);
pes
};
buf.extend_from_slice(&ts_pkt(0x0200, true, 0b01, &video_pes));
buf.extend_from_slice(&ts_pkt(0x1FFF, false, 0b01, &[]));
let d = demux_ts(&buf).expect("demux");
assert!(
d.audio.is_none(),
"PMT without AAC-ADTS stream → no audio track surfaced"
);
}