use crate::bit_reader::BitReader;
use crate::BitstreamError;
pub const BDU_SEQUENCE_HEADER: u8 = 0x0F;
pub const BDU_ENTRY_POINT: u8 = 0x0E;
pub const BDU_FRAME: u8 = 0x0D;
pub const BDU_FIELD: u8 = 0x0C;
pub const BDU_SLICE: u8 = 0x0B;
pub const BDU_END_OF_SEQUENCE: u8 = 0x1F;
pub const PROFILE_SIMPLE: u8 = 0;
pub const PROFILE_MAIN: u8 = 1;
pub const PROFILE_ADVANCED: u8 = 3;
#[derive(Debug, Clone, Copy)]
pub struct VcBdu<'a> {
pub bdu_type: u8,
pub payload: &'a [u8],
}
pub fn split_bdus(stream: &[u8]) -> Vec<VcBdu<'_>> {
let mut out = Vec::new();
let mut i = 0;
let n = stream.len();
let mut current: Option<(u8, usize)> = None; while i + 3 < n {
if stream[i] == 0 && stream[i + 1] == 0 && stream[i + 2] == 1 {
if let Some((t, body_start)) = current.take() {
out.push(VcBdu {
bdu_type: t,
payload: &stream[body_start..i],
});
}
let bdu_type = stream[i + 3];
i += 4;
current = Some((bdu_type, i));
continue;
}
i += 1;
}
if let Some((t, body_start)) = current.take() {
out.push(VcBdu {
bdu_type: t,
payload: &stream[body_start..n],
});
}
out
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Vc1SequenceHeader {
pub profile: u8,
pub level: u8,
pub colordiff_format: u8,
pub frmrtq_postproc: u8,
pub bitrtq_postproc: u8,
pub postprocflag: bool,
pub max_coded_width: u32,
pub max_coded_height: u32,
pub pulldown: bool,
pub interlace: bool,
pub tfcntrflag: bool,
pub finterpflag: bool,
pub psf: bool,
pub display_ext: bool,
pub display_horiz_size: u32,
pub display_vert_size: u32,
pub aspect_ratio_flag: bool,
pub aspect_ratio: u8,
pub aspect_horiz_size: u8,
pub aspect_vert_size: u8,
pub framerate_flag: bool,
pub framerateind: u8,
pub frameratenr: u32,
pub frameratedr: u8,
pub color_format_flag: bool,
pub color_primaries: u8,
pub transfer_char: u8,
pub matrix_coef: u8,
pub hrd_param_flag: bool,
pub hrd_num_leaky_buckets: u8,
pub hrd_bit_rate_exponent: u8,
pub hrd_buffer_size_exponent: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Vc1EntryPointHeader {
pub broken_link: bool,
pub closed_entry: bool,
pub panscan_flag: bool,
pub refdist_flag: bool,
pub loopfilter: bool,
pub fastuvmc: bool,
pub extended_mv: bool,
pub dquant: u8,
pub vstransform: bool,
pub overlap: bool,
pub quantizer: u8,
pub coded_size_flag: bool,
pub coded_width: u32,
pub coded_height: u32,
pub extended_dmv: bool,
pub range_mapy_flag: bool,
pub range_mapy: u8,
pub range_mapuv_flag: bool,
pub range_mapuv: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Vc1PictureHeader {
pub fcm: u8,
pub picture_type: u8,
}
#[derive(Debug)]
pub struct Vc1FirstPictureParse<'a> {
pub sequence_header: Vc1SequenceHeader,
pub entry_point: Vc1EntryPointHeader,
pub picture_header: Vc1PictureHeader,
pub frame_payload: &'a [u8],
}
pub fn parse_sequence_header_advanced(bytes: &[u8]) -> Result<Vc1SequenceHeader, BitstreamError> {
if bytes.len() < 6 {
return Err(BitstreamError::unexpected_end(
"VC-1 sequence header shorter than 6 bytes",
));
}
let mut r = BitReader::new(bytes);
let mut s = Vc1SequenceHeader {
profile: r.u(2) as u8,
..Vc1SequenceHeader::default()
};
if s.profile != PROFILE_ADVANCED {
return Err(BitstreamError::unsupported(format!(
"VC-1 profile {} not supported (only Advanced/3)",
s.profile
)));
}
s.level = r.u(3) as u8;
s.colordiff_format = r.u(2) as u8;
s.frmrtq_postproc = r.u(3) as u8;
s.bitrtq_postproc = r.u(5) as u8;
s.postprocflag = r.u(1) != 0;
s.max_coded_width = r.u(12) + 1;
s.max_coded_height = r.u(12) + 1;
s.pulldown = r.u(1) != 0;
s.interlace = r.u(1) != 0;
s.tfcntrflag = r.u(1) != 0;
s.finterpflag = r.u(1) != 0;
let _reserved = r.u(1);
s.psf = r.u(1) != 0;
s.display_ext = r.u(1) != 0;
if s.display_ext {
s.display_horiz_size = r.u(14) + 1;
s.display_vert_size = r.u(14) + 1;
s.aspect_ratio_flag = r.u(1) != 0;
if s.aspect_ratio_flag {
s.aspect_ratio = r.u(4) as u8;
if s.aspect_ratio == 15 {
s.aspect_horiz_size = r.u(8) as u8;
s.aspect_vert_size = r.u(8) as u8;
}
}
s.framerate_flag = r.u(1) != 0;
if s.framerate_flag {
s.framerateind = r.u(1) as u8;
if s.framerateind == 0 {
s.frameratenr = r.u(8);
s.frameratedr = r.u(4) as u8;
} else {
s.frameratenr = r.u(16);
}
}
s.color_format_flag = r.u(1) != 0;
if s.color_format_flag {
s.color_primaries = r.u(8) as u8;
s.transfer_char = r.u(8) as u8;
s.matrix_coef = r.u(8) as u8;
}
}
s.hrd_param_flag = r.u(1) != 0;
if s.hrd_param_flag {
s.hrd_num_leaky_buckets = r.u(5) as u8;
s.hrd_bit_rate_exponent = r.u(4) as u8;
s.hrd_buffer_size_exponent = r.u(4) as u8;
for _ in 0..s.hrd_num_leaky_buckets {
let _hrd_rate = r.u(16);
let _hrd_buffer = r.u(16);
}
}
Ok(s)
}
pub fn parse_entry_point_header(
bytes: &[u8],
seq: &Vc1SequenceHeader,
) -> Result<Vc1EntryPointHeader, BitstreamError> {
if bytes.is_empty() {
return Err(BitstreamError::unexpected_end(
"VC-1 entry-point header empty",
));
}
let mut r = BitReader::new(bytes);
let mut e = Vc1EntryPointHeader {
broken_link: r.u(1) != 0,
closed_entry: r.u(1) != 0,
panscan_flag: r.u(1) != 0,
refdist_flag: r.u(1) != 0,
loopfilter: r.u(1) != 0,
fastuvmc: r.u(1) != 0,
extended_mv: r.u(1) != 0,
dquant: r.u(2) as u8,
vstransform: r.u(1) != 0,
overlap: r.u(1) != 0,
quantizer: r.u(2) as u8,
..Vc1EntryPointHeader::default()
};
if seq.hrd_param_flag {
for _ in 0..seq.hrd_num_leaky_buckets {
let _hrd_full = r.u(8);
}
}
e.coded_size_flag = r.u(1) != 0;
if e.coded_size_flag {
e.coded_width = r.u(12) + 1;
e.coded_height = r.u(12) + 1;
}
if e.extended_mv {
e.extended_dmv = r.u(1) != 0;
}
e.range_mapy_flag = r.u(1) != 0;
if e.range_mapy_flag {
e.range_mapy = r.u(3) as u8;
}
e.range_mapuv_flag = r.u(1) != 0;
if e.range_mapuv_flag {
e.range_mapuv = r.u(3) as u8;
}
Ok(e)
}
pub fn parse_picture_header(
bytes: &[u8],
seq: &Vc1SequenceHeader,
) -> Result<Vc1PictureHeader, BitstreamError> {
if bytes.is_empty() {
return Err(BitstreamError::unexpected_end("VC-1 picture header empty"));
}
let mut r = BitReader::new(bytes);
let mut h = Vc1PictureHeader::default();
if seq.interlace {
let fcm0 = r.u(1);
if fcm0 == 0 {
h.fcm = 0;
} else {
let fcm1 = r.u(1);
h.fcm = if fcm1 == 0 { 2 } else { 3 };
}
} else {
h.fcm = 0;
}
if h.fcm == 0 {
let b0 = r.u(1);
if b0 == 0 {
h.picture_type = 1; } else {
let b1 = r.u(1);
if b1 == 0 {
h.picture_type = 2; } else {
let b2 = r.u(1);
if b2 == 0 {
h.picture_type = 0; } else {
let b3 = r.u(1);
h.picture_type = if b3 == 0 { 3 } else { 4 }; }
}
}
} else {
h.picture_type = r.u(3) as u8;
}
Ok(h)
}
pub fn parse_first_picture(stream: &[u8]) -> Result<Vc1FirstPictureParse<'_>, BitstreamError> {
let bdus = split_bdus(stream);
let seq = bdus
.iter()
.find(|b| b.bdu_type == BDU_SEQUENCE_HEADER)
.ok_or_else(|| BitstreamError::invalid("VC-1: no sequence header BDU in stream"))?;
let sequence_header = parse_sequence_header_advanced(seq.payload)?;
let entry = bdus
.iter()
.find(|b| b.bdu_type == BDU_ENTRY_POINT)
.ok_or_else(|| BitstreamError::invalid("VC-1: no entry-point BDU in stream"))?;
let entry_point = parse_entry_point_header(entry.payload, &sequence_header)?;
let frame = bdus
.iter()
.find(|b| b.bdu_type == BDU_FRAME)
.ok_or_else(|| BitstreamError::invalid("VC-1: no frame BDU in stream"))?;
let picture_header = parse_picture_header(frame.payload, &sequence_header)?;
Ok(Vc1FirstPictureParse {
sequence_header,
entry_point,
picture_header,
frame_payload: frame.payload,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_bdus_finds_seq_entry_frame() {
let stream = [
0,
0,
1,
BDU_SEQUENCE_HEADER,
0xaa,
0,
0,
1,
BDU_ENTRY_POINT,
0xbb,
0,
0,
1,
BDU_FRAME,
0xcc,
0xdd,
];
let bdus = split_bdus(&stream);
assert_eq!(bdus.len(), 3);
assert_eq!(bdus[0].bdu_type, BDU_SEQUENCE_HEADER);
assert_eq!(bdus[0].payload, &[0xaa]);
assert_eq!(bdus[1].bdu_type, BDU_ENTRY_POINT);
assert_eq!(bdus[1].payload, &[0xbb]);
assert_eq!(bdus[2].bdu_type, BDU_FRAME);
assert_eq!(bdus[2].payload, &[0xcc, 0xdd]);
}
#[test]
fn parse_sequence_header_rejects_simple_profile() {
let buf = [0u8; 6];
assert!(matches!(
parse_sequence_header_advanced(&buf),
Err(BitstreamError::Unsupported(_))
));
}
#[test]
fn parse_sequence_header_rejects_short_input() {
assert!(matches!(
parse_sequence_header_advanced(&[0u8; 2]),
Err(BitstreamError::UnexpectedEnd(_))
));
}
}