use crate::bitstream::TsStreamBuffer;
use crate::stream::TsVideoStream;
pub fn scan(stream: &mut TsVideoStream, buffer: &mut TsStreamBuffer, tag: &mut Option<String>) {
let mut parse: i32 = 0;
let mut frame_header_parse: u8 = 0;
let mut sequence_header_parse: u8 = 0;
let mut is_interlaced = false;
for _ in 0..buffer.length() {
parse = parse.wrapping_shl(8).wrapping_add(i32::from(buffer.read_byte(false)));
if parse == 0x0000_010D {
frame_header_parse = 4;
} else if frame_header_parse > 0 {
frame_header_parse = frame_header_parse.wrapping_sub(1);
if frame_header_parse == 0 {
let p = parse.cast_unsigned();
if is_interlaced && (p & 0xC000_0000) == 0xC000_0000 {
let fptype = (p & 0x3800_0000).wrapping_shr(27);
*tag = Some(
match ((fptype & 0x4) == 0, (fptype & 0x2) == 0) {
(true, true) => "I",
(true, false) => "P",
(false, true) => "B",
(false, false) => "BI",
}
.to_owned(),
);
} else {
let picture_type: u32 = if is_interlaced {
if (p & 0x8000_0000) == 0 {
(p & 0x7800_0000).wrapping_shr(13)
} else {
(p & 0x3C00_0000).wrapping_shr(12)
}
} else {
(p & 0xF000_0000).wrapping_shr(14)
};
if (picture_type & 0x2_0000) == 0 {
*tag = Some("P".to_owned());
} else if (picture_type & 0x1_0000) == 0 {
*tag = Some("B".to_owned());
} else if (picture_type & 0x8000) == 0 {
*tag = Some("I".to_owned());
} else if (picture_type & 0x4000) == 0 {
*tag = Some("BI".to_owned());
} else {
*tag = None;
}
}
if stream.base.is_initialized {
return;
}
}
} else if parse == 0x0000_010F {
sequence_header_parse = 6;
} else if sequence_header_parse > 0 {
sequence_header_parse = sequence_header_parse.wrapping_sub(1);
match sequence_header_parse {
5 => {
let profile_level = (parse & 0x38).wrapping_shr(3);
if (parse & 0xC0).wrapping_shr(6) == 3 {
stream.encoding_profile = Some(format!("Advanced Profile {profile_level}"));
} else {
stream.encoding_profile = Some(format!("Main Profile {profile_level}"));
}
}
0 => {
is_interlaced = (parse & 0x40).wrapping_shr(6) > 0;
}
_ => {}
}
stream.base.is_vbr = true;
stream.base.is_initialized = true;
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, proptest};
use super::scan;
use crate::bitstream::TsStreamBuffer;
use crate::stream::{TsAspectRatio, TsFrameRate, TsStreamType, TsVideoFormat, TsVideoStream};
fn buf(data: &[u8]) -> TsStreamBuffer {
let mut b = TsStreamBuffer::new();
b.add(data, 0, data.len());
b.begin_read();
b
}
fn stream() -> TsVideoStream {
let mut s = TsVideoStream::default();
s.base.stream_type = TsStreamType::Vc1Video;
s
}
fn run(data: &[u8]) -> (TsVideoStream, Option<String>) {
let mut s = stream();
let mut b = buf(data);
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
(s, tag)
}
fn sequence_header(profile_byte: u8, case0_byte: u8) -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x0F, profile_byte, 0x00, 0x00, 0x00, 0x00, case0_byte]
}
fn frame_header(a: u8) -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x0D, a, 0x00, 0x00, 0x00]
}
#[test]
fn sequence_header_advanced_profile_sets_encoding_profile() {
let (s, _) = run(&sequence_header(0xD8, 0x00));
assert_eq!(s.encoding_profile.as_deref(), Some("Advanced Profile 3"));
assert!(s.base.is_vbr);
assert!(s.base.is_initialized);
}
#[test]
fn sequence_header_main_profile_and_level_extraction() {
let (s, _) = run(&sequence_header(0x18, 0x00)); assert_eq!(s.encoding_profile.as_deref(), Some("Main Profile 3"));
let (s, _) = run(&sequence_header(0x48, 0x00)); assert_eq!(s.encoding_profile.as_deref(), Some("Main Profile 1"));
let (s, _) = run(&sequence_header(0xC8, 0x00)); assert_eq!(s.encoding_profile.as_deref(), Some("Advanced Profile 1"));
}
#[test]
fn sequence_header_with_clpi_metadata_matches_the_expected_vc1_desc() {
let mut s = stream();
s.set_video_format(TsVideoFormat::Videoformat1080p);
s.set_frame_rate(TsFrameRate::Framerate23_976);
s.aspect_ratio = TsAspectRatio::Aspect16_9;
let mut b = buf(&sequence_header(0xD8, 0x00));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.description(), "1080p / 23.976 fps / 16:9 / Advanced Profile 3");
assert_eq!(s.codec_short_name(), "VC-1");
assert_eq!(s.codec_name(), "VC-1 Video");
}
#[test]
fn progressive_frame_header_picture_types() {
assert_eq!(run(&frame_header(0x00)).1.as_deref(), Some("P")); assert_eq!(run(&frame_header(0x80)).1.as_deref(), Some("B")); assert_eq!(run(&frame_header(0xC0)).1.as_deref(), Some("I")); assert_eq!(run(&frame_header(0xE0)).1.as_deref(), Some("BI")); assert_eq!(run(&frame_header(0xF0)).1, None); }
#[test]
fn interlaced_frame_header_uses_the_interlaced_picture_bits() {
let mut data = sequence_header(0xD8, 0x40);
data.extend_from_slice(&frame_header(0x40));
let (s, tag) = run(&data);
assert!(s.base.is_initialized);
assert_eq!(tag.as_deref(), Some("B"));
let mut data = sequence_header(0xD8, 0x40);
data.extend_from_slice(&frame_header(0x80));
assert_eq!(run(&data).1.as_deref(), Some("P"));
}
#[test]
fn interlaced_field_frame_header_uses_the_fixed_fptype() {
let field_tag = |a: u8| {
let mut data = sequence_header(0xD8, 0x40); data.extend_from_slice(&frame_header(a));
run(&data).1
};
assert_eq!(field_tag(0xC0).as_deref(), Some("I")); assert_eq!(field_tag(0xC8).as_deref(), Some("I")); assert_eq!(field_tag(0xD0).as_deref(), Some("P")); assert_eq!(field_tag(0xD8).as_deref(), Some("P")); assert_eq!(field_tag(0xE0).as_deref(), Some("B")); assert_eq!(field_tag(0xE8).as_deref(), Some("B")); assert_eq!(field_tag(0xF0).as_deref(), Some("BI")); assert_eq!(field_tag(0xF8).as_deref(), Some("BI")); assert_eq!(run(&frame_header(0xE0)).1.as_deref(), Some("BI"));
assert_eq!(run(&frame_header(0xF0)).1, None);
}
#[test]
fn progressive_after_a_non_interlaced_sequence_header_returns() {
let mut data = sequence_header(0xD8, 0x00);
data.extend_from_slice(&frame_header(0x40));
let (s, tag) = run(&data);
assert!(s.base.is_initialized);
assert_eq!(tag.as_deref(), Some("P"));
}
#[test]
fn a_frame_header_before_initialization_sets_the_tag_without_returning() {
let mut data = frame_header(0x00); data.extend_from_slice(&sequence_header(0xD8, 0x00));
let (s, tag) = run(&data);
assert_eq!(tag.as_deref(), Some("P"));
assert_eq!(s.encoding_profile.as_deref(), Some("Advanced Profile 3"));
}
#[test]
fn no_start_codes_leaves_the_stream_untouched() {
let (s, tag) = run(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
assert!(!s.base.is_initialized);
assert!(s.encoding_profile.is_none());
assert_eq!(tag, None);
}
proptest! {
#[test]
fn scan_never_panics_on_arbitrary_bytes(data in any::<Vec<u8>>()) {
let mut s = stream();
let mut b = buf(&data);
let mut tag = None;
scan(&mut s, &mut b, &mut tag);
}
}
}