use crate::bitstream::TsStreamBuffer;
use crate::codec::ac3;
use crate::stream::{TsAudioStream, TsStreamType};
const TRUEHD_MAJOR_SYNC: u32 = 0xF872_6FBA;
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::as_conversions,
reason = "deliberate low-32-bits truncation of the i64 (peak_bitrate * sample_rate) >> 4; TryFrom would wrongly reject it"
)]
fn scale_peak_bitrate(peak_bitrate: u32, sample_rate: i32) -> u32 {
i64::from(peak_bitrate).wrapping_mul(i64::from(sample_rate)).wrapping_shr(4) as u32
}
#[expect(
clippy::too_many_lines,
reason = "one linear major-sync header parse; the 13 channel-assignment reads are clearest inline"
)]
pub fn scan(stream: &mut TsAudioStream, buffer: &mut TsStreamBuffer, tag: &mut Option<String>) {
if stream.base.is_initialized
&& stream.core_stream.as_ref().is_some_and(|c| c.base.is_initialized)
{
return;
}
let mut sync_found = false;
let mut sync: u32 = 0;
for _ in 0..buffer.length() {
sync = sync.wrapping_shl(8).wrapping_add(u32::from(buffer.read_byte(false)));
if sync == TRUEHD_MAJOR_SYNC {
sync_found = true;
break;
}
}
if !sync_found {
*tag = Some("CORE".to_owned());
if stream.core_stream.is_none() {
let mut core = TsAudioStream::default();
core.base.stream_type = TsStreamType::Ac3Audio;
stream.core_stream = Some(Box::new(core));
}
if let Some(core) = stream.core_stream.as_mut()
&& !core.base.is_initialized
{
buffer.begin_read();
ac3::scan(core, buffer, tag);
}
return;
}
*tag = Some("HD".to_owned());
let ratebits = buffer.read_bits2(4, false);
if ratebits != 0xF {
let base_rate: i32 = if (ratebits & 8) > 0 { 44_100 } else { 48_000 };
stream.sample_rate = base_rate.wrapping_shl(u32::from(ratebits & 7));
}
buffer.bs_skip_bits(15, false);
stream.channel_count = 0;
stream.lfe = 0;
if buffer.read_bool(false) {
stream.lfe = stream.lfe.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
if buffer.read_bool(false) {
stream.lfe = stream.lfe.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(1);
}
if buffer.read_bool(false) {
stream.channel_count = stream.channel_count.wrapping_add(2);
}
buffer.bs_skip_bits(49, false);
let peak_bitrate = scale_peak_bitrate(buffer.read_bits4(15, false), stream.sample_rate);
let peak_bitdepth = f64::from(peak_bitrate)
/ f64::from(stream.channel_count.wrapping_add(stream.lfe))
/ f64::from(stream.sample_rate);
stream.bit_depth = if peak_bitdepth > 14.0 { 24 } else { 16 };
buffer.bs_skip_bits(79, false);
let has_extensions = buffer.read_bool(false);
let num_extensions = i32::from(buffer.read_bits2(4, false)).wrapping_mul(2).wrapping_add(1);
let mut has_content = buffer.read_bits4(4, false) != 0;
if has_extensions {
for _ in 0..num_extensions {
if buffer.read_bits2(8, false) != 0 {
has_content = true;
}
}
if has_content {
stream.has_extensions = true;
}
}
stream.base.is_vbr = true;
if stream.core_stream.as_ref().is_some_and(|c| c.base.is_initialized) {
stream.base.is_initialized = true;
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, proptest};
use super::scan;
use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsAudioStream, TsStreamType};
fn buf(data: &[u8]) -> TsStreamBuffer {
let mut b = TsStreamBuffer::new();
b.add(data, 0, data.len());
b.begin_read();
b
}
fn stream(stream_type: TsStreamType) -> TsAudioStream {
let mut s = TsAudioStream::default();
s.base.stream_type = stream_type;
s
}
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 ac3_core_5_1() -> Vec<u8> {
pack(&[
(0x0B77, 16),
(0, 16), (0, 2),
(36, 6),
(8, 5),
(0, 3), (7, 3),
(0, 2),
(0, 2),
(1, 1), (31, 5),
(0, 1),
(0, 1),
(0, 1), (0, 2),
(0, 16),
])
}
fn hd_frame(
ratebits: u64,
chan_bits: [u64; 13],
peak_bitrate: u64,
has_extensions: u64,
num_ext_field: u64,
has_content_field: u64,
ext_bytes: &[u64],
) -> Vec<u8> {
let mut fields: Vec<(u64, u32)> = vec![(ratebits, 4), (0, 15)];
for &bit in &chan_bits {
fields.push((bit, 1));
}
fields.push((0, 49));
fields.push((peak_bitrate, 15));
fields.push((0, 79));
fields.push((has_extensions, 1));
fields.push((num_ext_field, 4));
fields.push((has_content_field, 4));
for &e in ext_bytes {
fields.push((e, 8));
}
fields.push((0, 64));
let mut out = vec![0xF8, 0x72, 0x6F, 0xBA];
out.extend(pack(&fields));
out
}
const CHAN_7_1: [u64; 13] = [1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0];
#[test]
fn truehd_over_ac3_core_reproduces_the_expected_line() {
let mut thd = stream(TsStreamType::Ac3TrueHdAudio);
let mut tag = None;
let mut core_buf = buf(&ac3_core_5_1());
scan(&mut thd, &mut core_buf, &mut tag);
assert_eq!(tag.as_deref(), Some("CORE"));
assert_eq!(thd.core_stream.as_deref().map(|c| c.channel_count), Some(5));
assert_eq!(thd.core_stream.as_deref().map(|c| c.base.bit_rate), Some(640_000));
assert!(!thd.base.is_initialized);
let mut hd_buf = buf(&hd_frame(0, CHAN_7_1, 0, 0, 0, 0, &[]));
scan(&mut thd, &mut hd_buf, &mut tag);
assert_eq!(tag.as_deref(), Some("HD"));
assert_eq!(thd.channel_count, 7);
assert_eq!(thd.lfe, 1);
assert_eq!(thd.sample_rate, 48_000);
assert_eq!(thd.bit_depth, 16);
assert!(!thd.has_extensions);
assert!(thd.base.is_vbr);
assert!(thd.base.is_initialized);
assert_eq!(thd.codec_short_name(), "TrueHD");
assert_eq!(thd.codec_name(), "Dolby TrueHD Audio");
assert_eq!(
thd.description(),
"7.1 / 48 kHz / 16-bit (AC3 Embedded: 5.1 / 48 kHz / 640 kbps / DN -31dB)"
);
}
#[test]
fn hd_sample_rate_codes() {
let rate = |ratebits| {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(ratebits, CHAN_7_1, 0, 0, 0, 0, &[]));
scan(&mut s, &mut b, &mut None);
s.sample_rate
};
assert_eq!(rate(0), 48_000); assert_eq!(rate(1), 96_000); assert_eq!(rate(8), 44_100); assert_eq!(rate(0xF), 0); }
#[test]
fn hd_all_channel_bits_and_24_bit_depth() {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, [1; 13], 5000, 0, 0, 0, &[]));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.lfe, 2);
assert_eq!(s.channel_count, 18);
assert_eq!(s.bit_depth, 24);
}
#[test]
fn hd_bit_depth_threshold_is_exact() {
let depth = |peak| {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, CHAN_7_1, peak, 0, 0, 0, &[]));
scan(&mut s, &mut b, &mut None);
s.bit_depth
};
assert_eq!(depth(1), 16);
assert_eq!(depth(256), 16);
assert_eq!(depth(1792), 16); assert_eq!(depth(1793), 24); assert_eq!(depth(5000), 24);
}
#[test]
fn hd_all_channel_bits_clear() {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, [0; 13], 0, 0, 0, 0, &[]));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.channel_count, 0);
assert_eq!(s.lfe, 0);
assert_eq!(s.bit_depth, 16);
}
#[test]
fn hd_atmos_extension_detection() {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, CHAN_7_1, 0, 1, 1, 0, &[0, 0, 1]));
scan(&mut s, &mut b, &mut None);
assert!(s.has_extensions);
assert_eq!(s.codec_short_name(), "Atmos");
assert_eq!(s.codec_name(), "Dolby TrueHD/Atmos Audio");
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, CHAN_7_1, 0, 1, 0, 1, &[0]));
scan(&mut s, &mut b, &mut None);
assert!(s.has_extensions);
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&hd_frame(0, CHAN_7_1, 0, 1, 0, 0, &[0, 1]));
scan(&mut s, &mut b, &mut None);
assert!(!s.has_extensions);
}
#[test]
fn core_scan_skips_when_core_already_initialized() {
let mut thd = stream(TsStreamType::Ac3TrueHdAudio);
let mut tag = None;
let mut first = buf(&ac3_core_5_1());
scan(&mut thd, &mut first, &mut tag);
assert_eq!(thd.core_stream.as_deref().map(|c| c.channel_count), Some(5));
let other = pack(&[
(0x0B77, 16),
(0, 16),
(0, 2),
(36, 6),
(8, 5),
(0, 3),
(1, 3),
(0, 1),
(0, 5),
(0, 1),
(0, 1),
(0, 1),
(0, 2),
(0, 16),
]);
let mut second = buf(&other);
scan(&mut thd, &mut second, &mut tag);
assert_eq!(thd.core_stream.as_deref().map(|c| c.channel_count), Some(5));
}
#[test]
fn fully_initialized_stream_returns_immediately() {
let mut thd = stream(TsStreamType::Ac3TrueHdAudio);
thd.channel_count = 99;
thd.base.is_initialized = true;
let mut core = TsAudioStream::default();
core.base.stream_type = TsStreamType::Ac3Audio;
core.base.is_initialized = true;
thd.core_stream = Some(Box::new(core));
let mut b = buf(&hd_frame(0, CHAN_7_1, 0, 0, 0, 0, &[]));
scan(&mut thd, &mut b, &mut None);
assert_eq!(thd.channel_count, 99); }
proptest! {
#[test]
fn scan_never_panics_on_arbitrary_bytes(data in any::<Vec<u8>>()) {
let mut s = stream(TsStreamType::Ac3TrueHdAudio);
let mut b = buf(&data);
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
}
}
}