use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsAudioMode, TsAudioStream};
const DTS_SYNC: u32 = 0x7FFE_8001;
const DCA_SAMPLE_RATES: [i32; 16] = [
0, 8_000, 16_000, 32_000, 0, 0, 11_025, 22_050, 44_100, 0, 0, 12_000, 24_000, 48_000, 96_000,
192_000,
];
const DCA_BIT_RATES: [i32; 32] = [
32_000, 56_000, 64_000, 96_000, 112_000, 128_000, 192_000, 224_000, 256_000, 320_000, 384_000,
448_000, 512_000, 576_000, 640_000, 768_000, 896_000, 1_024_000, 1_152_000, 1_280_000,
1_344_000, 1_408_000, 1_411_200, 1_472_000, 1_536_000, 1_920_000, 2_048_000, 3_072_000,
3_840_000, 1, 2, 3,
];
const DCA_BITS_PER_SAMPLE: [i32; 7] = [16, 16, 20, 20, 0, 24, 24];
pub fn scan(
stream: &mut TsAudioStream,
buffer: &mut TsStreamBuffer,
bitrate: i64,
tag: &mut Option<String>,
) {
let _ = tag;
if stream.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 == DTS_SYNC {
sync_found = true;
break;
}
}
if !sync_found {
return;
}
buffer.bs_skip_bits(6, false);
let crc_present = buffer.read_bits4(1, false);
buffer.bs_skip_bits(7, false);
let frame_size = buffer.read_bits4(14, false);
if frame_size < 95 {
return;
}
buffer.bs_skip_bits(6, false);
let sample_rate_code = buffer.read_bits4(4, false);
let bit_rate_code = buffer.read_bits4(5, false);
buffer.bs_skip_bits(8, false);
let ext_coding = buffer.read_bits4(1, false);
buffer.bs_skip_bits(1, false);
let lfe = buffer.read_bits4(2, false);
buffer.bs_skip_bits(1, false);
if crc_present == 1 {
buffer.bs_skip_bits(16, false);
}
buffer.bs_skip_bits(7, false);
let source_pcm_res = buffer.read_bits4(3, false);
buffer.bs_skip_bits(2, false);
let dialog_norm = buffer.read_bits4(4, false);
let pcm_idx = usize::try_from(source_pcm_res).unwrap_or(usize::MAX);
let Some(&bit_depth) = DCA_BITS_PER_SAMPLE.get(pcm_idx) else {
return;
};
buffer.bs_skip_bits(4, false);
let total_channels = buffer.read_bits4(3, false).wrapping_add(1).wrapping_add(ext_coding);
let sr_idx = usize::try_from(sample_rate_code).unwrap_or(usize::MAX);
stream.sample_rate = DCA_SAMPLE_RATES.get(sr_idx).copied().unwrap_or(0);
stream.channel_count = i32::try_from(total_channels).unwrap_or(0);
stream.lfe = i32::from(lfe > 0);
stream.bit_depth = bit_depth;
stream.dial_norm = i32::try_from(dialog_norm).unwrap_or(0).wrapping_mul(-1);
if (source_pcm_res & 0x1) == 0x1 {
stream.audio_mode = TsAudioMode::Extended;
}
let br_idx = usize::try_from(bit_rate_code).unwrap_or(usize::MAX);
stream.base.bit_rate = i64::from(DCA_BIT_RATES.get(br_idx).copied().unwrap_or(0));
match stream.base.bit_rate {
1 => {
if bitrate > 0 {
stream.base.bit_rate = bitrate;
stream.base.is_vbr = false;
stream.base.is_initialized = true;
} else {
stream.base.bit_rate = 0;
}
}
2 | 3 => {
stream.base.is_vbr = true;
stream.base.is_initialized = true;
}
_ => {
stream.base.is_vbr = false;
stream.base.is_initialized = true;
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, proptest};
use super::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 stream() -> TsAudioStream {
let mut s = TsAudioStream::default();
s.base.stream_type = TsStreamType::DtsAudio;
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
}
#[expect(clippy::too_many_arguments, reason = "spells one DTS header field-by-field")]
fn dts_frame(
crc_present: u64,
frame_size: u64,
sample_rate_code: u64,
bit_rate_code: u64,
ext_coding: u64,
lfe: u64,
source_pcm_res: u64,
dialog_norm: u64,
total_ch_base: u64,
) -> Vec<u8> {
let mut f: Vec<(u64, u32)> = vec![
(0x7FFE_8001, 32),
(0, 6),
(crc_present, 1),
(0, 7),
(frame_size, 14),
(0, 6),
(sample_rate_code, 4),
(bit_rate_code, 5),
(0, 8),
(ext_coding, 1),
(0, 1),
(lfe, 2),
(0, 1),
];
if crc_present == 1 {
f.push((0, 16));
}
f.push((0, 7));
f.push((source_pcm_res, 3));
f.push((0, 2));
f.push((dialog_norm, 4));
f.push((0, 4));
f.push((total_ch_base, 3));
f.push((0, 64));
pack(&f)
}
fn run(bytes: &[u8], bitrate: i64) -> TsAudioStream {
let mut s = stream();
let mut b = buf(bytes);
let mut tag = None;
scan(&mut s, &mut b, bitrate, &mut tag);
assert_eq!(tag, None, "the DTS core never sets the tag");
s
}
#[test]
fn pack_handles_byte_aligned_and_partial_inputs() {
assert_eq!(pack(&[(0xABCD, 16)]), vec![0xAB, 0xCD]);
assert_eq!(pack(&[(0xABC, 12)]), vec![0xAB, 0xC0]);
}
#[test]
fn core_5_1_48k_1536_kbps_matches_the_embedded_core_line() {
let s = run(&dts_frame(0, 100, 13, 24, 0, 1, 0, 0, 4), 0);
assert_eq!(s.sample_rate, 48_000);
assert_eq!(s.channel_count, 5);
assert_eq!(s.lfe, 1);
assert_eq!(s.bit_depth, 16);
assert_eq!(s.dial_norm, 0);
assert_eq!(s.base.bit_rate, 1_536_000);
assert_eq!(s.audio_mode, TsAudioMode::Unknown);
assert!(s.base.is_initialized);
assert!(!s.base.is_vbr);
assert_eq!(s.codec_short_name(), "DTS");
assert_eq!(s.codec_name(), "DTS Audio");
assert_eq!(s.description(), "5.1 / 48 kHz / 1536 kbps / 16-bit");
}
#[test]
fn core_24bit_es_and_dialnorm() {
let s = run(&dts_frame(0, 100, 13, 24, 0, 0, 5, 6, 1), 0);
assert_eq!(s.bit_depth, 24);
assert_eq!(s.audio_mode, TsAudioMode::Extended);
assert_eq!(s.lfe, 0);
assert_eq!(s.channel_count, 2);
assert_eq!(s.dial_norm, -6);
assert_eq!(s.codec_short_name(), "DTS-ES");
assert_eq!(s.codec_name(), "DTS-ES Audio");
assert_eq!(s.description(), "2.0-ES / 48 kHz / 1536 kbps / 24-bit / DN -6dB");
}
#[test]
fn core_24bit_non_es_uses_even_pcmr() {
let s = run(&dts_frame(0, 100, 13, 24, 1, 1, 6, 0, 3), 0);
assert_eq!(s.bit_depth, 24);
assert_eq!(s.audio_mode, TsAudioMode::Unknown);
assert_eq!(s.channel_count, 5);
assert_eq!(s.description(), "5.1 / 48 kHz / 1536 kbps / 24-bit");
}
#[test]
fn crc_present_skips_the_extra_field() {
let s = run(&dts_frame(1, 100, 13, 24, 0, 1, 0, 0, 4), 0);
assert_eq!(s.sample_rate, 48_000);
assert_eq!(s.channel_count, 5);
assert_eq!(s.bit_depth, 16);
assert_eq!(s.base.bit_rate, 1_536_000);
}
#[test]
fn sample_rate_codes() {
let rate = |code| run(&dts_frame(0, 100, code, 24, 0, 0, 0, 0, 1), 0).sample_rate;
assert_eq!(rate(1), 8_000);
assert_eq!(rate(8), 44_100);
assert_eq!(rate(13), 48_000);
assert_eq!(rate(15), 192_000);
assert_eq!(rate(4), 0);
}
#[test]
fn lfe_field_is_clamped_to_zero_or_one() {
assert_eq!(run(&dts_frame(0, 100, 13, 24, 0, 0, 0, 0, 1), 0).lfe, 0);
assert_eq!(run(&dts_frame(0, 100, 13, 24, 0, 1, 0, 0, 1), 0).lfe, 1);
assert_eq!(run(&dts_frame(0, 100, 13, 24, 0, 3, 0, 0, 1), 0).lfe, 1);
}
#[test]
fn frame_size_boundary_at_95() {
let small = run(&dts_frame(0, 94, 13, 24, 0, 1, 0, 0, 4), 0);
assert!(!small.base.is_initialized);
assert_eq!(small.channel_count, 0);
let boundary = run(&dts_frame(0, 95, 13, 24, 0, 1, 0, 0, 4), 0);
assert!(boundary.base.is_initialized);
assert_eq!(boundary.channel_count, 5);
let big = run(&dts_frame(0, 16_383, 13, 24, 0, 1, 0, 0, 4), 0);
assert!(big.base.is_initialized);
}
#[test]
fn source_pcm_res_out_of_range_returns() {
let s = run(&dts_frame(0, 100, 13, 24, 0, 1, 7, 0, 4), 0);
assert!(!s.base.is_initialized);
assert_eq!(s.bit_depth, 0);
assert_eq!(s.channel_count, 0);
let ok = run(&dts_frame(0, 100, 13, 24, 0, 1, 6, 0, 4), 0);
assert!(ok.base.is_initialized);
assert_eq!(ok.bit_depth, 24);
}
#[test]
fn bit_rate_open_marker_uses_the_measured_rate() {
let with = run(&dts_frame(0, 100, 13, 29, 0, 1, 0, 0, 4), 768_000);
assert_eq!(with.base.bit_rate, 768_000);
assert!(with.base.is_initialized);
assert!(!with.base.is_vbr);
let without = run(&dts_frame(0, 100, 13, 29, 0, 1, 0, 0, 4), 0);
assert_eq!(without.base.bit_rate, 0);
assert!(!without.base.is_initialized);
}
#[test]
fn bit_rate_variable_and_lossless_markers_are_vbr() {
for code in [30_u64, 31] {
let s = run(&dts_frame(0, 100, 13, code, 0, 1, 0, 0, 4), 0);
assert!(s.base.is_vbr, "rate code {code} is VBR");
assert!(s.base.is_initialized);
}
}
#[test]
fn bit_rate_fixed_rate_is_cbr() {
let s = run(&dts_frame(0, 100, 13, 14, 0, 1, 0, 0, 4), 0);
assert_eq!(s.base.bit_rate, 640_000);
assert!(!s.base.is_vbr);
assert!(s.base.is_initialized);
}
#[test]
fn rejects_missing_sync_and_already_initialized() {
let s = run(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55], 0);
assert!(!s.base.is_initialized);
assert_eq!(s.channel_count, 0);
let mut s = stream();
s.base.is_initialized = true;
let mut b = buf(&dts_frame(0, 100, 13, 24, 0, 1, 0, 0, 4));
scan(&mut s, &mut b, 0, &mut None);
assert_eq!(s.channel_count, 0);
}
proptest! {
#[test]
fn scan_never_panics_on_arbitrary_bytes(data in any::<Vec<u8>>(), bitrate in any::<i64>()) {
let mut s = stream();
let mut b = buf(&data);
let mut tag = None;
scan(&mut s, &mut b, bitrate, &mut tag);
}
}
}