pub mod aac;
pub mod ac3;
pub mod avc;
pub mod dts;
pub mod dts_hd;
pub mod hevc;
pub mod lpcm;
pub mod mpa;
pub mod mpeg2;
pub mod mvc;
pub mod pgs;
pub mod truehd;
pub mod vc1;
use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsStream, TsStreamType};
pub(crate) fn scan_access_unit(
stream: &mut TsStream,
buffer: &mut TsStreamBuffer,
bitrate: i64,
is_full_scan: bool,
tag: &mut Option<String>,
) {
match stream {
TsStream::Video(video) => match video.base.stream_type {
TsStreamType::Mpeg2Video => mpeg2::scan(video, buffer, tag),
TsStreamType::AvcVideo => avc::scan(video, buffer, tag),
TsStreamType::MvcVideo => mvc::scan(video, buffer, tag),
TsStreamType::HevcVideo => hevc::scan(video, buffer, tag),
TsStreamType::Vc1Video => vc1::scan(video, buffer, tag),
_ => video.base.is_initialized = true,
},
TsStream::Audio(audio) => match audio.base.stream_type {
TsStreamType::Mpeg1Audio | TsStreamType::Mpeg2Audio => {
mpa::scan(audio, buffer, tag);
}
TsStreamType::Mpeg2AacAudio | TsStreamType::Mpeg4AacAudio => {
aac::scan(audio, buffer, tag);
}
TsStreamType::Ac3Audio
| TsStreamType::Ac3PlusAudio
| TsStreamType::Ac3PlusSecondaryAudio => ac3::scan(audio, buffer, tag),
TsStreamType::Ac3TrueHdAudio => truehd::scan(audio, buffer, tag),
TsStreamType::LpcmAudio => lpcm::scan(audio, buffer, tag),
TsStreamType::DtsAudio => dts::scan(audio, buffer, bitrate, tag),
TsStreamType::DtsHdAudio
| TsStreamType::DtsHdMasterAudio
| TsStreamType::DtsHdSecondaryAudio => dts_hd::scan(audio, buffer, bitrate, tag),
_ => audio.base.is_initialized = true,
},
TsStream::Graphics(graphics) => {
if is_full_scan && graphics.base.stream_type == TsStreamType::PresentationGraphics {
pgs::scan(graphics, buffer, tag);
} else {
graphics.base.is_initialized = true;
}
}
TsStream::Text(text) => text.base.is_initialized = true,
}
}
#[cfg(test)]
mod tests {
use super::scan_access_unit;
use crate::bitstream::TsStreamBuffer;
use crate::stream::{
TsAudioStream, TsGraphicsStream, TsStream, TsStreamType, TsTextStream, TsVideoStream,
};
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 ac3_core() -> 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 dts_core() -> Vec<u8> {
pack(&[
(0x7FFE_8001, 32),
(0, 6),
(0, 1),
(0, 7),
(100, 14),
(0, 6),
(13, 4),
(24, 5),
(0, 8),
(0, 1),
(0, 1),
(1, 2),
(0, 1),
(0, 7),
(0, 3),
(0, 2),
(0, 4),
(0, 4),
(4, 3),
(0, 64),
])
}
fn mpa_frame() -> Vec<u8> {
pack(&[
(0x7FF, 11),
(3, 2),
(1, 2),
(0, 1),
(9, 4),
(1, 2),
(0, 1),
(0, 1),
(0, 2),
(0, 2),
(0, 1),
(0, 1),
(0, 2),
(0, 13),
])
}
fn aac_frame() -> Vec<u8> {
pack(&[
(0xFFF, 12),
(0, 1),
(0, 2),
(0, 1),
(1, 2),
(3, 4),
(0, 1),
(2, 3),
(0, 1),
(0, 1),
(0, 16),
])
}
fn pcs() -> Vec<u8> {
let mut v = vec![0x16, 0, 0];
v.extend_from_slice(&1920_u16.to_be_bytes());
v.extend_from_slice(&1080_u16.to_be_bytes());
v.push(0);
v.extend_from_slice(&7_u16.to_be_bytes());
v.extend_from_slice(&[0, 0, 0]);
v.push(1); v.extend_from_slice(&[0, 0]); v.push(0); v.push(0); v.extend_from_slice(&[0_u8; 12]);
v
}
fn run(mut stream: TsStream, data: &[u8], bitrate: i64, is_full_scan: bool) -> TsStream {
let mut tag = None;
let mut b = buf(data);
scan_access_unit(&mut stream, &mut b, bitrate, is_full_scan, &mut tag);
stream
}
fn video(stream_type: TsStreamType) -> TsStream {
let mut v = TsVideoStream::default();
v.base.stream_type = stream_type;
TsStream::Video(v)
}
fn audio(stream_type: TsStreamType) -> TsStream {
let mut a = TsAudioStream::default();
a.base.stream_type = stream_type;
TsStream::Audio(a)
}
fn graphics(stream_type: TsStreamType) -> TsStream {
let mut g = TsGraphicsStream::default();
g.base.stream_type = stream_type;
TsStream::Graphics(g)
}
fn has_core(stream: &TsStream) -> bool {
matches!(stream, TsStream::Audio(audio) if audio.core_stream.is_some())
}
fn has_extended_data(stream: &TsStream) -> bool {
matches!(stream, TsStream::Video(video) if video.extended_data.is_some())
}
fn audio_channels(stream: &TsStream) -> i32 {
if let TsStream::Audio(audio) = stream { audio.channel_count } else { -1 }
}
#[test]
fn pack_handles_aligned_and_partial_inputs() {
assert_eq!(pack(&[(0xABCD, 16)]), vec![0xAB, 0xCD]);
assert_eq!(pack(&[(0xABC, 12)]), vec![0xAB, 0xC0]);
}
#[test]
fn video_arms_route_to_their_codec() {
assert!(
run(video(TsStreamType::Mpeg2Video), &[0, 0, 1, 0xB3, 0, 0, 0, 0, 0, 0, 0], 0, true)
.base()
.is_vbr
);
assert!(
run(video(TsStreamType::AvcVideo), &[0, 0, 1, 0x67, 100, 0, 41, 0], 0, true)
.base()
.is_vbr
);
assert!(run(video(TsStreamType::MvcVideo), &[], 0, true).base().is_vbr);
assert!(
run(video(TsStreamType::Vc1Video), &[0, 0, 1, 0x0F, 0xD8, 0, 0, 0, 0, 0], 0, true)
.base()
.is_vbr
);
assert!(has_extended_data(&run(video(TsStreamType::HevcVideo), &[], 0, true)));
let mpeg1 = run(video(TsStreamType::Mpeg1Video), &[], 0, true);
assert!(mpeg1.base().is_initialized);
assert!(!mpeg1.base().is_vbr);
assert!(!has_extended_data(&mpeg1));
}
#[test]
fn audio_arms_route_to_their_codec() {
for ty in [TsStreamType::Mpeg1Audio, TsStreamType::Mpeg2Audio] {
assert!(audio_channels(&run(audio(ty), &mpa_frame(), 0, true)) > 0, "{ty:?}");
}
for ty in [TsStreamType::Mpeg2AacAudio, TsStreamType::Mpeg4AacAudio] {
assert!(audio_channels(&run(audio(ty), &aac_frame(), 0, true)) > 0, "{ty:?}");
}
for ty in [
TsStreamType::Ac3Audio,
TsStreamType::Ac3PlusAudio,
TsStreamType::Ac3PlusSecondaryAudio,
] {
assert!(audio_channels(&run(audio(ty), &ac3_core(), 0, true)) > 0, "{ty:?}");
}
assert!(
audio_channels(&run(audio(TsStreamType::LpcmAudio), &[0, 0, 0x31, 0x40, 0], 0, true))
> 0
);
assert!(audio_channels(&run(audio(TsStreamType::DtsAudio), &dts_core(), 0, true)) > 0);
assert!(has_core(&run(audio(TsStreamType::Ac3TrueHdAudio), &ac3_core(), 0, true)));
for ty in [
TsStreamType::DtsHdAudio,
TsStreamType::DtsHdMasterAudio,
TsStreamType::DtsHdSecondaryAudio,
] {
assert!(has_core(&run(audio(ty), &dts_core(), 0, true)), "{ty:?}");
}
let unknown = run(audio(TsStreamType::Unknown), &[], 0, true);
assert!(unknown.base().is_initialized);
assert_eq!(audio_channels(&unknown), 0);
assert!(!has_core(&unknown));
assert_eq!(audio_channels(&run(video(TsStreamType::Mpeg1Video), &[], 0, true)), -1);
}
fn graphics_width(s: &TsStream) -> i32 {
if let TsStream::Graphics(graphics) = s { graphics.width } else { -1 }
}
#[test]
fn presentation_graphics_decode_only_on_a_full_scan() {
let decoded = run(graphics(TsStreamType::PresentationGraphics), &pcs(), 0, true);
assert!(decoded.base().is_initialized);
assert_eq!(graphics_width(&decoded), 1920);
let quick = run(graphics(TsStreamType::PresentationGraphics), &pcs(), 0, false);
assert!(quick.base().is_initialized);
assert_eq!(graphics_width(&quick), 0);
let interactive = run(graphics(TsStreamType::InteractiveGraphics), &pcs(), 0, true);
assert!(interactive.base().is_initialized);
assert_eq!(graphics_width(&interactive), 0);
assert_eq!(graphics_width(&run(audio(TsStreamType::Unknown), &[], 0, true)), -1);
}
#[test]
fn text_streams_are_marked_initialized() {
let mut tag = None;
let mut s = TsStream::Text(TsTextStream::default());
let mut b = buf(&[]);
scan_access_unit(&mut s, &mut b, 0, true, &mut tag);
assert!(s.base().is_initialized);
assert_eq!(tag, None);
}
#[test]
fn the_dispatch_writes_the_frame_tag_through_to_the_caller() {
let mut tag = None;
let mut s = video(TsStreamType::AvcVideo);
let mut b = buf(&[0x00, 0x00, 0x01, 0x09, 0x30]);
scan_access_unit(&mut s, &mut b, 0, true, &mut tag);
assert_eq!(tag.as_deref(), Some("P"));
let mut b2 = buf(&[0xFF, 0xFF]);
scan_access_unit(&mut s, &mut b2, 0, true, &mut tag);
assert_eq!(tag.as_deref(), Some("P"));
}
}