use oxideav_core::bits::BitReader;
use oxideav_core::{Error, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SourceFormat {
Qcif,
Cif,
}
impl SourceFormat {
pub fn dimensions(self) -> (u32, u32) {
match self {
SourceFormat::Qcif => (176, 144),
SourceFormat::Cif => (352, 288),
}
}
pub fn gob_numbers(self) -> &'static [u8] {
match self {
SourceFormat::Qcif => &[1, 3, 5],
SourceFormat::Cif => &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
}
}
}
#[derive(Clone, Debug)]
pub struct PictureHeader {
pub temporal_reference: u8,
pub split_screen: bool,
pub document_camera: bool,
pub freeze_release: bool,
pub source_format: SourceFormat,
pub hi_res_off: bool,
pub width: u32,
pub height: u32,
}
impl PictureHeader {
pub fn still_image_sub_index(
&self,
) -> std::result::Result<Option<crate::annex_d::SubImageIndex>, crate::annex_d::AnnexDTrError>
{
if self.hi_res_off {
return Ok(None);
}
crate::annex_d::parse_still_image_tr(self.temporal_reference).map(Some)
}
}
pub fn parse_picture_header(br: &mut BitReader<'_>) -> Result<PictureHeader> {
let psc = br.read_u32(20)?;
const PSC_VALUE: u32 = 0x00010;
if psc != PSC_VALUE {
return Err(Error::invalid(format!(
"h261 picture: bad PSC 0x{psc:05x} (want 0x{PSC_VALUE:05x})"
)));
}
let tr = br.read_u32(5)? as u8;
let split_screen = br.read_u1()? == 1;
let document_camera = br.read_u1()? == 1;
let freeze_release = br.read_u1()? == 1;
let source_fmt_bit = br.read_u1()?;
let hi_res_bit = br.read_u1()?; let _spare = br.read_u1()?;
let source_format = if source_fmt_bit == 0 {
SourceFormat::Qcif
} else {
SourceFormat::Cif
};
loop {
let pei = br.read_u1()?;
if pei == 0 {
break;
}
let _pspare = br.read_u32(8)?;
}
let (width, height) = source_format.dimensions();
Ok(PictureHeader {
temporal_reference: tr,
split_screen,
document_camera,
freeze_release,
source_format,
hi_res_off: hi_res_bit == 1,
width,
height,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn minimal_qcif_header() -> Vec<u8> {
let mut bits: Vec<u8> = Vec::new();
let append = |v: &mut Vec<u8>, val: u32, n: u32| {
for i in (0..n).rev() {
v.push(((val >> i) & 1) as u8);
}
};
append(&mut bits, 0x00010, 20); append(&mut bits, 1, 5); append(&mut bits, 1, 1); append(&mut bits, 0, 1); append(&mut bits, 0, 1); append(&mut bits, 0, 1); append(&mut bits, 1, 1); append(&mut bits, 0, 1); append(&mut bits, 0, 1); while bits.len() % 8 != 0 {
bits.push(0);
}
let mut out = Vec::new();
for chunk in bits.chunks(8) {
let mut b = 0u8;
for (i, &bit) in chunk.iter().enumerate() {
b |= bit << (7 - i);
}
out.push(b);
}
out
}
#[test]
fn parses_qcif_header() {
let data = minimal_qcif_header();
let mut br = BitReader::new(&data);
let p = parse_picture_header(&mut br).unwrap();
assert_eq!(p.temporal_reference, 1);
assert_eq!(p.source_format, SourceFormat::Qcif);
assert!(p.split_screen);
assert!(!p.document_camera);
assert!(!p.freeze_release);
assert!(p.hi_res_off);
assert_eq!(p.width, 176);
assert_eq!(p.height, 144);
assert_eq!(p.still_image_sub_index(), Ok(None));
}
#[test]
fn still_image_sub_index_motion_video_is_none() {
let h = PictureHeader {
temporal_reference: 12,
split_screen: false,
document_camera: false,
freeze_release: false,
source_format: SourceFormat::Qcif,
hi_res_off: true, width: 176,
height: 144,
};
assert_eq!(h.still_image_sub_index(), Ok(None));
}
#[test]
fn still_image_sub_index_each_quadrant_round_trips() {
use crate::annex_d::SubImageIndex;
for n in 0u8..4 {
let h = PictureHeader {
temporal_reference: n,
split_screen: false,
document_camera: false,
freeze_release: false,
source_format: SourceFormat::Cif,
hi_res_off: false, width: 352,
height: 288,
};
assert_eq!(
h.still_image_sub_index(),
Ok(Some(SubImageIndex::from_u8(n)))
);
}
}
#[test]
fn still_image_sub_index_rejects_high_bits_in_tr() {
let h = PictureHeader {
temporal_reference: 0b00100,
split_screen: false,
document_camera: false,
freeze_release: false,
source_format: SourceFormat::Qcif,
hi_res_off: false,
width: 176,
height: 144,
};
assert!(h.still_image_sub_index().is_err());
}
}