use anyhow::{Context, Result, bail};
use crate::ac3_sync::{
self, Eac3SyncInfo, SyncInfo, ac3_bit_rate_kbps, channel_count, eac3_sample_rate_hz,
eac3_samples_per_frame,
};
use crate::demux::AudioTrack;
use crate::mux::{dac3_body_from_sync, dec3_body_from_sync};
use super::{AudioCodecKind, AudioStreamInfo, TS_PACKET, TS_SYNC};
const AAC_SAMPLE_RATES: [u32; 13] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct AdtsHeader {
pub(super) profile: u8,
pub(super) sampling_frequency_index: u8,
pub(super) channel_configuration: u8,
pub(super) frame_length: usize,
pub(super) header_len: usize,
}
pub(super) fn parse_adts_header(buf: &[u8]) -> Option<AdtsHeader> {
if buf.len() < 7 {
return None;
}
if buf[0] != 0xFF || (buf[1] & 0xF0) != 0xF0 {
return None;
}
let protection_absent = (buf[1] & 0x01) != 0;
let header_len = if protection_absent { 7 } else { 9 };
if buf.len() < header_len {
return None;
}
let profile = (buf[2] >> 6) & 0x03;
let sampling_frequency_index = (buf[2] >> 2) & 0x0F;
let channel_configuration = ((buf[2] & 0x01) << 2) | ((buf[3] >> 6) & 0x03);
let frame_length =
(((buf[3] & 0x03) as usize) << 11) | ((buf[4] as usize) << 3) | ((buf[5] >> 5) as usize);
if frame_length < header_len {
return None;
}
Some(AdtsHeader {
profile,
sampling_frequency_index,
channel_configuration,
frame_length,
header_len,
})
}
pub(super) fn decode_sample_rate_index(idx: u8) -> Option<u32> {
AAC_SAMPLE_RATES.get(idx as usize).copied()
}
pub(super) fn synthesize_asc(adts: &AdtsHeader) -> [u8; 2] {
let aot = adts.profile + 1; let sr_idx = adts.sampling_frequency_index;
let ch_cfg = adts.channel_configuration;
let mut bits: u16 = 0;
bits |= ((aot as u16) & 0x1F) << 11;
bits |= ((sr_idx as u16) & 0x0F) << 7;
bits |= ((ch_cfg as u16) & 0x0F) << 3;
bits.to_be_bytes()
}
fn find_adts_sync(es: &[u8], from: usize) -> Option<usize> {
let mut i = from;
while i + 1 < es.len() {
if es[i] == 0xFF && (es[i + 1] & 0xF0) == 0xF0 {
return Some(i);
}
i += 1;
}
None
}
fn extract_ts_aac_audio(
data: &[u8],
packets: usize,
packet_stride: usize,
prefix_len: usize,
audio_pid: u16,
) -> Result<Option<AudioTrack>> {
let es = reassemble_audio_pes(data, packets, packet_stride, prefix_len, audio_pid);
if es.is_empty() {
return Ok(None);
}
let mut cursor = match find_adts_sync(&es, 0) {
Some(idx) => idx,
None => return Ok(None),
};
let first = parse_adts_header(&es[cursor..]).context("TS: first ADTS frame failed to parse")?;
let sample_rate = decode_sample_rate_index(first.sampling_frequency_index)
.context("TS: AAC sampling_frequency_index out of range")?;
let channels = first.channel_configuration as u16;
if channels == 0 {
bail!("TS: AAC channel_configuration=0 (PCE-defined); not supported");
}
let asc = synthesize_asc(&first).to_vec();
let mut samples: Vec<Vec<u8>> = Vec::new();
let mut durations: Vec<u32> = Vec::new();
while cursor < es.len() {
let Some(found) = find_adts_sync(&es, cursor) else {
break;
};
cursor = found;
let Some(hdr) = parse_adts_header(&es[cursor..]) else {
break;
};
if hdr.sampling_frequency_index != first.sampling_frequency_index
|| hdr.channel_configuration != first.channel_configuration
{
tracing::warn!(
"TS: AAC ADTS stream switched sr_idx/ch_cfg mid-stream; truncating audio at frame {}",
samples.len()
);
break;
}
let end = cursor + hdr.frame_length;
if end > es.len() {
break;
}
let payload_start = cursor + hdr.header_len;
if payload_start > end {
break;
}
samples.push(es[payload_start..end].to_vec());
durations.push(1024);
cursor = end;
}
if samples.is_empty() {
return Ok(None);
}
Ok(Some(AudioTrack {
codec: "aac".into(),
samples,
sample_rate,
channels,
asc,
codec_private: Vec::new(),
timescale: sample_rate,
durations,
}))
}
fn find_ac3_sync(es: &[u8], from: usize) -> Option<usize> {
let mut i = from;
while i + 1 < es.len() {
if es[i] == 0x0B && es[i + 1] == 0x77 {
return Some(i);
}
i += 1;
}
None
}
fn ac3_frame_size(brc: u8, fscod: u8, frmsizecod_low_bit: u8) -> Option<usize> {
let kbps = ac3_bit_rate_kbps(brc) as usize;
if kbps == 0 {
return None;
}
let sr = ac3_sync::ac3_sample_rate_hz(fscod) as usize;
if sr == 0 {
return None;
}
let base = (kbps * 1000 * 1536) / (sr * 8);
let extra = if fscod == 1 && frmsizecod_low_bit != 0 {
2
} else {
0
};
Some(base + extra)
}
fn eac3_frame_size(frmsiz: u16) -> usize {
((frmsiz as usize) + 1) * 2
}
fn extract_ts_ac3_audio(
data: &[u8],
packets: usize,
packet_stride: usize,
prefix_len: usize,
audio_pid: u16,
) -> Result<Option<AudioTrack>> {
let es = reassemble_audio_pes(data, packets, packet_stride, prefix_len, audio_pid);
if es.is_empty() {
return Ok(None);
}
let mut cursor = match find_ac3_sync(&es, 0) {
Some(idx) => idx,
None => return Ok(None),
};
let first = match ac3_sync::parse_sync_info(&es[cursor..])
.context("TS: first AC-3 frame failed to parse sync header")?
{
SyncInfo::Ac3(s) => s,
SyncInfo::Eac3(_) => bail!("TS: AC-3 PMT entry but bitstream is E-AC-3 (bsid=16)"),
};
let sample_rate = ac3_sync::ac3_sample_rate_hz(first.fscod);
if sample_rate == 0 {
bail!("TS: AC-3 fscod={} reserved", first.fscod);
}
let channels = channel_count(first.acmod, first.lfeon);
let dac3 = dac3_body_from_sync(&first).to_vec();
let mut samples: Vec<Vec<u8>> = Vec::new();
let mut durations: Vec<u32> = Vec::new();
while cursor < es.len() {
let Some(found) = find_ac3_sync(&es, cursor) else {
break;
};
cursor = found;
if cursor + 5 > es.len() {
break;
}
let frmsizecod = es[cursor + 4] & 0x3F;
let bit_rate_code = frmsizecod >> 1;
let low_bit = frmsizecod & 0x01;
let fscod = (es[cursor + 4] >> 6) & 0x03;
let Some(size) = ac3_frame_size(bit_rate_code, fscod, low_bit) else {
break;
};
let end = cursor + size;
if end > es.len() {
break;
}
samples.push(es[cursor..end].to_vec());
durations.push(1536);
cursor = end;
}
if samples.is_empty() {
return Ok(None);
}
Ok(Some(AudioTrack {
codec: "ac3".into(),
samples,
sample_rate,
channels,
asc: Vec::new(),
codec_private: dac3,
timescale: sample_rate,
durations,
}))
}
fn extract_ts_eac3_audio(
data: &[u8],
packets: usize,
packet_stride: usize,
prefix_len: usize,
audio_pid: u16,
) -> Result<Option<AudioTrack>> {
let es = reassemble_audio_pes(data, packets, packet_stride, prefix_len, audio_pid);
if es.is_empty() {
return Ok(None);
}
let mut cursor = match find_ac3_sync(&es, 0) {
Some(idx) => idx,
None => return Ok(None),
};
let first: Eac3SyncInfo = match ac3_sync::parse_sync_info(&es[cursor..])
.context("TS: first E-AC-3 frame failed to parse sync header")?
{
SyncInfo::Eac3(s) => s,
SyncInfo::Ac3(_) => bail!("TS: E-AC-3 PMT entry but bitstream is AC-3 (bsid<=10)"),
};
let sample_rate = eac3_sample_rate_hz(first.fscod, first.fscod2);
if sample_rate == 0 {
bail!(
"TS: E-AC-3 reserved sample rate (fscod={}, fscod2={})",
first.fscod,
first.fscod2
);
}
let channels = channel_count(first.acmod, first.lfeon);
let spf = eac3_samples_per_frame(first.numblkscod) as u64;
let frame_bytes = ((first.frmsiz as u64) + 1) * 2;
let bitrate_kbps = if spf > 0 && sample_rate > 0 {
(frame_bytes * 8 * sample_rate as u64) / spf / 1000
} else {
0
};
let data_rate = bitrate_kbps.div_ceil(2) as u16;
let dec3 = dec3_body_from_sync(&first, data_rate).to_vec();
let mut samples: Vec<Vec<u8>> = Vec::new();
let mut durations: Vec<u32> = Vec::new();
while cursor < es.len() {
let Some(found) = find_ac3_sync(&es, cursor) else {
break;
};
cursor = found;
if cursor + 5 > es.len() {
break;
}
let raw = u16::from_be_bytes([es[cursor + 2], es[cursor + 3]]);
let frmsiz = raw & 0x07FF;
let size = eac3_frame_size(frmsiz);
let end = cursor + size;
if end > es.len() {
break;
}
samples.push(es[cursor..end].to_vec());
durations.push(spf as u32);
cursor = end;
}
if samples.is_empty() {
return Ok(None);
}
Ok(Some(AudioTrack {
codec: "eac3".into(),
samples,
sample_rate,
channels,
asc: Vec::new(),
codec_private: dec3,
timescale: sample_rate,
durations,
}))
}
fn reassemble_audio_pes(
data: &[u8],
packets: usize,
packet_stride: usize,
prefix_len: usize,
audio_pid: u16,
) -> Vec<u8> {
let mut es: Vec<u8> = Vec::new();
let mut have_first_start = false;
for i in 0..packets {
let start = i * packet_stride + prefix_len;
let pkt = &data[start..start + TS_PACKET];
if pkt[0] != TS_SYNC {
continue;
}
let pid = (((pkt[1] & 0x1F) as u16) << 8) | pkt[2] as u16;
if pid != audio_pid {
continue;
}
let pusi = pkt[1] & 0x40 != 0;
let scramble = (pkt[3] >> 6) & 0x03;
if scramble != 0 {
continue;
}
let adaptation = (pkt[3] >> 4) & 0x03;
let has_payload = adaptation & 0x01 != 0;
let has_adaptation = adaptation & 0x02 != 0;
if !has_payload {
continue;
}
let mut offset = 4usize;
if has_adaptation {
if offset >= TS_PACKET {
continue;
}
let adap_len = pkt[offset] as usize;
offset += 1 + adap_len;
if offset > TS_PACKET {
continue;
}
}
if offset >= TS_PACKET {
continue;
}
let payload = &pkt[offset..];
if pusi {
let Some((es_start, _pts)) = parse_pes_header_audio(payload) else {
have_first_start = false;
continue;
};
have_first_start = true;
if es_start < payload.len() {
es.extend_from_slice(&payload[es_start..]);
}
} else if have_first_start {
es.extend_from_slice(payload);
}
}
es
}
fn parse_pes_header_audio(payload: &[u8]) -> Option<(usize, Option<u64>)> {
if payload.len() < 9 {
return None;
}
if payload[0] != 0 || payload[1] != 0 || payload[2] != 1 {
return None;
}
let stream_id = payload[3];
if !(0xC0..=0xDF).contains(&stream_id) {
return None;
}
let flags = payload[7];
let pts_dts_flags = (flags >> 6) & 0x03;
let header_data_len = payload[8] as usize;
let es_start = 9 + header_data_len;
if es_start > payload.len() {
return None;
}
let pts = if pts_dts_flags == 0b10 || pts_dts_flags == 0b11 {
if payload.len() < 14 {
return None;
}
let p0 = ((payload[9] >> 1) & 0x07) as u64;
let p1 = (((payload[10] as u64) << 7) | ((payload[11] as u64) >> 1)) & 0x7FFF;
let p2 = (((payload[12] as u64) << 7) | ((payload[13] as u64) >> 1)) & 0x7FFF;
Some((p0 << 30) | (p1 << 15) | p2)
} else {
None
};
Some((es_start, pts))
}
pub(super) fn extract_ts_audio(
data: &[u8],
packets: usize,
packet_stride: usize,
prefix_len: usize,
info: AudioStreamInfo,
) -> Result<Option<AudioTrack>> {
match info.kind {
AudioCodecKind::AacAdts => {
extract_ts_aac_audio(data, packets, packet_stride, prefix_len, info.pid)
}
AudioCodecKind::Ac3 => {
extract_ts_ac3_audio(data, packets, packet_stride, prefix_len, info.pid)
}
AudioCodecKind::Eac3 => {
extract_ts_eac3_audio(data, packets, packet_stride, prefix_len, info.pid)
}
}
}