use crate::bitstream::TsStreamBuffer;
use crate::stream::TsVideoStream;
pub fn scan(stream: &mut TsVideoStream, buffer: &mut TsStreamBuffer, tag: &mut Option<String>) {
let mut parse: u32 = 0;
let mut access_unit_delimiter_parse: u8 = 0;
let mut sequence_parameter_set_parse: u8 = 0;
let mut profile: &str = "";
let mut constraint_set3_flag: u32 = 0;
for _ in 0..buffer.length() {
parse = parse.wrapping_shl(8).wrapping_add(u32::from(buffer.read_byte(false)));
if parse == 0x0000_0109 {
access_unit_delimiter_parse = 1;
} else if access_unit_delimiter_parse > 0 {
access_unit_delimiter_parse = access_unit_delimiter_parse.wrapping_sub(1);
match (parse & 0xFF).wrapping_shr(5) {
0 | 3 | 5 => *tag = Some("I".to_owned()),
1 | 4 | 6 => *tag = Some("P".to_owned()),
_ => *tag = Some("B".to_owned()),
}
if stream.base.is_initialized {
return;
}
} else if parse == 0x0000_0127 || parse == 0x0000_0167 {
sequence_parameter_set_parse = 3;
} else if sequence_parameter_set_parse > 0 {
sequence_parameter_set_parse = sequence_parameter_set_parse.wrapping_sub(1);
if !stream.base.is_initialized {
match sequence_parameter_set_parse {
2 => {
profile = match parse & 0xFF {
66 => "Baseline Profile",
77 => "Main Profile",
88 => "Extended Profile",
100 => "High Profile",
110 => "High 10 Profile",
122 => "High 4:2:2 Profile",
144 | 244 => "High 4:4:4 Profile",
_ => "Unknown Profile",
};
}
1 => {
constraint_set3_flag = (parse & 0x10).wrapping_shr(4);
}
_ => {
let b = parse & 0xFF;
let level = if b == 11 && constraint_set3_flag == 1 {
"1b".to_owned()
} else {
format!("{}.{}", b.wrapping_div(10), b.wrapping_rem(10))
};
stream.encoding_profile = Some(format!("{profile} {level}"));
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::AvcVideo;
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 sps(profile_idc: u8, constraint: u8, level_idc: u8) -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x67, profile_idc, constraint, level_idc, 0x00]
}
#[test]
fn sps_sets_the_high_profile_4_1_encoding_profile() {
let (s, tag) = run(&sps(100, 0x00, 41));
assert_eq!(s.encoding_profile.as_deref(), Some("High Profile 4.1"));
assert!(s.base.is_vbr);
assert!(s.base.is_initialized);
assert_eq!(tag, None);
}
#[test]
fn sps_with_clpi_metadata_matches_the_expected_avc_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(&sps(100, 0x00, 41));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.description(), "1080p / 23.976 fps / 16:9 / High Profile 4.1");
assert_eq!(s.codec_short_name(), "AVC");
assert_eq!(s.codec_name(), "MPEG-4 AVC Video");
}
#[test]
fn every_profile_idc_maps_to_its_name() {
let cases = [
(66_u8, "Baseline Profile"),
(77, "Main Profile"),
(88, "Extended Profile"),
(100, "High Profile"),
(110, "High 10 Profile"),
(122, "High 4:2:2 Profile"),
(144, "High 4:4:4 Profile"),
(244, "High 4:4:4 Profile"),
(44, "Unknown Profile"),
(0, "Unknown Profile"),
(200, "Unknown Profile"),
];
for (idc, name) in cases {
let (s, _) = run(&sps(idc, 0x00, 41));
assert_eq!(s.encoding_profile.as_deref(), Some(format!("{name} 4.1").as_str()));
}
}
#[test]
fn level_1b_needs_both_level_11_and_constraint_set_3() {
let (s, _) = run(&sps(66, 0x10, 11));
assert_eq!(s.encoding_profile.as_deref(), Some("Baseline Profile 1b"));
let (s, _) = run(&sps(66, 0x00, 11));
assert_eq!(s.encoding_profile.as_deref(), Some("Baseline Profile 1.1"));
let (s, _) = run(&sps(66, 0x10, 30));
assert_eq!(s.encoding_profile.as_deref(), Some("Baseline Profile 3.0"));
}
#[test]
fn level_byte_splits_into_tens_and_units() {
for (level, text) in [(51_u8, "5.1"), (30, "3.0"), (0, "0.0")] {
let (s, _) = run(&sps(77, 0x00, level));
assert_eq!(
s.encoding_profile.as_deref(),
Some(format!("Main Profile {text}").as_str())
);
}
}
#[test]
fn access_unit_delimiter_sets_the_picture_type_tag() {
let aud = |pic: u8| vec![0x00, 0x00, 0x01, 0x09, pic << 5, 0x00];
for code in [0_u8, 3, 5] {
assert_eq!(run(&aud(code)).1.as_deref(), Some("I"), "pic {code}");
}
for code in [1_u8, 4, 6] {
assert_eq!(run(&aud(code)).1.as_deref(), Some("P"), "pic {code}");
}
for code in [2_u8, 7] {
assert_eq!(run(&aud(code)).1.as_deref(), Some("B"), "pic {code}");
}
}
#[test]
fn an_initialized_stream_returns_at_the_next_access_unit_delimiter() {
let mut data = sps(100, 0x00, 41);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x09, 0x40]); data.extend_from_slice(&sps(77, 0x00, 51)); let (s, tag) = run(&data);
assert_eq!(tag.as_deref(), Some("B"));
assert_eq!(s.encoding_profile.as_deref(), Some("High Profile 4.1"));
}
#[test]
fn sps_on_an_already_initialized_stream_is_skipped() {
let mut s = stream();
s.base.is_initialized = true;
s.encoding_profile = Some("Existing".to_owned());
let mut b = buf(&sps(100, 0x00, 41));
scan(&mut s, &mut b, &mut None);
assert_eq!(s.encoding_profile.as_deref(), Some("Existing"));
}
#[test]
fn an_access_unit_delimiter_on_an_uninitialized_stream_continues() {
let mut data = vec![0x00, 0x00, 0x01, 0x09, 0x00, 0x00]; data.extend_from_slice(&sps(77, 0x00, 51));
let (s, tag) = run(&data);
assert_eq!(tag.as_deref(), Some("I"));
assert_eq!(s.encoding_profile.as_deref(), Some("Main Profile 5.1"));
}
#[test]
fn the_alternate_sps_start_code_is_recognized() {
let data = vec![0x00, 0x00, 0x01, 0x27, 100, 0x00, 41, 0x00];
let (s, _) = run(&data);
assert_eq!(s.encoding_profile.as_deref(), Some("High Profile 4.1"));
}
#[test]
fn no_start_codes_leaves_the_stream_untouched() {
let (s, tag) = run(&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);
assert!(s.encoding_profile.is_none());
assert!(!s.base.is_initialized);
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);
}
}
}