#![cfg(feature = "mpeg2")]
use oximedia_codec::mpeg2::bitreader::{
BitReader, EXTENSION_START_CODE, PICTURE_START_CODE, SEQUENCE_HEADER_CODE,
};
use oximedia_codec::mpeg2::dequant::{dequantize_intra, intra_dc_mult, DEFAULT_INTRA_MATRIX};
use oximedia_codec::mpeg2::headers::parse_sequence_header;
use oximedia_codec::mpeg2::idct::idct_8x8;
use oximedia_codec::mpeg2::Mpeg2Decoder;
#[derive(Default)]
struct BitWriter {
bits: Vec<u8>,
}
impl BitWriter {
fn new() -> Self {
Self::default()
}
fn put(&mut self, value: u32, len: u8) {
for i in (0..len).rev() {
self.bits.push(((value >> i) & 1) as u8);
}
}
fn align(&mut self) {
while self.bits.len() % 8 != 0 {
self.bits.push(0);
}
}
fn into_bytes(mut self) -> Vec<u8> {
self.align();
self.bits
.chunks(8)
.map(|c| c.iter().fold(0u8, |acc, &b| (acc << 1) | b))
.collect()
}
}
fn push_start_code(out: &mut Vec<u8>, code: u8) {
out.extend_from_slice(&[0x00, 0x00, 0x01, code]);
}
fn sequence_header_payload(width: u32, height: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.put(width & 0xFFF, 12); w.put(height & 0xFFF, 12); w.put(1, 4); w.put(3, 4); w.put(0x3FFFF, 18); w.put(1, 1); w.put(112, 10); w.put(0, 1); w.put(0, 1); w.put(0, 1); w.into_bytes()
}
fn sequence_extension_payload() -> Vec<u8> {
let mut w = BitWriter::new();
w.put(0b0001, 4); w.put(0x44, 8); w.put(0, 1); w.put(1, 2); w.put(0, 2); w.put(0, 2); w.put(0, 12); w.put(1, 1); w.put(0, 8); w.put(0, 1); w.put(0, 2); w.put(0, 5); w.into_bytes()
}
fn picture_header_payload() -> Vec<u8> {
let mut w = BitWriter::new();
w.put(0, 10); w.put(1, 3); w.put(0xFFFF, 16); w.into_bytes()
}
fn picture_coding_extension_payload(
intra_dc_precision: u8,
intra_vlc_format: bool,
alternate_scan: bool,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.put(0b1000, 4); w.put(0xF, 4); w.put(0xF, 4); w.put(0xF, 4); w.put(0xF, 4); w.put(u32::from(intra_dc_precision), 2);
w.put(3, 2); w.put(0, 1); w.put(1, 1); w.put(0, 1); w.put(0, 1); w.put(u32::from(intra_vlc_format), 1);
w.put(u32::from(alternate_scan), 1);
w.put(0, 1); w.put(1, 1); w.put(1, 1); w.put(0, 1); w.into_bytes()
}
fn put_grey_macroblock(w: &mut BitWriter) {
w.put(0b1, 1); w.put(0b1, 1); for blk in 0..6 {
if blk < 4 {
w.put(0b100, 3);
} else {
w.put(0b00, 2);
}
w.put(0b10, 2);
}
}
fn build_grey_iframe(width: u32, height: u32) -> Vec<u8> {
let mut stream = Vec::new();
push_start_code(&mut stream, SEQUENCE_HEADER_CODE);
stream.extend_from_slice(&sequence_header_payload(width, height));
push_start_code(&mut stream, EXTENSION_START_CODE);
stream.extend_from_slice(&sequence_extension_payload());
push_start_code(&mut stream, PICTURE_START_CODE);
stream.extend_from_slice(&picture_header_payload());
push_start_code(&mut stream, EXTENSION_START_CODE);
stream.extend_from_slice(&picture_coding_extension_payload(0, false, false));
push_start_code(&mut stream, 0x01);
let mut slice = BitWriter::new();
slice.put(2, 5); slice.put(0, 1);
let mb_cols = (width as usize).div_ceil(16);
for _ in 0..mb_cols {
put_grey_macroblock(&mut slice);
}
stream.extend_from_slice(&slice.into_bytes());
push_start_code(&mut stream, 0xB7);
stream
}
#[test]
fn parse_sequence_header_synthetic() {
let payload = sequence_header_payload(16, 16);
let mut reader = BitReader::new(&payload);
let sh = parse_sequence_header(&mut reader).expect("parse sequence header");
assert_eq!(sh.horizontal_size_value, 16);
assert_eq!(sh.vertical_size_value, 16);
assert_eq!(sh.aspect_ratio_information, 1);
assert_eq!(sh.frame_rate_code, 3);
assert_eq!(sh.intra_quantiser_matrix, DEFAULT_INTRA_MATRIX);
}
#[test]
fn idct_dc_only_block() {
let mut coeffs = [0i32; 64];
coeffs[0] = 1024; let out = idct_8x8(&coeffs);
for &v in &out {
assert!((v - 128).abs() <= 1, "expected flat ~128, got {v}");
}
let mut coeffs = [0i32; 64];
coeffs[0] = 512;
let out = idct_8x8(&coeffs);
for &v in &out {
assert!((v - 64).abs() <= 1, "expected flat ~64, got {v}");
}
}
#[test]
fn dequant_intra_dc_precision() {
for (prec, mult) in [(0u8, 8i32), (1, 4), (2, 2), (3, 1)] {
assert_eq!(intra_dc_mult(prec), mult);
let mut q = [0i32; 64];
q[0] = 5;
let f = dequantize_intra(&q, &DEFAULT_INTRA_MATRIX, prec, 4);
assert_eq!(f[0], 5 * mult, "precision {prec}");
}
}
#[test]
fn decode_intra_macroblock_constant_grey() {
let stream = build_grey_iframe(16, 16);
let decoder = Mpeg2Decoder::new();
let frame = decoder.decode(&stream).expect("decode grey frame");
assert_eq!(frame.width, 16);
assert_eq!(frame.height, 16);
assert_eq!(frame.y.len(), 16 * 16);
assert_eq!(frame.cb.len(), 8 * 8);
assert_eq!(frame.cr.len(), 8 * 8);
for (i, &v) in frame.y.iter().enumerate() {
assert!(
(i32::from(v) - 128).abs() <= 2,
"luma[{i}] = {v}, expected ~128"
);
}
for (i, &v) in frame.cb.iter().enumerate() {
assert!(
(i32::from(v) - 128).abs() <= 2,
"cb[{i}] = {v}, expected ~128"
);
}
for (i, &v) in frame.cr.iter().enumerate() {
assert!(
(i32::from(v) - 128).abs() <= 2,
"cr[{i}] = {v}, expected ~128"
);
}
}
#[test]
fn decode_wider_grey_frame() {
let stream = build_grey_iframe(32, 16);
let decoder = Mpeg2Decoder::new();
let frame = decoder.decode(&stream).expect("decode 32x16 grey");
assert_eq!(frame.width, 32);
assert_eq!(frame.height, 16);
assert_eq!(frame.y.len(), 32 * 16);
for &v in &frame.y {
assert!((i32::from(v) - 128).abs() <= 2, "luma {v} expected ~128");
}
}
#[test]
fn reject_truncated_stream() {
let mut stream = build_grey_iframe(16, 16);
stream.truncate(8);
let decoder = Mpeg2Decoder::new();
assert!(decoder.decode(&stream).is_err());
assert!(Mpeg2Decoder::new().decode(&[]).is_err());
assert!(Mpeg2Decoder::new()
.decode(&[0xDE, 0xAD, 0xBE, 0xEF])
.is_err());
}
#[test]
fn reject_p_picture() {
let mut stream = Vec::new();
push_start_code(&mut stream, SEQUENCE_HEADER_CODE);
stream.extend_from_slice(&sequence_header_payload(16, 16));
push_start_code(&mut stream, EXTENSION_START_CODE);
stream.extend_from_slice(&sequence_extension_payload());
push_start_code(&mut stream, PICTURE_START_CODE);
let mut ph = BitWriter::new();
ph.put(0, 10); ph.put(2, 3); ph.put(0, 16); ph.put(0, 1); ph.put(0b111, 3); stream.extend_from_slice(&ph.into_bytes());
let decoder = Mpeg2Decoder::new();
assert!(decoder.decode(&stream).is_err());
}