mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
M9.15: MPEG-1/2 video decoder crate (frame/field pictures, 4:2:2, IEEE 1180 IDCT) with ffmpeg oracle tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ac4d458712
commit
2cbf91dded
Generated
+17
@@ -2012,6 +2012,23 @@ dependencies = [
|
|||||||
"thiserror 2.0.21",
|
"thiserror 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filmcraft-mpeg2v"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"filmcraft-testkit",
|
||||||
|
"rayon",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filmcraft-mpegts"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"filmcraft-testkit",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filmcraft-mxf"
|
name = "filmcraft-mxf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ filmcraft-bitstream = { path = "crates/bitstream" }
|
|||||||
filmcraft-isobmff = { path = "crates/isobmff" }
|
filmcraft-isobmff = { path = "crates/isobmff" }
|
||||||
filmcraft-matroska = { path = "crates/matroska" }
|
filmcraft-matroska = { path = "crates/matroska" }
|
||||||
filmcraft-mxf = { path = "crates/mxf" }
|
filmcraft-mxf = { path = "crates/mxf" }
|
||||||
|
filmcraft-mpegts = { path = "crates/mpegts" }
|
||||||
|
filmcraft-mpeg2v = { path = "crates/mpeg2v" }
|
||||||
filmcraft-ogg = { path = "crates/ogg" }
|
filmcraft-ogg = { path = "crates/ogg" }
|
||||||
filmcraft-h264 = { path = "crates/h264" }
|
filmcraft-h264 = { path = "crates/h264" }
|
||||||
filmcraft-hevc = { path = "crates/hevc" }
|
filmcraft-hevc = { path = "crates/hevc" }
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "filmcraft-mpeg2v"
|
||||||
|
description = "Clean-room MPEG-2 video (ITU-T H.262 | ISO/IEC 13818-2) and MPEG-1 video (ISO/IEC 11172-2) decoder"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["threads"]
|
||||||
|
threads = ["dep:rayon"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
rayon = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
filmcraft-testkit = { workspace = true }
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
//! Big-endian bit reader over one start-code unit. Reads past the end return zero bits; callers
|
||||||
|
//! check [`Bits::overrun`] to detect truncated data.
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Bits<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Bits<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> Self {
|
||||||
|
Self { data, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next 32 bits, MSB first (zeros past the end).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn peek32(&self) -> u32 {
|
||||||
|
let byte = self.pos >> 3;
|
||||||
|
let w = if byte + 8 <= self.data.len() {
|
||||||
|
u64::from_be_bytes(self.data[byte..byte + 8].try_into().expect("8 bytes"))
|
||||||
|
} else {
|
||||||
|
let mut b = [0u8; 8];
|
||||||
|
if byte < self.data.len() {
|
||||||
|
let n = self.data.len() - byte;
|
||||||
|
b[..n].copy_from_slice(&self.data[byte..]);
|
||||||
|
}
|
||||||
|
u64::from_be_bytes(b)
|
||||||
|
};
|
||||||
|
((w << (self.pos & 7)) >> 32) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next `n` (1..=32) bits without consuming them.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn peek(&self, n: u32) -> u32 {
|
||||||
|
debug_assert!((1..=32).contains(&n));
|
||||||
|
((self.peek32() as u64) >> (32 - n)) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn skip(&mut self, n: u32) {
|
||||||
|
self.pos += n as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read(&mut self, n: u32) -> u32 {
|
||||||
|
if n == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let v = self.peek(n);
|
||||||
|
self.pos += n as usize;
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn bit(&mut self) -> bool {
|
||||||
|
self.read(1) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `n`-bit two's complement value.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read_signed(&mut self, n: u32) -> i32 {
|
||||||
|
let v = self.read(n) as i32;
|
||||||
|
(v << (32 - n)) >> (32 - n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bits_left(&self) -> isize {
|
||||||
|
(self.data.len() * 8) as isize - self.pos as isize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// More than a few bytes have been read past the end: the data is truncated or corrupt.
|
||||||
|
pub fn overrun(&self) -> bool {
|
||||||
|
self.bits_left() < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reads_across_bytes_and_past_the_end() {
|
||||||
|
let d = [0b1010_1100, 0xFF, 0x01];
|
||||||
|
let mut b = Bits::new(&d);
|
||||||
|
assert_eq!(b.read(3), 0b101);
|
||||||
|
assert_eq!(b.read(7), 0b011_0011);
|
||||||
|
assert_eq!(b.peek(4), 0xF);
|
||||||
|
assert_eq!(b.read_signed(4), -1);
|
||||||
|
assert_eq!(b.read(10), 0b11_0000_0001);
|
||||||
|
assert!(!b.overrun());
|
||||||
|
assert_eq!(b.read(8), 0);
|
||||||
|
assert!(b.overrun());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,466 @@
|
|||||||
|
//! Start codes and headers (H.262 §6.2 / §6.3; ISO/IEC 11172-2 §2.4.2).
|
||||||
|
|
||||||
|
use crate::bits::Bits;
|
||||||
|
use crate::{Error, Result};
|
||||||
|
|
||||||
|
pub const PICTURE_START: u8 = 0x00;
|
||||||
|
pub const SLICE_MIN: u8 = 0x01;
|
||||||
|
pub const SLICE_MAX: u8 = 0xAF;
|
||||||
|
pub const USER_DATA: u8 = 0xB2;
|
||||||
|
pub const SEQUENCE_HEADER: u8 = 0xB3;
|
||||||
|
pub const SEQUENCE_ERROR: u8 = 0xB4;
|
||||||
|
pub const EXTENSION: u8 = 0xB5;
|
||||||
|
pub const SEQUENCE_END: u8 = 0xB7;
|
||||||
|
pub const GROUP_START: u8 = 0xB8;
|
||||||
|
|
||||||
|
/// Start codes in `data`: (offset of the `00 00 01` prefix, start code value). A unit's payload
|
||||||
|
/// runs from `offset + 4` to the next start code.
|
||||||
|
pub fn start_codes(data: &[u8]) -> Vec<(usize, u8)> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
let n = data.len();
|
||||||
|
while i + 3 < n {
|
||||||
|
// skip quickly to a zero byte at i + 2
|
||||||
|
if data[i + 2] > 1 {
|
||||||
|
i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
|
||||||
|
out.push((i, data[i + 3]));
|
||||||
|
i += 4;
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default intra quantiser matrix (H.262 §6.3.11), raster order.
|
||||||
|
pub const DEFAULT_INTRA: [u8; 64] = [
|
||||||
|
8, 16, 19, 22, 26, 27, 29, 34, //
|
||||||
|
16, 16, 22, 24, 27, 29, 34, 37, //
|
||||||
|
19, 22, 26, 27, 29, 34, 34, 38, //
|
||||||
|
22, 22, 26, 27, 29, 34, 37, 40, //
|
||||||
|
22, 26, 27, 29, 32, 35, 40, 48, //
|
||||||
|
26, 27, 29, 32, 35, 40, 48, 58, //
|
||||||
|
26, 27, 29, 34, 38, 46, 56, 69, //
|
||||||
|
27, 29, 35, 38, 46, 56, 69, 83,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Scan index → raster position: zigzag (scan 0) and alternate (scan 1), H.262 Figure 7-2/7-3.
|
||||||
|
pub const SCAN: [[u8; 64]; 2] = [
|
||||||
|
[
|
||||||
|
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36,
|
||||||
|
29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0, 8, 16, 24, 1, 9, 2, 10, 17, 25, 32, 40, 48, 56, 57, 49, 41, 33, 26, 18, 3, 11, 4, 12, 19, 27, 34, 42, 50, 58, 35, 43, 51, 59, 20, 28, 5, 13, 6, 14,
|
||||||
|
21, 29, 36, 44, 52, 60, 37, 45, 53, 61, 22, 30, 7, 15, 23, 31, 38, 46, 54, 62, 39, 47, 55, 63,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/// A quantiser matrix as transmitted (zigzag order) → raster order.
|
||||||
|
fn read_matrix(b: &mut Bits) -> [u8; 64] {
|
||||||
|
let mut m = [0u8; 64];
|
||||||
|
for i in 0..64 {
|
||||||
|
m[SCAN[0][i] as usize] = b.read(8) as u8;
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sequence_header() (§6.2.2.1).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct SequenceHeader {
|
||||||
|
pub horizontal_size: u32,
|
||||||
|
pub vertical_size: u32,
|
||||||
|
pub aspect_ratio_information: u8,
|
||||||
|
pub frame_rate_code: u8,
|
||||||
|
pub bit_rate_value: u32,
|
||||||
|
pub vbv_buffer_size_value: u32,
|
||||||
|
pub constrained_parameters: bool,
|
||||||
|
/// Raster order; `None`: default.
|
||||||
|
pub intra_matrix: Option<[u8; 64]>,
|
||||||
|
pub non_intra_matrix: Option<[u8; 64]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequenceHeader {
|
||||||
|
pub fn parse(payload: &[u8]) -> Result<Self> {
|
||||||
|
if payload.len() < 8 {
|
||||||
|
return Err(Error::Invalid("short sequence header".into()));
|
||||||
|
}
|
||||||
|
let mut b = Bits::new(payload);
|
||||||
|
let horizontal_size = b.read(12);
|
||||||
|
let vertical_size = b.read(12);
|
||||||
|
let aspect_ratio_information = b.read(4) as u8;
|
||||||
|
let frame_rate_code = b.read(4) as u8;
|
||||||
|
let bit_rate_value = b.read(18);
|
||||||
|
b.skip(1);
|
||||||
|
let vbv_buffer_size_value = b.read(10);
|
||||||
|
let constrained_parameters = b.bit();
|
||||||
|
let intra_matrix = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
let non_intra_matrix = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
if b.overrun() {
|
||||||
|
return Err(Error::Invalid("truncated sequence header".into()));
|
||||||
|
}
|
||||||
|
if horizontal_size == 0 || vertical_size == 0 {
|
||||||
|
return Err(Error::Invalid("zero picture size".into()));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
horizontal_size,
|
||||||
|
vertical_size,
|
||||||
|
aspect_ratio_information,
|
||||||
|
frame_rate_code,
|
||||||
|
bit_rate_value,
|
||||||
|
vbv_buffer_size_value,
|
||||||
|
constrained_parameters,
|
||||||
|
intra_matrix,
|
||||||
|
non_intra_matrix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sequence_extension() (§6.2.2.3).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SequenceExtension {
|
||||||
|
pub profile_and_level_indication: u8,
|
||||||
|
pub progressive_sequence: bool,
|
||||||
|
/// 1 = 4:2:0, 2 = 4:2:2, 3 = 4:4:4.
|
||||||
|
pub chroma_format: u8,
|
||||||
|
pub horizontal_size_extension: u8,
|
||||||
|
pub vertical_size_extension: u8,
|
||||||
|
pub bit_rate_extension: u16,
|
||||||
|
pub vbv_buffer_size_extension: u8,
|
||||||
|
pub low_delay: bool,
|
||||||
|
pub frame_rate_extension_n: u8,
|
||||||
|
pub frame_rate_extension_d: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sequence_display_extension() (§6.2.2.4).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
|
pub struct DisplayExtension {
|
||||||
|
pub video_format: u8,
|
||||||
|
/// (colour_primaries, transfer_characteristics, matrix_coefficients), H.262 Tables 6-7..6-9.
|
||||||
|
pub colour: Option<(u8, u8, u8)>,
|
||||||
|
pub display_horizontal_size: u16,
|
||||||
|
pub display_vertical_size: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// quant_matrix_extension() (§6.2.3.2), raster order.
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct QuantMatrixExtension {
|
||||||
|
pub intra: Option<[u8; 64]>,
|
||||||
|
pub non_intra: Option<[u8; 64]>,
|
||||||
|
pub chroma_intra: Option<[u8; 64]>,
|
||||||
|
pub chroma_non_intra: Option<[u8; 64]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// picture_coding_extension() (§6.2.3.1).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct PictureCodingExtension {
|
||||||
|
/// f_code[s][t]: s 0 forward / 1 backward, t 0 horizontal / 1 vertical.
|
||||||
|
pub f_code: [[u8; 2]; 2],
|
||||||
|
pub intra_dc_precision: u8,
|
||||||
|
/// 1 top field, 2 bottom field, 3 frame picture.
|
||||||
|
pub picture_structure: u8,
|
||||||
|
pub top_field_first: bool,
|
||||||
|
pub frame_pred_frame_dct: bool,
|
||||||
|
pub concealment_motion_vectors: bool,
|
||||||
|
pub q_scale_type: bool,
|
||||||
|
pub intra_vlc_format: bool,
|
||||||
|
pub alternate_scan: bool,
|
||||||
|
pub repeat_first_field: bool,
|
||||||
|
pub chroma_420_type: bool,
|
||||||
|
pub progressive_frame: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PictureCodingExtension {
|
||||||
|
/// The values an MPEG-1 picture behaves as (progressive frame picture, zigzag, linear q).
|
||||||
|
pub fn mpeg1(ph: &PictureHeader) -> Self {
|
||||||
|
Self {
|
||||||
|
f_code: [[ph.forward_f_code; 2], [ph.backward_f_code; 2]],
|
||||||
|
intra_dc_precision: 0,
|
||||||
|
picture_structure: 3,
|
||||||
|
top_field_first: false,
|
||||||
|
frame_pred_frame_dct: true,
|
||||||
|
concealment_motion_vectors: false,
|
||||||
|
q_scale_type: false,
|
||||||
|
intra_vlc_format: false,
|
||||||
|
alternate_scan: false,
|
||||||
|
repeat_first_field: false,
|
||||||
|
chroma_420_type: true,
|
||||||
|
progressive_frame: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension unit (§6.2.2.2 / §6.2.3).
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Extension {
|
||||||
|
Sequence(SequenceExtension),
|
||||||
|
Display(DisplayExtension),
|
||||||
|
QuantMatrix(Box<QuantMatrixExtension>),
|
||||||
|
PictureCoding(PictureCodingExtension),
|
||||||
|
/// Scalability, copyright, picture display… (not needed to decode the base layer).
|
||||||
|
Other(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension {
|
||||||
|
pub fn parse(payload: &[u8]) -> Result<Self> {
|
||||||
|
let mut b = Bits::new(payload);
|
||||||
|
let id = b.read(4) as u8;
|
||||||
|
let e = match id {
|
||||||
|
1 => {
|
||||||
|
let profile_and_level_indication = b.read(8) as u8;
|
||||||
|
let progressive_sequence = b.bit();
|
||||||
|
let chroma_format = b.read(2) as u8;
|
||||||
|
let horizontal_size_extension = b.read(2) as u8;
|
||||||
|
let vertical_size_extension = b.read(2) as u8;
|
||||||
|
let bit_rate_extension = b.read(12) as u16;
|
||||||
|
b.skip(1);
|
||||||
|
let vbv_buffer_size_extension = b.read(8) as u8;
|
||||||
|
let low_delay = b.bit();
|
||||||
|
let frame_rate_extension_n = b.read(2) as u8;
|
||||||
|
let frame_rate_extension_d = b.read(5) as u8;
|
||||||
|
Extension::Sequence(SequenceExtension {
|
||||||
|
profile_and_level_indication,
|
||||||
|
progressive_sequence,
|
||||||
|
chroma_format,
|
||||||
|
horizontal_size_extension,
|
||||||
|
vertical_size_extension,
|
||||||
|
bit_rate_extension,
|
||||||
|
vbv_buffer_size_extension,
|
||||||
|
low_delay,
|
||||||
|
frame_rate_extension_n,
|
||||||
|
frame_rate_extension_d,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let video_format = b.read(3) as u8;
|
||||||
|
let colour = b.bit().then(|| (b.read(8) as u8, b.read(8) as u8, b.read(8) as u8));
|
||||||
|
let display_horizontal_size = b.read(14) as u16;
|
||||||
|
b.skip(1);
|
||||||
|
let display_vertical_size = b.read(14) as u16;
|
||||||
|
Extension::Display(DisplayExtension { video_format, colour, display_horizontal_size, display_vertical_size })
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let intra = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
let non_intra = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
let chroma_intra = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
let chroma_non_intra = b.bit().then(|| read_matrix(&mut b));
|
||||||
|
Extension::QuantMatrix(Box::new(QuantMatrixExtension { intra, non_intra, chroma_intra, chroma_non_intra }))
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
let f_code = [[b.read(4) as u8, b.read(4) as u8], [b.read(4) as u8, b.read(4) as u8]];
|
||||||
|
let intra_dc_precision = b.read(2) as u8;
|
||||||
|
let picture_structure = b.read(2) as u8;
|
||||||
|
let top_field_first = b.bit();
|
||||||
|
let frame_pred_frame_dct = b.bit();
|
||||||
|
let concealment_motion_vectors = b.bit();
|
||||||
|
let q_scale_type = b.bit();
|
||||||
|
let intra_vlc_format = b.bit();
|
||||||
|
let alternate_scan = b.bit();
|
||||||
|
let repeat_first_field = b.bit();
|
||||||
|
let chroma_420_type = b.bit();
|
||||||
|
let progressive_frame = b.bit();
|
||||||
|
if picture_structure == 0 {
|
||||||
|
return Err(Error::Invalid("reserved picture_structure".into()));
|
||||||
|
}
|
||||||
|
Extension::PictureCoding(PictureCodingExtension {
|
||||||
|
f_code,
|
||||||
|
intra_dc_precision,
|
||||||
|
picture_structure,
|
||||||
|
top_field_first,
|
||||||
|
frame_pred_frame_dct,
|
||||||
|
concealment_motion_vectors,
|
||||||
|
q_scale_type,
|
||||||
|
intra_vlc_format,
|
||||||
|
alternate_scan,
|
||||||
|
repeat_first_field,
|
||||||
|
chroma_420_type,
|
||||||
|
progressive_frame,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
other => Extension::Other(other),
|
||||||
|
};
|
||||||
|
if b.overrun() {
|
||||||
|
return Err(Error::Invalid(format!("truncated extension {id}")));
|
||||||
|
}
|
||||||
|
Ok(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// group_of_pictures_header() (§6.2.2.6).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct GopHeader {
|
||||||
|
pub drop_frame: bool,
|
||||||
|
pub hours: u8,
|
||||||
|
pub minutes: u8,
|
||||||
|
pub seconds: u8,
|
||||||
|
pub pictures: u8,
|
||||||
|
pub closed_gop: bool,
|
||||||
|
pub broken_link: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GopHeader {
|
||||||
|
pub fn parse(payload: &[u8]) -> Result<Self> {
|
||||||
|
if payload.len() < 4 {
|
||||||
|
return Err(Error::Invalid("short GOP header".into()));
|
||||||
|
}
|
||||||
|
let mut b = Bits::new(payload);
|
||||||
|
let drop_frame = b.bit();
|
||||||
|
let hours = b.read(5) as u8;
|
||||||
|
let minutes = b.read(6) as u8;
|
||||||
|
b.skip(1);
|
||||||
|
let seconds = b.read(6) as u8;
|
||||||
|
let pictures = b.read(6) as u8;
|
||||||
|
let closed_gop = b.bit();
|
||||||
|
let broken_link = b.bit();
|
||||||
|
Ok(Self { drop_frame, hours, minutes, seconds, pictures, closed_gop, broken_link })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// picture_coding_type.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum PictureType {
|
||||||
|
I,
|
||||||
|
P,
|
||||||
|
B,
|
||||||
|
/// ISO/IEC 11172-2 DC-coded picture.
|
||||||
|
D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PictureType {
|
||||||
|
pub fn is_anchor(self) -> bool {
|
||||||
|
matches!(self, PictureType::I | PictureType::P | PictureType::D)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// picture_header() (§6.2.3).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct PictureHeader {
|
||||||
|
pub temporal_reference: u16,
|
||||||
|
pub coding_type: PictureType,
|
||||||
|
pub vbv_delay: u16,
|
||||||
|
pub full_pel_forward_vector: bool,
|
||||||
|
pub forward_f_code: u8,
|
||||||
|
pub full_pel_backward_vector: bool,
|
||||||
|
pub backward_f_code: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PictureHeader {
|
||||||
|
pub fn parse(payload: &[u8]) -> Result<Self> {
|
||||||
|
if payload.len() < 4 {
|
||||||
|
return Err(Error::Invalid("short picture header".into()));
|
||||||
|
}
|
||||||
|
let mut b = Bits::new(payload);
|
||||||
|
let temporal_reference = b.read(10) as u16;
|
||||||
|
let coding_type = match b.read(3) {
|
||||||
|
1 => PictureType::I,
|
||||||
|
2 => PictureType::P,
|
||||||
|
3 => PictureType::B,
|
||||||
|
4 => PictureType::D,
|
||||||
|
t => return Err(Error::Invalid(format!("picture_coding_type {t}"))),
|
||||||
|
};
|
||||||
|
let vbv_delay = b.read(16) as u16;
|
||||||
|
let (mut full_pel_forward_vector, mut forward_f_code, mut full_pel_backward_vector, mut backward_f_code) = (false, 7, false, 7);
|
||||||
|
if matches!(coding_type, PictureType::P | PictureType::B) {
|
||||||
|
full_pel_forward_vector = b.bit();
|
||||||
|
forward_f_code = b.read(3) as u8;
|
||||||
|
}
|
||||||
|
if coding_type == PictureType::B {
|
||||||
|
full_pel_backward_vector = b.bit();
|
||||||
|
backward_f_code = b.read(3) as u8;
|
||||||
|
}
|
||||||
|
Ok(Self { temporal_reference, coding_type, vbv_delay, full_pel_forward_vector, forward_f_code, full_pel_backward_vector, backward_f_code })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frame rate from frame_rate_code (Table 6-4) and the sequence extension's n/d (MPEG-2).
|
||||||
|
pub fn frame_rate(code: u8, ext_n: u8, ext_d: u8) -> Option<(u32, u32)> {
|
||||||
|
let (n, d) = match code {
|
||||||
|
1 => (24000, 1001),
|
||||||
|
2 => (24, 1),
|
||||||
|
3 => (25, 1),
|
||||||
|
4 => (30000, 1001),
|
||||||
|
5 => (30, 1),
|
||||||
|
6 => (50, 1),
|
||||||
|
7 => (60000, 1001),
|
||||||
|
8 => (60, 1),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let (n, d) = (n * (ext_n as u32 + 1), d * (ext_d as u32 + 1));
|
||||||
|
let g = gcd(n, d);
|
||||||
|
Some((n / g, d / g))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gcd(a: u32, b: u32) -> u32 {
|
||||||
|
if b == 0 { a.max(1) } else { gcd(b, a % b) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Profile and level names (Tables 8-2, 8-3, 8-7).
|
||||||
|
pub fn profile_level_name(pli: u8) -> String {
|
||||||
|
if pli & 0x80 != 0 {
|
||||||
|
return match pli {
|
||||||
|
0x82 => "4:2:2@High".into(),
|
||||||
|
0x85 => "4:2:2@Main".into(),
|
||||||
|
0x8A => "Multi-view@High".into(),
|
||||||
|
0x8B => "Multi-view@High-1440".into(),
|
||||||
|
0x8D => "Multi-view@Main".into(),
|
||||||
|
0x8E => "Multi-view@Low".into(),
|
||||||
|
other => format!("profile/level 0x{other:02x}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let profile = match (pli >> 4) & 7 {
|
||||||
|
1 => "High",
|
||||||
|
2 => "Spatially Scalable",
|
||||||
|
3 => "SNR Scalable",
|
||||||
|
4 => "Main",
|
||||||
|
5 => "Simple",
|
||||||
|
_ => "Reserved",
|
||||||
|
};
|
||||||
|
let level = match pli & 15 {
|
||||||
|
4 => "High",
|
||||||
|
6 => "High-1440",
|
||||||
|
8 => "Main",
|
||||||
|
10 => "Low",
|
||||||
|
_ => "Reserved",
|
||||||
|
};
|
||||||
|
format!("{profile}@{level}")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scans_are_permutations_and_inverse_of_the_figures() {
|
||||||
|
for s in SCAN {
|
||||||
|
let mut seen = [false; 64];
|
||||||
|
for &p in &s {
|
||||||
|
assert!(!seen[p as usize]);
|
||||||
|
seen[p as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Figure 7-3 row 0 of the alternate scan: 0 4 6 20 22 36 38 52
|
||||||
|
let inv: Vec<usize> = (0..8).map(|u| SCAN[1].iter().position(|&p| p as usize == u).unwrap()).collect();
|
||||||
|
assert_eq!(inv, vec![0, 4, 6, 20, 22, 36, 38, 52]);
|
||||||
|
// Figure 7-2 row 0 of the zigzag scan: 0 1 5 6 14 15 27 28
|
||||||
|
let inv: Vec<usize> = (0..8).map(|u| SCAN[0].iter().position(|&p| p as usize == u).unwrap()).collect();
|
||||||
|
assert_eq!(inv, vec![0, 1, 5, 6, 14, 15, 27, 28]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_codes_found_at_every_alignment() {
|
||||||
|
let d = [0, 0, 1, 0xB3, 9, 0, 0, 0, 1, 0x00, 0, 0, 1, 0x01, 0, 0];
|
||||||
|
assert_eq!(start_codes(&d), vec![(0, 0xB3), (6, 0x00), (10, 0x01)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frame_rates() {
|
||||||
|
assert_eq!(frame_rate(4, 0, 0), Some((30000, 1001)));
|
||||||
|
assert_eq!(frame_rate(3, 1, 0), Some((50, 1)));
|
||||||
|
assert_eq!(frame_rate(0, 0, 0), None);
|
||||||
|
assert_eq!(profile_level_name(0x44), "Main@High");
|
||||||
|
assert_eq!(profile_level_name(0x85), "4:2:2@Main");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
//! 8×8 inverse DCT (H.262 Annex A): the separable 2-D IDCT evaluated in `f32` with even/odd
|
||||||
|
//! symmetry, rounded to the nearest integer and saturated to [-256, 255]. Its error is far inside
|
||||||
|
//! the IEEE Std 1180-1990 accuracy limits the standard requires (checked by the test procedure of
|
||||||
|
//! IEEE 1180 itself, below).
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
/// `M[x][u] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`, `C(u>0) = 1`.
|
||||||
|
fn basis() -> &'static [[f32; 8]; 8] {
|
||||||
|
static B: OnceLock<[[f32; 8]; 8]> = OnceLock::new();
|
||||||
|
B.get_or_init(|| {
|
||||||
|
let mut m = [[0f32; 8]; 8];
|
||||||
|
for (x, row) in m.iter_mut().enumerate() {
|
||||||
|
for (u, v) in row.iter_mut().enumerate() {
|
||||||
|
let c = if u == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 };
|
||||||
|
*v = (c / 2.0 * (((2 * x + 1) * u) as f64 * std::f64::consts::PI / 16.0).cos()) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In-place IDCT of a block of dequantised coefficients (raster order). `dc_only`: only `blk[0]`
|
||||||
|
/// may be non-zero.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn idct(blk: &mut [i32; 64], dc_only: bool) {
|
||||||
|
if dc_only {
|
||||||
|
let v = ((blk[0] + 4) >> 3).clamp(-256, 255);
|
||||||
|
blk.fill(v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let m = basis();
|
||||||
|
// The DC term F[0][0]/8 is added exactly (so ties round as in the DC-only path).
|
||||||
|
let dc = blk[0] as f32 * 0.125;
|
||||||
|
blk[0] = 0;
|
||||||
|
// rows: tmp[v][x] = Σ_u M[x][u] F[v][u]
|
||||||
|
let mut tmp = [[0f32; 8]; 8];
|
||||||
|
for v in 0..8 {
|
||||||
|
let r = &blk[v * 8..v * 8 + 8];
|
||||||
|
if r.iter().all(|&c| c == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let f: [f32; 8] = std::array::from_fn(|u| r[u] as f32);
|
||||||
|
for x in 0..4 {
|
||||||
|
let mx = &m[x];
|
||||||
|
let e = mx[0] * f[0] + mx[2] * f[2] + mx[4] * f[4] + mx[6] * f[6];
|
||||||
|
let o = mx[1] * f[1] + mx[3] * f[3] + mx[5] * f[5] + mx[7] * f[7];
|
||||||
|
tmp[v][x] = e + o;
|
||||||
|
tmp[v][7 - x] = e - o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// columns, eight at a time: out[y][x] = Σ_v M[y][v] tmp[v][x]
|
||||||
|
for y in 0..4 {
|
||||||
|
let my = &m[y];
|
||||||
|
let mut e = [0f32; 8];
|
||||||
|
let mut o = [0f32; 8];
|
||||||
|
for x in 0..8 {
|
||||||
|
e[x] = my[0] * tmp[0][x] + my[2] * tmp[2][x] + my[4] * tmp[4][x] + my[6] * tmp[6][x];
|
||||||
|
o[x] = my[1] * tmp[1][x] + my[3] * tmp[3][x] + my[5] * tmp[5][x] + my[7] * tmp[7][x];
|
||||||
|
}
|
||||||
|
for x in 0..8 {
|
||||||
|
blk[y * 8 + x] = ((dc + (e[x] + o[x]) + 0.5).floor() as i32).clamp(-256, 255);
|
||||||
|
blk[(7 - y) * 8 + x] = ((dc + (e[x] - o[x]) + 0.5).floor() as i32).clamp(-256, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// The pseudo-random generator of IEEE Std 1180-1990 (values in [-l, h]).
|
||||||
|
struct Rand(i64);
|
||||||
|
impl Rand {
|
||||||
|
fn next(&mut self, l: i64, h: i64) -> i64 {
|
||||||
|
self.0 = (self.0.wrapping_mul(1_103_515_245).wrapping_add(12_345)) & 0xFFFF_FFFF;
|
||||||
|
let i = self.0 & 0x7FFF_FFFE;
|
||||||
|
let x = i as f64 / 0x7FFF_FFFF as f64 * (l + h + 1) as f64;
|
||||||
|
x as i64 - l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn c(u: usize) -> f64 {
|
||||||
|
if u == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cosv(a: usize, b: usize) -> f64 {
|
||||||
|
static T: OnceLock<[[f64; 8]; 8]> = OnceLock::new();
|
||||||
|
T.get_or_init(|| std::array::from_fn(|a| std::array::from_fn(|b| (((2 * a + 1) * b) as f64 * std::f64::consts::PI / 16.0).cos())))[a][b]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Separable double-precision 1-D passes (exact up to f64 rounding).
|
||||||
|
fn fdct(p: &[i64; 64]) -> [i64; 64] {
|
||||||
|
let mut t = [0f64; 64];
|
||||||
|
for y in 0..8 {
|
||||||
|
for u in 0..8 {
|
||||||
|
t[y * 8 + u] = (0..8).map(|x| p[y * 8 + x] as f64 * cosv(x, u)).sum::<f64>() * c(u) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut out = [0i64; 64];
|
||||||
|
for v in 0..8 {
|
||||||
|
for u in 0..8 {
|
||||||
|
let s = (0..8).map(|y| t[y * 8 + u] * cosv(y, v)).sum::<f64>() * c(v) / 2.0;
|
||||||
|
out[v * 8 + u] = (s.round() as i64).clamp(-2048, 2047);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reference_idct(f: &[i64; 64]) -> [i64; 64] {
|
||||||
|
let mut t = [0f64; 64];
|
||||||
|
for v in 0..8 {
|
||||||
|
for x in 0..8 {
|
||||||
|
t[v * 8 + x] = (0..8).map(|u| c(u) * f[v * 8 + u] as f64 * cosv(x, u)).sum::<f64>() / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut out = [0i64; 64];
|
||||||
|
for y in 0..8 {
|
||||||
|
for x in 0..8 {
|
||||||
|
let s = (0..8).map(|v| c(v) * t[v * 8 + x] * cosv(y, v)).sum::<f64>() / 2.0;
|
||||||
|
out[y * 8 + x] = (s.round() as i64).clamp(-256, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IEEE 1180-1990 §3: 10 000 blocks for each range and sign; peak error ≤ 1, per-pixel mean
|
||||||
|
/// square error ≤ 0.06, overall ≤ 0.02, per-pixel mean error ≤ 0.015, overall ≤ 0.0015.
|
||||||
|
#[test]
|
||||||
|
fn ieee_1180_accuracy() {
|
||||||
|
let blocks = if cfg!(debug_assertions) { 2_000 } else { 10_000 };
|
||||||
|
for (l, h) in [(256i64, 255i64), (5, 5), (300, 300)] {
|
||||||
|
for sign in [1i64, -1] {
|
||||||
|
let mut rng = Rand(1);
|
||||||
|
let mut sum_err = [0i64; 64];
|
||||||
|
let mut sum_sq = [0i64; 64];
|
||||||
|
for _ in 0..blocks {
|
||||||
|
let mut p = [0i64; 64];
|
||||||
|
for v in p.iter_mut() {
|
||||||
|
*v = rng.next(l, h) * sign;
|
||||||
|
}
|
||||||
|
let f = fdct(&p);
|
||||||
|
let r = reference_idct(&f);
|
||||||
|
let mut blk: [i32; 64] = std::array::from_fn(|i| f[i] as i32);
|
||||||
|
idct(&mut blk, false);
|
||||||
|
for i in 0..64 {
|
||||||
|
let e = blk[i] as i64 - r[i];
|
||||||
|
assert!(e.abs() <= 1, "peak error {e} (range {l}/{h} sign {sign})");
|
||||||
|
sum_err[i] += e;
|
||||||
|
sum_sq[i] += e * e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let n = blocks as f64;
|
||||||
|
for i in 0..64 {
|
||||||
|
assert!(sum_sq[i] as f64 / n <= 0.06);
|
||||||
|
assert!((sum_err[i] as f64 / n).abs() <= 0.015);
|
||||||
|
}
|
||||||
|
assert!(sum_sq.iter().sum::<i64>() as f64 / (64.0 * n) <= 0.02);
|
||||||
|
assert!((sum_err.iter().sum::<i64>() as f64 / (64.0 * n)).abs() <= 0.0015);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all-zero input gives all-zero output
|
||||||
|
let mut z = [0i32; 64];
|
||||||
|
idct(&mut z, false);
|
||||||
|
assert!(z.iter().all(|&v| v == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dc_only_matches_the_full_transform() {
|
||||||
|
for dc in -2048..=2047 {
|
||||||
|
let mut a = [0i32; 64];
|
||||||
|
a[0] = dc;
|
||||||
|
let mut b = a;
|
||||||
|
idct(&mut a, true);
|
||||||
|
idct(&mut b, false);
|
||||||
|
assert_eq!(a, b, "dc {dc}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,800 @@
|
|||||||
|
//! Clean-room MPEG-2 video (ITU-T H.262 | ISO/IEC 13818-2) and MPEG-1 video (ISO/IEC 11172-2)
|
||||||
|
//! decoder.
|
||||||
|
//!
|
||||||
|
//! - Main Profile at Low/Main/High-1440/High Level, 4:2:2 Profile (XDCAM HD422, IMX / D-10),
|
||||||
|
//! Simple Profile, and MPEG-1 (including D pictures and constrained-parameters streams).
|
||||||
|
//! - Progressive and interlaced coding: frame pictures (frame / field / dual-prime motion
|
||||||
|
//! compensation, frame and field DCT) and field pictures (field / 16x8 / dual-prime motion
|
||||||
|
//! compensation, the second field predicted from the first), alternate scan, both intra VLC
|
||||||
|
//! tables, linear and non-linear quantiser scale, quantiser matrices (sequence header and quant
|
||||||
|
//! matrix extension), intra DC precision 8-11 bits, concealment motion vectors.
|
||||||
|
//! - Output in display order as planar 8-bit frames (two fields woven into one frame), with
|
||||||
|
//! field order, repeat-first-field, picture type and colour description.
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! let mut dec = filmcraft_mpeg2v::Decoder::new();
|
||||||
|
//! # let access_units: Vec<(Vec<u8>, i64)> = vec![];
|
||||||
|
//! for (au, pts) in access_units {
|
||||||
|
//! for pic in dec.decode(&au, pts)? {
|
||||||
|
//! println!("{}x{} {:?} pts {}", pic.width, pic.height, pic.picture_type, pic.pts);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! let rest = dec.flush();
|
||||||
|
//! # Ok::<(), filmcraft_mpeg2v::Error>(())
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
mod bits;
|
||||||
|
pub mod headers;
|
||||||
|
mod idct;
|
||||||
|
mod mc;
|
||||||
|
mod slice;
|
||||||
|
mod vlc;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
pub use headers::{
|
||||||
|
DisplayExtension, Extension, GopHeader, PictureCodingExtension, PictureHeader, PictureType, QuantMatrixExtension, SequenceExtension, SequenceHeader,
|
||||||
|
profile_level_name, start_codes,
|
||||||
|
};
|
||||||
|
|
||||||
|
use headers::{EXTENSION, GROUP_START, PICTURE_START, SEQUENCE_END, SEQUENCE_HEADER, SLICE_MAX, SLICE_MIN};
|
||||||
|
use mc::Frame;
|
||||||
|
use slice::{PicParams, Target, decode_slice, slice_row};
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("invalid MPEG video: {0}")]
|
||||||
|
Invalid(String),
|
||||||
|
#[error("unsupported MPEG video: {0}")]
|
||||||
|
Unsupported(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ChromaFormat {
|
||||||
|
Yuv420,
|
||||||
|
Yuv422,
|
||||||
|
Yuv444,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream-level properties (sequence header and its extensions).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct SequenceInfo {
|
||||||
|
/// ISO/IEC 13818-2 (has a sequence extension) or ISO/IEC 11172-2.
|
||||||
|
pub mpeg2: bool,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub chroma: ChromaFormat,
|
||||||
|
pub profile_and_level: Option<u8>,
|
||||||
|
pub progressive_sequence: bool,
|
||||||
|
pub low_delay: bool,
|
||||||
|
/// Frames per second (num, den).
|
||||||
|
pub frame_rate: Option<(u32, u32)>,
|
||||||
|
/// Sample aspect ratio.
|
||||||
|
pub sar: (u32, u32),
|
||||||
|
/// Display aspect ratio when signalled (MPEG-2 aspect_ratio_information 2-4).
|
||||||
|
pub dar: Option<(u32, u32)>,
|
||||||
|
/// (colour_primaries, transfer_characteristics, matrix_coefficients) from the sequence
|
||||||
|
/// display extension.
|
||||||
|
pub colour: Option<(u8, u8, u8)>,
|
||||||
|
pub video_format: Option<u8>,
|
||||||
|
/// bits/s (0: unknown / variable).
|
||||||
|
pub bit_rate: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SequenceInfo {
|
||||||
|
/// "MPEG-2 Video (Main@High)", "MPEG-1 Video".
|
||||||
|
pub fn codec_name(&self) -> String {
|
||||||
|
match (self.mpeg2, self.profile_and_level) {
|
||||||
|
(false, _) => "MPEG-1 Video".into(),
|
||||||
|
(true, Some(p)) => format!("MPEG-2 Video ({})", profile_level_name(p)),
|
||||||
|
(true, None) => "MPEG-2 Video".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A decoded frame (both fields of field-coded pictures), cropped to the coded size.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Picture {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub chroma: ChromaFormat,
|
||||||
|
pub y: Vec<u8>,
|
||||||
|
pub cb: Vec<u8>,
|
||||||
|
pub cr: Vec<u8>,
|
||||||
|
/// The timestamp passed with the access unit the picture (its first field) came in.
|
||||||
|
pub pts: i64,
|
||||||
|
pub picture_type: PictureType,
|
||||||
|
pub temporal_reference: u16,
|
||||||
|
pub progressive_frame: bool,
|
||||||
|
pub top_field_first: bool,
|
||||||
|
pub repeat_first_field: bool,
|
||||||
|
/// Coded as two field pictures.
|
||||||
|
pub field_pictures: bool,
|
||||||
|
pub info: Arc<SequenceInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Picture {
|
||||||
|
pub fn chroma_width(&self) -> u32 {
|
||||||
|
match self.chroma {
|
||||||
|
ChromaFormat::Yuv444 => self.width,
|
||||||
|
_ => self.width.div_ceil(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn chroma_height(&self) -> u32 {
|
||||||
|
match self.chroma {
|
||||||
|
ChromaFormat::Yuv420 => self.height.div_ceil(2),
|
||||||
|
_ => self.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Interlaced content and which field comes first: `None` for progressive frames.
|
||||||
|
pub fn field_order(&self) -> Option<FieldOrder> {
|
||||||
|
if self.progressive_frame {
|
||||||
|
None
|
||||||
|
} else if self.top_field_first {
|
||||||
|
Some(FieldOrder::TopFirst)
|
||||||
|
} else {
|
||||||
|
Some(FieldOrder::BottomFirst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FieldOrder {
|
||||||
|
TopFirst,
|
||||||
|
BottomFirst,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The active sequence.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Seq {
|
||||||
|
hdr: SequenceHeader,
|
||||||
|
ext: Option<SequenceExtension>,
|
||||||
|
display: Option<DisplayExtension>,
|
||||||
|
info: Arc<SequenceInfo>,
|
||||||
|
mb_width: usize,
|
||||||
|
mb_height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MPEG-1 pel_aspect_ratio (height/width of a sample) ×10000, Table 2-D.2.
|
||||||
|
const MPEG1_PEL_ASPECT: [u32; 15] = [0, 10000, 6735, 7031, 7615, 8055, 8437, 8935, 9157, 9815, 10255, 10695, 10950, 11575, 12015];
|
||||||
|
|
||||||
|
impl Seq {
|
||||||
|
fn new(hdr: SequenceHeader, ext: Option<SequenceExtension>, display: Option<DisplayExtension>) -> Result<Seq> {
|
||||||
|
let (w, h) = match &ext {
|
||||||
|
Some(e) => (hdr.horizontal_size | (e.horizontal_size_extension as u32) << 12, hdr.vertical_size | (e.vertical_size_extension as u32) << 12),
|
||||||
|
None => (hdr.horizontal_size, hdr.vertical_size),
|
||||||
|
};
|
||||||
|
let chroma = match ext.map_or(1, |e| e.chroma_format) {
|
||||||
|
1 => ChromaFormat::Yuv420,
|
||||||
|
2 => ChromaFormat::Yuv422,
|
||||||
|
3 => ChromaFormat::Yuv444,
|
||||||
|
_ => return Err(Error::Invalid("reserved chroma_format".into())),
|
||||||
|
};
|
||||||
|
if w > 16383 || h > 16383 {
|
||||||
|
return Err(Error::Unsupported(format!("{w}x{h}")));
|
||||||
|
}
|
||||||
|
let progressive_sequence = ext.is_none_or(|e| e.progressive_sequence);
|
||||||
|
let mb_width = (w as usize).div_ceil(16);
|
||||||
|
let mb_height = if progressive_sequence { (h as usize).div_ceil(16) } else { 2 * (h as usize).div_ceil(32) };
|
||||||
|
let frame_rate = headers::frame_rate(hdr.frame_rate_code, ext.map_or(0, |e| e.frame_rate_extension_n), ext.map_or(0, |e| e.frame_rate_extension_d));
|
||||||
|
let (sar, dar) = if ext.is_some() {
|
||||||
|
let (dw, dh) = display
|
||||||
|
.filter(|d| d.display_horizontal_size > 0 && d.display_vertical_size > 0)
|
||||||
|
.map_or((w, h), |d| (d.display_horizontal_size as u32, d.display_vertical_size as u32));
|
||||||
|
let dar = match hdr.aspect_ratio_information {
|
||||||
|
2 => Some((4, 3)),
|
||||||
|
3 => Some((16, 9)),
|
||||||
|
4 => Some((221, 100)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let sar = match dar {
|
||||||
|
Some((n, d)) => {
|
||||||
|
// the display rectangle (dw × dh samples) has this aspect: SAR = DAR · dh / dw
|
||||||
|
let (n, d) = (n as u64 * dh as u64, d as u64 * dw as u64);
|
||||||
|
let g = gcd64(n, d);
|
||||||
|
((n / g) as u32, (d / g) as u32)
|
||||||
|
}
|
||||||
|
None => (1, 1),
|
||||||
|
};
|
||||||
|
(reduce_sar(sar), dar)
|
||||||
|
} else {
|
||||||
|
let v = MPEG1_PEL_ASPECT.get(hdr.aspect_ratio_information as usize).copied().filter(|&v| v > 0).unwrap_or(10000);
|
||||||
|
let g = headers::gcd(10000, v);
|
||||||
|
((10000 / g, v / g), None)
|
||||||
|
};
|
||||||
|
let bit_rate = (hdr.bit_rate_value as u64 | (ext.map_or(0, |e| e.bit_rate_extension as u64) << 18)) * 400;
|
||||||
|
let info = SequenceInfo {
|
||||||
|
mpeg2: ext.is_some(),
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
chroma,
|
||||||
|
profile_and_level: ext.map(|e| e.profile_and_level_indication),
|
||||||
|
progressive_sequence,
|
||||||
|
low_delay: ext.is_some_and(|e| e.low_delay),
|
||||||
|
frame_rate,
|
||||||
|
sar,
|
||||||
|
dar,
|
||||||
|
colour: display.and_then(|d| d.colour),
|
||||||
|
video_format: display.map(|d| d.video_format),
|
||||||
|
bit_rate: if hdr.bit_rate_value == 0x3FFFF && ext.is_none() { 0 } else { bit_rate },
|
||||||
|
};
|
||||||
|
Ok(Seq { hdr, ext, display, info: Arc::new(info), mb_width, mb_height })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shifts(&self) -> (u32, u32) {
|
||||||
|
match self.info.chroma {
|
||||||
|
ChromaFormat::Yuv420 => (1, 1),
|
||||||
|
ChromaFormat::Yuv422 => (1, 0),
|
||||||
|
ChromaFormat::Yuv444 => (0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gcd64(a: u64, b: u64) -> u64 {
|
||||||
|
if b == 0 { a.max(1) } else { gcd64(b, a % b) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce_sar((n, d): (u32, u32)) -> (u32, u32) {
|
||||||
|
if n == 0 || d == 0 {
|
||||||
|
return (1, 1);
|
||||||
|
}
|
||||||
|
let g = headers::gcd(n, d);
|
||||||
|
(n / g, d / g)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the first sequence header (and its extensions) in `data`.
|
||||||
|
pub fn probe(data: &[u8]) -> Option<SequenceInfo> {
|
||||||
|
let codes = start_codes(data);
|
||||||
|
let mut seq: Option<(SequenceHeader, Option<SequenceExtension>, Option<DisplayExtension>)> = None;
|
||||||
|
for (k, &(pos, code)) in codes.iter().enumerate() {
|
||||||
|
let end = codes.get(k + 1).map_or(data.len(), |c| c.0);
|
||||||
|
let payload = &data[(pos + 4).min(end)..end];
|
||||||
|
match code {
|
||||||
|
SEQUENCE_HEADER if seq.is_none() => seq = Some((SequenceHeader::parse(payload).ok()?, None, None)),
|
||||||
|
EXTENSION => {
|
||||||
|
if let Some(s) = seq.as_mut() {
|
||||||
|
match Extension::parse(payload) {
|
||||||
|
Ok(Extension::Sequence(e)) => s.1 = Some(e),
|
||||||
|
Ok(Extension::Display(d)) => s.2 = Some(d),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ if seq.is_some() => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (h, e, d) = seq?;
|
||||||
|
Seq::new(h, e, d).ok().map(|s| (*s.info).clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What an access unit holds, without decoding it.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct AccessUnitInfo {
|
||||||
|
pub sequence_header: bool,
|
||||||
|
pub gop: Option<GopHeader>,
|
||||||
|
pub pictures: Vec<(PictureHeader, Option<PictureCodingExtension>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccessUnitInfo {
|
||||||
|
/// Starts with an I picture (decoding can begin here when a sequence header has been seen).
|
||||||
|
pub fn is_intra(&self) -> bool {
|
||||||
|
self.pictures.first().is_some_and(|p| matches!(p.0.coding_type, PictureType::I | PictureType::D))
|
||||||
|
}
|
||||||
|
/// Only B pictures (never used for prediction).
|
||||||
|
pub fn is_disposable(&self) -> bool {
|
||||||
|
!self.pictures.is_empty() && self.pictures.iter().all(|p| p.0.coding_type == PictureType::B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scan_access_unit(data: &[u8]) -> AccessUnitInfo {
|
||||||
|
let codes = start_codes(data);
|
||||||
|
let mut info = AccessUnitInfo::default();
|
||||||
|
for (k, &(pos, code)) in codes.iter().enumerate() {
|
||||||
|
let end = codes.get(k + 1).map_or(data.len(), |c| c.0);
|
||||||
|
let payload = &data[(pos + 4).min(end)..end];
|
||||||
|
match code {
|
||||||
|
SEQUENCE_HEADER => info.sequence_header = true,
|
||||||
|
GROUP_START => info.gop = GopHeader::parse(payload).ok(),
|
||||||
|
PICTURE_START => {
|
||||||
|
if let Ok(ph) = PictureHeader::parse(payload) {
|
||||||
|
info.pictures.push((ph, None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXTENSION => {
|
||||||
|
if let (Ok(Extension::PictureCoding(p)), Some(last)) = (Extension::parse(payload), info.pictures.last_mut()) {
|
||||||
|
last.1 = Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split an elementary stream into access units: one coded frame each (a frame picture or a
|
||||||
|
/// pair of field pictures) with the sequence / GOP headers before it. Bytes before the first
|
||||||
|
/// header are skipped.
|
||||||
|
pub fn access_units(es: &[u8]) -> Vec<std::ops::Range<usize>> {
|
||||||
|
let codes = start_codes(es);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut cur: Option<usize> = None;
|
||||||
|
let mut header: Option<usize> = None;
|
||||||
|
let mut open_field: Option<u8> = None;
|
||||||
|
for (k, &(pos, code)) in codes.iter().enumerate() {
|
||||||
|
match code {
|
||||||
|
SEQUENCE_HEADER | GROUP_START => {
|
||||||
|
header.get_or_insert(pos);
|
||||||
|
}
|
||||||
|
PICTURE_START => {
|
||||||
|
// picture_structure from the picture coding extension that follows
|
||||||
|
let structure = codes[k + 1..]
|
||||||
|
.iter()
|
||||||
|
.take_while(|c| c.1 == EXTENSION || c.1 == headers::USER_DATA)
|
||||||
|
.find_map(|&(p, c)| {
|
||||||
|
let end = codes.iter().find(|x| x.0 > p).map_or(es.len(), |x| x.0);
|
||||||
|
(c == EXTENSION).then(|| Extension::parse(&es[(p + 4).min(end)..end]).ok()).flatten()
|
||||||
|
})
|
||||||
|
.and_then(|e| match e {
|
||||||
|
Extension::PictureCoding(p) => Some(p.picture_structure),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(3);
|
||||||
|
let start = header.take().unwrap_or(pos);
|
||||||
|
let second = matches!(open_field, Some(s) if structure != 3 && structure != s);
|
||||||
|
if second {
|
||||||
|
open_field = None;
|
||||||
|
} else {
|
||||||
|
if let Some(c) = cur {
|
||||||
|
out.push(c..start);
|
||||||
|
}
|
||||||
|
cur = Some(start);
|
||||||
|
open_field = (structure != 3).then_some(structure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(c) = cur {
|
||||||
|
out.push(c..es.len());
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A picture whose header has been read and whose slices are being collected.
|
||||||
|
struct Pending {
|
||||||
|
ph: PictureHeader,
|
||||||
|
pce: Option<PictureCodingExtension>,
|
||||||
|
/// (start code, payload range).
|
||||||
|
slices: Vec<(u8, std::ops::Range<usize>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Meta {
|
||||||
|
pts: i64,
|
||||||
|
ptype: PictureType,
|
||||||
|
temporal_reference: u16,
|
||||||
|
pce: PictureCodingExtension,
|
||||||
|
field_pictures: bool,
|
||||||
|
info: Arc<SequenceInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A frame whose first field has been decoded.
|
||||||
|
struct Building {
|
||||||
|
frame: Frame,
|
||||||
|
parity: usize,
|
||||||
|
meta: Meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Anchor {
|
||||||
|
frame: Arc<Frame>,
|
||||||
|
meta: Meta,
|
||||||
|
shown: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stateful decoder. Feed access units (or any chunks holding whole pictures) in decode order;
|
||||||
|
/// pictures come out in display order.
|
||||||
|
pub struct Decoder {
|
||||||
|
seq: Option<Seq>,
|
||||||
|
/// A sequence header is waiting for its extensions (applied at the next non-extension unit).
|
||||||
|
seq_pending: Option<(SequenceHeader, Option<SequenceExtension>, Option<DisplayExtension>)>,
|
||||||
|
matrices: [[u8; 64]; 4],
|
||||||
|
closed_gop: bool,
|
||||||
|
/// Older and newer anchor (reference) frames.
|
||||||
|
fwd: Option<Anchor>,
|
||||||
|
bwd: Option<Anchor>,
|
||||||
|
building: Option<Building>,
|
||||||
|
pending: Option<Pending>,
|
||||||
|
out: Vec<Picture>,
|
||||||
|
errors: usize,
|
||||||
|
last_error: Option<&'static str>,
|
||||||
|
threads: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Decoder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
pub fn new() -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
seq: None,
|
||||||
|
seq_pending: None,
|
||||||
|
matrices: [headers::DEFAULT_INTRA, [16; 64], headers::DEFAULT_INTRA, [16; 64]],
|
||||||
|
closed_gop: false,
|
||||||
|
fwd: None,
|
||||||
|
bwd: None,
|
||||||
|
building: None,
|
||||||
|
pending: None,
|
||||||
|
out: Vec::new(),
|
||||||
|
errors: 0,
|
||||||
|
last_error: None,
|
||||||
|
threads: cfg!(feature = "threads"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode slices on the rayon pool (default with the `threads` feature).
|
||||||
|
pub fn set_threads(&mut self, on: bool) {
|
||||||
|
self.threads = on && cfg!(feature = "threads");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slices that failed to decode so far (corrupt data; the picture is still output).
|
||||||
|
pub fn errors(&self) -> usize {
|
||||||
|
self.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the most recent slice error was.
|
||||||
|
pub fn last_error(&self) -> Option<&'static str> {
|
||||||
|
self.last_error
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The active sequence.
|
||||||
|
pub fn sequence(&self) -> Option<&SequenceInfo> {
|
||||||
|
self.seq.as_ref().map(|s| &*s.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a chunk of the elementary stream holding whole pictures (an access unit) tagged
|
||||||
|
/// with `pts`. Returns the pictures that became displayable, in display order.
|
||||||
|
pub fn decode(&mut self, data: &[u8], pts: i64) -> Result<Vec<Picture>> {
|
||||||
|
let codes = start_codes(data);
|
||||||
|
let mut err = None;
|
||||||
|
for (k, &(pos, code)) in codes.iter().enumerate() {
|
||||||
|
let end = codes.get(k + 1).map_or(data.len(), |c| c.0);
|
||||||
|
let range = (pos + 4).min(end)..end;
|
||||||
|
let payload = &data[range.clone()];
|
||||||
|
if code != EXTENSION {
|
||||||
|
self.apply_sequence();
|
||||||
|
}
|
||||||
|
match code {
|
||||||
|
SEQUENCE_HEADER => {
|
||||||
|
self.finish_picture(data, pts);
|
||||||
|
match SequenceHeader::parse(payload) {
|
||||||
|
Ok(h) => self.seq_pending = Some((h, None, None)),
|
||||||
|
Err(e) => err = Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXTENSION => match Extension::parse(payload) {
|
||||||
|
Ok(Extension::Sequence(e)) => {
|
||||||
|
if let Some(s) = self.seq_pending.as_mut() {
|
||||||
|
s.1 = Some(e);
|
||||||
|
} else if let Some(s) = &self.seq {
|
||||||
|
self.seq_pending = Some((s.hdr.clone(), Some(e), s.display));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Extension::Display(d)) => {
|
||||||
|
if let Some(s) = self.seq_pending.as_mut() {
|
||||||
|
s.2 = Some(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Extension::QuantMatrix(q)) => {
|
||||||
|
if let Some(m) = q.intra {
|
||||||
|
self.matrices[0] = m;
|
||||||
|
self.matrices[2] = m;
|
||||||
|
}
|
||||||
|
if let Some(m) = q.non_intra {
|
||||||
|
self.matrices[1] = m;
|
||||||
|
self.matrices[3] = m;
|
||||||
|
}
|
||||||
|
if let Some(m) = q.chroma_intra {
|
||||||
|
self.matrices[2] = m;
|
||||||
|
}
|
||||||
|
if let Some(m) = q.chroma_non_intra {
|
||||||
|
self.matrices[3] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Extension::PictureCoding(p)) => {
|
||||||
|
if let Some(pd) = self.pending.as_mut() {
|
||||||
|
pd.pce = Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Extension::Other(_)) => {}
|
||||||
|
Err(e) => err = Some(e),
|
||||||
|
},
|
||||||
|
GROUP_START => {
|
||||||
|
self.finish_picture(data, pts);
|
||||||
|
if let Ok(g) = GopHeader::parse(payload) {
|
||||||
|
self.closed_gop = g.closed_gop || g.broken_link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PICTURE_START => {
|
||||||
|
self.finish_picture(data, pts);
|
||||||
|
match PictureHeader::parse(payload) {
|
||||||
|
Ok(ph) => self.pending = Some(Pending { ph, pce: None, slices: Vec::new() }),
|
||||||
|
Err(e) => err = Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SLICE_MIN..=SLICE_MAX => {
|
||||||
|
if let Some(pd) = self.pending.as_mut() {
|
||||||
|
pd.slices.push((code, range));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SEQUENCE_END => self.finish_picture(data, pts),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.apply_sequence();
|
||||||
|
self.finish_picture(data, pts);
|
||||||
|
let out = std::mem::take(&mut self.out);
|
||||||
|
match err {
|
||||||
|
Some(e) if out.is_empty() && self.seq.is_none() => Err(e),
|
||||||
|
_ => Ok(out),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output the frames held back for reordering (end of stream).
|
||||||
|
pub fn flush(&mut self) -> Vec<Picture> {
|
||||||
|
if let Some(b) = self.building.take() {
|
||||||
|
self.complete(b.frame, b.meta);
|
||||||
|
}
|
||||||
|
if let Some(a) = self.bwd.as_mut()
|
||||||
|
&& !a.shown
|
||||||
|
{
|
||||||
|
a.shown = true;
|
||||||
|
let p = to_picture(&a.frame, &a.meta);
|
||||||
|
self.out.push(p);
|
||||||
|
}
|
||||||
|
std::mem::take(&mut self.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forget all pictures (before decoding from a new random-access point). The sequence
|
||||||
|
/// parameters are kept: a random-access point without a sequence header still decodes.
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.fwd = None;
|
||||||
|
self.bwd = None;
|
||||||
|
self.building = None;
|
||||||
|
self.pending = None;
|
||||||
|
self.out.clear();
|
||||||
|
self.closed_gop = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_sequence(&mut self) {
|
||||||
|
let Some((h, e, d)) = self.seq_pending.take() else { return };
|
||||||
|
// a sequence header resets the quantiser matrices (§6.3.11)
|
||||||
|
let intra = h.intra_matrix.unwrap_or(headers::DEFAULT_INTRA);
|
||||||
|
let non_intra = h.non_intra_matrix.unwrap_or([16; 64]);
|
||||||
|
self.matrices = [intra, non_intra, intra, non_intra];
|
||||||
|
match Seq::new(h, e, d) {
|
||||||
|
Ok(s) => {
|
||||||
|
let changed = self.seq.as_ref().is_none_or(|o| o.mb_width != s.mb_width || o.mb_height != s.mb_height || o.info.chroma != s.info.chroma);
|
||||||
|
if changed {
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
self.seq = Some(s);
|
||||||
|
}
|
||||||
|
Err(_) => self.errors += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_picture(&mut self, data: &[u8], pts: i64) {
|
||||||
|
let Some(pd) = self.pending.take() else { return };
|
||||||
|
let Some(seq) = self.seq.clone() else {
|
||||||
|
self.errors += 1;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mpeg2 = seq.ext.is_some();
|
||||||
|
let pce = match (mpeg2, pd.pce) {
|
||||||
|
(true, Some(p)) => p,
|
||||||
|
(true, None) => {
|
||||||
|
self.errors += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(false, _) => PictureCodingExtension::mpeg1(&pd.ph),
|
||||||
|
};
|
||||||
|
if seq.info.chroma == ChromaFormat::Yuv444 {
|
||||||
|
self.errors += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ptype = pd.ph.coding_type;
|
||||||
|
let (shx, shy) = seq.shifts();
|
||||||
|
let (w, h) = (seq.mb_width * 16, seq.mb_height * 16);
|
||||||
|
let (cw, ch) = (w >> shx, h >> shy);
|
||||||
|
let structure = pce.picture_structure;
|
||||||
|
let parity = if structure == 2 { 1 } else { 0 };
|
||||||
|
// field pairing
|
||||||
|
let mut second = None;
|
||||||
|
if structure == 3 {
|
||||||
|
if let Some(b) = self.building.take() {
|
||||||
|
self.complete(b.frame, b.meta);
|
||||||
|
}
|
||||||
|
} else if let Some(b) = self.building.take() {
|
||||||
|
if b.parity != parity && b.frame.w == w && b.frame.h == h {
|
||||||
|
second = Some(b);
|
||||||
|
} else {
|
||||||
|
self.complete(b.frame, b.meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// B pictures need their forward reference unless the GOP is closed
|
||||||
|
if ptype == PictureType::B && second.is_none() && (self.bwd.is_none() || (self.fwd.is_none() && !self.closed_gop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let is_second = second.is_some();
|
||||||
|
let (mut frame, meta) = match second {
|
||||||
|
Some(b) => (b.frame, b.meta),
|
||||||
|
None => (
|
||||||
|
Frame::new(w, h, cw, ch),
|
||||||
|
Meta { pts, ptype, temporal_reference: pd.ph.temporal_reference, pce, field_pictures: structure != 3, info: seq.info.clone() },
|
||||||
|
),
|
||||||
|
};
|
||||||
|
// a P second field may predict from the first field of its own frame
|
||||||
|
let first_field = (is_second && ptype == PictureType::P).then(|| frame.clone());
|
||||||
|
let (fwd, bwd) = match ptype {
|
||||||
|
PictureType::P => (self.bwd.as_ref().map(|a| &*a.frame), None),
|
||||||
|
PictureType::B => (self.fwd.as_ref().map(|a| &*a.frame), self.bwd.as_ref().map(|a| &*a.frame)),
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
let p = PicParams {
|
||||||
|
mpeg2,
|
||||||
|
ptype,
|
||||||
|
pce,
|
||||||
|
full_pel: [pd.ph.full_pel_forward_vector, pd.ph.full_pel_backward_vector],
|
||||||
|
shx,
|
||||||
|
shy,
|
||||||
|
mb_width: seq.mb_width,
|
||||||
|
mb_rows: if structure == 3 { seq.mb_height } else { seq.mb_height / 2 },
|
||||||
|
matrices: self.matrices,
|
||||||
|
fwd,
|
||||||
|
bwd,
|
||||||
|
first_field: first_field.as_ref(),
|
||||||
|
parity,
|
||||||
|
vertical_size_extension: seq.info.height > 2800,
|
||||||
|
};
|
||||||
|
let errors = SliceErrors::default();
|
||||||
|
decode_picture(&p, data, &pd.slices, &mut frame, structure, self.threads, &errors);
|
||||||
|
self.errors += errors.count.load(Ordering::Relaxed);
|
||||||
|
if let Some(e) = errors.first.into_inner().unwrap_or(None) {
|
||||||
|
self.last_error = Some(e);
|
||||||
|
}
|
||||||
|
if structure != 3 && !is_second {
|
||||||
|
self.building = Some(Building { frame, parity, meta });
|
||||||
|
} else {
|
||||||
|
self.complete(frame, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(&mut self, frame: Frame, meta: Meta) {
|
||||||
|
if meta.ptype.is_anchor() {
|
||||||
|
if let Some(mut prev) = self.bwd.take() {
|
||||||
|
if !prev.shown {
|
||||||
|
prev.shown = true;
|
||||||
|
self.out.push(to_picture(&prev.frame, &prev.meta));
|
||||||
|
}
|
||||||
|
self.fwd = Some(prev);
|
||||||
|
}
|
||||||
|
self.bwd = Some(Anchor { frame: Arc::new(frame), meta, shown: false });
|
||||||
|
} else {
|
||||||
|
self.out.push(to_picture(&frame, &meta));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slice errors of one picture (slices may decode on several threads).
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SliceErrors {
|
||||||
|
count: AtomicUsize,
|
||||||
|
first: std::sync::Mutex<Option<&'static str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SliceErrors {
|
||||||
|
fn add(&self, what: &'static str) {
|
||||||
|
self.count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let mut f = self.first.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
f.get_or_insert(what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode the slices of one picture into `frame`.
|
||||||
|
fn decode_picture(p: &PicParams, data: &[u8], slices: &[(u8, std::ops::Range<usize>)], frame: &mut Frame, structure: u8, threads: bool, errors: &SliceErrors) {
|
||||||
|
let (w, cw) = (frame.w, frame.cw);
|
||||||
|
let field = (structure != 3).then_some(p.parity);
|
||||||
|
let lines = if structure == 3 { 16 } else { 32 };
|
||||||
|
let clines = lines >> p.shy;
|
||||||
|
let [y, cb, cr] = &mut frame.planes;
|
||||||
|
if !p.mpeg2 {
|
||||||
|
// MPEG-1 slices may span rows: decode in order into the whole picture
|
||||||
|
let mut t = Target { planes: [&mut y[..], &mut cb[..], &mut cr[..]], strides: [w, cw], line0: [0, 0], field };
|
||||||
|
for (code, r) in slices {
|
||||||
|
let payload = &data[r.clone()];
|
||||||
|
if let Err(e) = decode_slice(p, payload, *code as usize - 1, false, &mut t) {
|
||||||
|
errors.add(e.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut rows: Vec<Vec<&[u8]>> = vec![Vec::new(); p.mb_rows];
|
||||||
|
for (code, r) in slices {
|
||||||
|
let payload = &data[r.clone()];
|
||||||
|
let row = slice_row(p.vertical_size_extension, *code, payload);
|
||||||
|
match rows.get_mut(row) {
|
||||||
|
Some(v) => v.push(payload),
|
||||||
|
None => {
|
||||||
|
errors.add("slice row out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let run = |r: usize, y: &mut [u8], cb: &mut [u8], cr: &mut [u8]| {
|
||||||
|
let Some(list) = rows.get(r) else { return };
|
||||||
|
let mut t = Target { planes: [y, cb, cr], strides: [w, cw], line0: [r * lines, r * clines], field };
|
||||||
|
for payload in list {
|
||||||
|
if let Err(e) = decode_slice(p, payload, r, true, &mut t) {
|
||||||
|
errors.add(e.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#[cfg(feature = "threads")]
|
||||||
|
if threads && p.mb_rows >= 4 {
|
||||||
|
use rayon::prelude::*;
|
||||||
|
y.par_chunks_mut(lines * w)
|
||||||
|
.zip(cb.par_chunks_mut(clines * cw))
|
||||||
|
.zip(cr.par_chunks_mut(clines * cw))
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(r, ((y, cb), cr))| run(r, y, cb, cr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = threads;
|
||||||
|
for (r, ((y, cb), cr)) in y.chunks_mut(lines * w).zip(cb.chunks_mut(clines * cw)).zip(cr.chunks_mut(clines * cw)).enumerate() {
|
||||||
|
run(r, y, cb, cr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_picture(f: &Frame, meta: &Meta) -> Picture {
|
||||||
|
let info = &meta.info;
|
||||||
|
let (w, h) = (info.width as usize, info.height as usize);
|
||||||
|
let (cw, ch) = match info.chroma {
|
||||||
|
ChromaFormat::Yuv420 => (w.div_ceil(2), h.div_ceil(2)),
|
||||||
|
ChromaFormat::Yuv422 => (w.div_ceil(2), h),
|
||||||
|
ChromaFormat::Yuv444 => (w, h),
|
||||||
|
};
|
||||||
|
let crop = |src: &[u8], stride: usize, w: usize, h: usize| {
|
||||||
|
let mut v = Vec::with_capacity(w * h);
|
||||||
|
for r in 0..h {
|
||||||
|
v.extend_from_slice(&src[r * stride..r * stride + w]);
|
||||||
|
}
|
||||||
|
v
|
||||||
|
};
|
||||||
|
let progressive_frame = meta.pce.progressive_frame;
|
||||||
|
Picture {
|
||||||
|
width: w as u32,
|
||||||
|
height: h as u32,
|
||||||
|
chroma: info.chroma,
|
||||||
|
y: crop(&f.planes[0], f.w, w, h),
|
||||||
|
cb: crop(&f.planes[1], f.cw, cw, ch),
|
||||||
|
cr: crop(&f.planes[2], f.cw, cw, ch),
|
||||||
|
pts: meta.pts,
|
||||||
|
picture_type: meta.ptype,
|
||||||
|
temporal_reference: meta.temporal_reference,
|
||||||
|
progressive_frame,
|
||||||
|
// field pictures: the first coded field is displayed first
|
||||||
|
top_field_first: if meta.field_pictures { meta.pce.picture_structure == 1 } else { meta.pce.top_field_first },
|
||||||
|
repeat_first_field: meta.pce.repeat_first_field,
|
||||||
|
field_pictures: meta.field_pictures,
|
||||||
|
info: meta.info.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
//! Frame buffers and motion-compensated prediction (H.262 §7.6).
|
||||||
|
|
||||||
|
/// A decoded frame, padded to whole macroblocks.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Frame {
|
||||||
|
pub planes: [Vec<u8>; 3],
|
||||||
|
/// Luma width / height (multiples of 16).
|
||||||
|
pub w: usize,
|
||||||
|
pub h: usize,
|
||||||
|
/// Chroma width / height.
|
||||||
|
pub cw: usize,
|
||||||
|
pub ch: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn new(w: usize, h: usize, cw: usize, ch: usize) -> Frame {
|
||||||
|
Frame { planes: [vec![16; w * h], vec![128; cw * ch], vec![128; cw * ch]], w, h, cw, ch }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plane `c` (0 Y, 1 Cb, 2 Cr) as a reference: the whole frame, or one field (0 top, 1 bottom).
|
||||||
|
#[inline]
|
||||||
|
pub fn plane(&self, c: usize, field: Option<usize>) -> RefPlane<'_> {
|
||||||
|
let (w, h) = if c == 0 { (self.w, self.h) } else { (self.cw, self.ch) };
|
||||||
|
match field {
|
||||||
|
None => RefPlane { data: &self.planes[c], off: 0, stride: w, w, h },
|
||||||
|
Some(p) => RefPlane { data: &self.planes[c], off: p * w, stride: 2 * w, w, h: h / 2 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plane (or a field of one) to predict from.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct RefPlane<'a> {
|
||||||
|
pub data: &'a [u8],
|
||||||
|
pub off: usize,
|
||||||
|
pub stride: usize,
|
||||||
|
pub w: usize,
|
||||||
|
pub h: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Form the `bw`×`bh` prediction at integer position (`x`, `y`) with half-sample flags into
|
||||||
|
/// `dst` (row `r` at `dst[doff + r·dstride..]`), or average it with what is there (`avg`).
|
||||||
|
/// Positions outside the reference are clamped to its edges (not allowed in conforming streams;
|
||||||
|
/// this keeps corrupt ones safe).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn predict(dst: &mut [u8], doff: usize, dstride: usize, src: RefPlane, x: i32, y: i32, hx: bool, hy: bool, bw: usize, bh: usize, avg: bool) {
|
||||||
|
let (ex, ey) = (hx as i32, hy as i32);
|
||||||
|
let inside = x >= 0 && y >= 0 && (x + bw as i32 + ex) as usize <= src.w && (y + bh as i32 + ey) as usize <= src.h;
|
||||||
|
let mut tmp = [0u8; 16];
|
||||||
|
if inside {
|
||||||
|
let s = src.stride;
|
||||||
|
let base = src.off + y as usize * s + x as usize;
|
||||||
|
for r in 0..bh {
|
||||||
|
let p = base + r * s;
|
||||||
|
let a = &src.data[p..p + bw + 1 - (!hx) as usize];
|
||||||
|
let row = &mut tmp[..bw];
|
||||||
|
match (hx, hy) {
|
||||||
|
(false, false) => row.copy_from_slice(&a[..bw]),
|
||||||
|
(true, false) => {
|
||||||
|
for i in 0..bw {
|
||||||
|
row[i] = ((a[i] as u16 + a[i + 1] as u16 + 1) >> 1) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
let b = &src.data[p + s..p + s + bw];
|
||||||
|
for i in 0..bw {
|
||||||
|
row[i] = ((a[i] as u16 + b[i] as u16 + 1) >> 1) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, true) => {
|
||||||
|
let b = &src.data[p + s..p + s + bw + 1];
|
||||||
|
for i in 0..bw {
|
||||||
|
row[i] = ((a[i] as u16 + a[i + 1] as u16 + b[i] as u16 + b[i + 1] as u16 + 2) >> 2) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store(dst, doff + r * dstride, row, avg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let get = |xx: i32, yy: i32| -> u16 {
|
||||||
|
let xx = xx.clamp(0, src.w as i32 - 1) as usize;
|
||||||
|
let yy = yy.clamp(0, src.h as i32 - 1) as usize;
|
||||||
|
src.data[src.off + yy * src.stride + xx] as u16
|
||||||
|
};
|
||||||
|
for r in 0..bh {
|
||||||
|
let yy = y + r as i32;
|
||||||
|
let row = &mut tmp[..bw];
|
||||||
|
for (i, v) in row.iter_mut().enumerate() {
|
||||||
|
let xx = x + i as i32;
|
||||||
|
*v = match (hx, hy) {
|
||||||
|
(false, false) => get(xx, yy),
|
||||||
|
(true, false) => (get(xx, yy) + get(xx + 1, yy) + 1) >> 1,
|
||||||
|
(false, true) => (get(xx, yy) + get(xx, yy + 1) + 1) >> 1,
|
||||||
|
(true, true) => (get(xx, yy) + get(xx + 1, yy) + get(xx, yy + 1) + get(xx + 1, yy + 1) + 2) >> 2,
|
||||||
|
} as u8;
|
||||||
|
}
|
||||||
|
store(dst, doff + r * dstride, row, avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn store(dst: &mut [u8], at: usize, row: &[u8], avg: bool) {
|
||||||
|
let d = &mut dst[at..at + row.len()];
|
||||||
|
if avg {
|
||||||
|
for (o, &p) in d.iter_mut().zip(row) {
|
||||||
|
*o = ((*o as u16 + p as u16 + 1) >> 1) as u8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.copy_from_slice(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn frame() -> Frame {
|
||||||
|
let mut f = Frame::new(32, 32, 16, 16);
|
||||||
|
for (i, v) in f.planes[0].iter_mut().enumerate() {
|
||||||
|
*v = (i % 32 * 3 + i / 32 * 5) as u8;
|
||||||
|
}
|
||||||
|
f
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn half_sample_interpolation_rounds_up() {
|
||||||
|
let f = frame();
|
||||||
|
let p = f.plane(0, None);
|
||||||
|
let mut d = [0u8; 4];
|
||||||
|
predict(&mut d, 0, 2, p, 1, 1, true, true, 2, 2, false);
|
||||||
|
let s = |x: usize, y: usize| f.planes[0][y * 32 + x] as u16;
|
||||||
|
assert_eq!(d[0] as u16, (s(1, 1) + s(2, 1) + s(1, 2) + s(2, 2) + 2) >> 2);
|
||||||
|
predict(&mut d, 0, 2, p, 1, 1, true, false, 2, 2, false);
|
||||||
|
assert_eq!(d[3] as u16, (s(2, 2) + s(3, 2) + 1) >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fields_and_clamped_edges() {
|
||||||
|
let f = frame();
|
||||||
|
let mut d = [0u8; 4];
|
||||||
|
// bottom field row 0 = frame row 1
|
||||||
|
predict(&mut d, 0, 2, f.plane(0, Some(1)), 0, 0, false, false, 2, 2, false);
|
||||||
|
assert_eq!(d, [f.planes[0][32], f.planes[0][33], f.planes[0][96], f.planes[0][97]]);
|
||||||
|
// outside: clamped to the corner
|
||||||
|
predict(&mut d, 0, 2, f.plane(0, None), -10, -10, false, false, 2, 2, false);
|
||||||
|
assert_eq!(d, [f.planes[0][0]; 4]);
|
||||||
|
// averaging
|
||||||
|
let mut d = [10u8; 4];
|
||||||
|
predict(&mut d, 0, 2, f.plane(0, None), -10, -10, false, false, 2, 2, true);
|
||||||
|
assert_eq!(d[0], ((10 + f.planes[0][0] as u16 + 1) >> 1) as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,615 @@
|
|||||||
|
//! Slice, macroblock and block decoding (H.262 §6.2.4-§6.2.6, §7.2-§7.6; ISO/IEC 11172-2
|
||||||
|
//! §2.4.2.7-§2.4.4).
|
||||||
|
|
||||||
|
use crate::bits::Bits;
|
||||||
|
use crate::headers::{PictureCodingExtension, PictureType, SCAN};
|
||||||
|
use crate::idct::idct;
|
||||||
|
use crate::mc::{Frame, predict};
|
||||||
|
use crate::vlc::{DCT_EOB, DCT_ESCAPE, MB_BWD, MB_FWD, MB_INTRA, MB_PATTERN, MB_QUANT, MBA_ESCAPE, MBA_STUFFING, tables};
|
||||||
|
|
||||||
|
/// Non-linear quantiser_scale (Table 7-6), indexed by quantiser_scale_code.
|
||||||
|
const NON_LINEAR_Q: [u8; 32] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 96, 104, 112];
|
||||||
|
|
||||||
|
/// Motion types (frame_motion_type / field_motion_type values).
|
||||||
|
const MT_FIELD: u8 = 1;
|
||||||
|
const MT_FRAME: u8 = 2; // frame pictures; 16x8 MC in field pictures
|
||||||
|
const MT_16X8: u8 = 2;
|
||||||
|
const MT_DUAL: u8 = 3;
|
||||||
|
|
||||||
|
/// Everything slices of one picture share (read-only while slices decode in parallel).
|
||||||
|
pub(crate) struct PicParams<'a> {
|
||||||
|
pub mpeg2: bool,
|
||||||
|
pub ptype: PictureType,
|
||||||
|
pub pce: PictureCodingExtension,
|
||||||
|
pub full_pel: [bool; 2],
|
||||||
|
/// Chroma subsampling shifts (4:2:0: 1, 1; 4:2:2: 1, 0).
|
||||||
|
pub shx: u32,
|
||||||
|
pub shy: u32,
|
||||||
|
pub mb_width: usize,
|
||||||
|
/// Macroblock rows of this picture (field rows for field pictures).
|
||||||
|
pub mb_rows: usize,
|
||||||
|
/// Intra luma, non-intra luma, intra chroma, non-intra chroma (raster order).
|
||||||
|
pub matrices: [[u8; 64]; 4],
|
||||||
|
/// Forward reference (P: the latest anchor; B: the older anchor) and backward reference.
|
||||||
|
pub fwd: Option<&'a Frame>,
|
||||||
|
pub bwd: Option<&'a Frame>,
|
||||||
|
/// The first field of this frame (when decoding its second field).
|
||||||
|
pub first_field: Option<&'a Frame>,
|
||||||
|
/// Field pictures: parity of this field (0 top, 1 bottom).
|
||||||
|
pub parity: usize,
|
||||||
|
pub vertical_size_extension: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PicParams<'_> {
|
||||||
|
fn frame_picture(&self) -> bool {
|
||||||
|
self.pce.picture_structure == 3
|
||||||
|
}
|
||||||
|
fn chroma_blocks(&self) -> usize {
|
||||||
|
if self.shy == 1 { 2 } else { 4 }
|
||||||
|
}
|
||||||
|
/// Chroma macroblock size.
|
||||||
|
fn cmb(&self) -> (usize, usize) {
|
||||||
|
(16 >> self.shx, 16 >> self.shy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where a row (or, for MPEG-1, the whole picture) of macroblocks is written.
|
||||||
|
pub(crate) struct Target<'a> {
|
||||||
|
pub planes: [&'a mut [u8]; 3],
|
||||||
|
pub strides: [usize; 2],
|
||||||
|
/// First luma / chroma frame line held by `planes`.
|
||||||
|
pub line0: [usize; 2],
|
||||||
|
/// Field pictures: parity written.
|
||||||
|
pub field: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target<'_> {
|
||||||
|
fn put(&mut self, mbx: usize, mby: usize, mb: &MbBuf, cmb: (usize, usize)) {
|
||||||
|
for (c, plane) in self.planes.iter_mut().enumerate() {
|
||||||
|
let k = (c > 0) as usize;
|
||||||
|
let (bw, bh) = if c == 0 { (16, 16) } else { cmb };
|
||||||
|
let stride = self.strides[k];
|
||||||
|
let src = if c == 0 { &mb.y[..] } else { &mb.c[c - 1][..] };
|
||||||
|
for l in 0..bh {
|
||||||
|
let line = match self.field {
|
||||||
|
None => bh * mby + l,
|
||||||
|
Some(p) => 2 * (bh * mby + l) + p,
|
||||||
|
};
|
||||||
|
let Some(local) = line.checked_sub(self.line0[k]) else { continue };
|
||||||
|
let at = local * stride + bw * mbx;
|
||||||
|
if at + bw <= plane.len() {
|
||||||
|
plane[at..at + bw].copy_from_slice(&src[l * bw..l * bw + bw]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One macroblock's samples (luma 16×16; chroma rows of `16 >> shx` samples).
|
||||||
|
pub(crate) struct MbBuf {
|
||||||
|
y: [u8; 256],
|
||||||
|
c: [[u8; 256]; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct Motion {
|
||||||
|
flags: i16,
|
||||||
|
mtype: u8,
|
||||||
|
/// vectors[r][s][t] in half-sample units (vertical in field units for field prediction).
|
||||||
|
mv: [[[i32; 2]; 2]; 2],
|
||||||
|
select: [[usize; 2]; 2],
|
||||||
|
dmv: [i32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SliceState {
|
||||||
|
qscale: i32,
|
||||||
|
dc_pred: [i32; 3],
|
||||||
|
pmv: [[[i32; 2]; 2]; 2],
|
||||||
|
last: Motion,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dc_reset(p: &PicParams) -> i32 {
|
||||||
|
1 << (7 + p.pce.intra_dc_precision)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SliceError(pub &'static str);
|
||||||
|
|
||||||
|
type R<T> = std::result::Result<T, SliceError>;
|
||||||
|
|
||||||
|
/// Decode one slice (`data`: the bytes after its start code) into `t`. `row`: the slice's
|
||||||
|
/// macroblock row; `single_row`: MPEG-2 slices may not leave their row.
|
||||||
|
pub(crate) fn decode_slice(p: &PicParams, data: &[u8], row: usize, single_row: bool, t: &mut Target) -> R<()> {
|
||||||
|
let tb = tables();
|
||||||
|
let mut b = Bits::new(data);
|
||||||
|
if p.vertical_size_extension {
|
||||||
|
b.skip(3); // already applied to `row` by the caller
|
||||||
|
}
|
||||||
|
let qcode = b.read(5) as usize;
|
||||||
|
if p.mpeg2 && b.peek(1) == 1 {
|
||||||
|
b.skip(9); // intra_slice_flag, intra_slice, reserved_bits
|
||||||
|
}
|
||||||
|
while b.bit() {
|
||||||
|
b.skip(8); // extra_information_slice
|
||||||
|
}
|
||||||
|
let mut st = SliceState { qscale: qscale(p, qcode), dc_pred: [dc_reset(p); 3], pmv: [[[0; 2]; 2]; 2], last: Motion::default() };
|
||||||
|
let total = p.mb_width * p.mb_rows;
|
||||||
|
let row_end = if single_row { (row + 1) * p.mb_width } else { total };
|
||||||
|
let mut addr: isize = (row * p.mb_width) as isize - 1;
|
||||||
|
let mut first = true;
|
||||||
|
let mut buf = MbBuf { y: [0; 256], c: [[0; 256]; 2] };
|
||||||
|
let mut blk = [0i32; 64];
|
||||||
|
loop {
|
||||||
|
// macroblock_address_increment
|
||||||
|
let mut inc = 0isize;
|
||||||
|
loop {
|
||||||
|
match tb.mba.decode(&mut b) {
|
||||||
|
Some(MBA_ESCAPE) => inc += 33,
|
||||||
|
Some(MBA_STUFFING) => {}
|
||||||
|
Some(n) => {
|
||||||
|
inc += n as isize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
None => return Err(SliceError("bad macroblock_address_increment")),
|
||||||
|
}
|
||||||
|
if b.overrun() {
|
||||||
|
return Err(SliceError("truncated slice"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !first && inc > 1 {
|
||||||
|
// skipped macroblocks
|
||||||
|
for _ in 1..inc {
|
||||||
|
addr += 1;
|
||||||
|
if addr as usize >= row_end {
|
||||||
|
return Err(SliceError("macroblock address past the row"));
|
||||||
|
}
|
||||||
|
skipped_mb(p, &mut st, addr as usize, &mut buf, t)?;
|
||||||
|
}
|
||||||
|
addr += 1;
|
||||||
|
} else {
|
||||||
|
addr += inc;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
if addr < 0 || addr as usize >= row_end {
|
||||||
|
return Err(SliceError("macroblock address out of range"));
|
||||||
|
}
|
||||||
|
let a = addr as usize;
|
||||||
|
macroblock(p, &mut st, &mut b, a, &mut buf, &mut blk, t)?;
|
||||||
|
if b.overrun() {
|
||||||
|
return Err(SliceError("truncated macroblock"));
|
||||||
|
}
|
||||||
|
// next_start_code: 23 zero bits end the slice
|
||||||
|
if b.bits_left() <= 0 || b.peek(23) == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qscale(p: &PicParams, code: usize) -> i32 {
|
||||||
|
if !p.mpeg2 {
|
||||||
|
code as i32
|
||||||
|
} else if p.pce.q_scale_type {
|
||||||
|
NON_LINEAR_Q[code & 31] as i32
|
||||||
|
} else {
|
||||||
|
2 * code as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A skipped macroblock (§7.6.6): P pictures predict from the same position with a zero vector;
|
||||||
|
/// B pictures repeat the previous macroblock's prediction.
|
||||||
|
fn skipped_mb(p: &PicParams, st: &mut SliceState, addr: usize, buf: &mut MbBuf, t: &mut Target) -> R<()> {
|
||||||
|
st.dc_pred = [dc_reset(p); 3];
|
||||||
|
let m = match p.ptype {
|
||||||
|
PictureType::P => {
|
||||||
|
st.pmv = [[[0; 2]; 2]; 2];
|
||||||
|
let mut m = Motion { flags: MB_FWD, mtype: if p.frame_picture() { MT_FRAME } else { MT_FIELD }, ..Default::default() };
|
||||||
|
m.select[0][0] = p.parity;
|
||||||
|
m
|
||||||
|
}
|
||||||
|
PictureType::B => {
|
||||||
|
let last = st.last;
|
||||||
|
if last.flags & MB_INTRA != 0 {
|
||||||
|
return Err(SliceError("skipped macroblock after an intra macroblock"));
|
||||||
|
}
|
||||||
|
// the previous macroblock's direction, one vector per direction (PMV[0][s])
|
||||||
|
let mut m = Motion { flags: last.flags & (MB_FWD | MB_BWD), mtype: if p.frame_picture() { MT_FRAME } else { MT_FIELD }, ..Default::default() };
|
||||||
|
for s in 0..2 {
|
||||||
|
m.mv[0][s] = st.pmv[0][s];
|
||||||
|
m.select[0][s] = last.select[0][s];
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
_ => return Err(SliceError("skipped macroblock in an intra picture")),
|
||||||
|
};
|
||||||
|
let (mbx, mby) = (addr % p.mb_width, addr / p.mb_width);
|
||||||
|
predict_mb(p, &m, mbx, mby, buf);
|
||||||
|
t.put(mbx, mby, buf, p.cmb());
|
||||||
|
if p.ptype == PictureType::B {
|
||||||
|
st.last = Motion { flags: m.flags, ..st.last };
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn macroblock(p: &PicParams, st: &mut SliceState, b: &mut Bits, addr: usize, buf: &mut MbBuf, blk: &mut [i32; 64], t: &mut Target) -> R<()> {
|
||||||
|
let tb = tables();
|
||||||
|
let ti = match p.ptype {
|
||||||
|
PictureType::I => 0,
|
||||||
|
PictureType::P => 1,
|
||||||
|
PictureType::B => 2,
|
||||||
|
PictureType::D => 3,
|
||||||
|
};
|
||||||
|
let flags = tb.mbtype[ti].decode(b).ok_or(SliceError("bad macroblock_type"))?;
|
||||||
|
let intra = flags & MB_INTRA != 0;
|
||||||
|
let frame_pic = p.frame_picture();
|
||||||
|
let mut m = Motion { flags, ..Default::default() };
|
||||||
|
if flags & (MB_FWD | MB_BWD) != 0 {
|
||||||
|
m.mtype = if frame_pic && (p.pce.frame_pred_frame_dct || !p.mpeg2) { MT_FRAME } else { b.read(2) as u8 };
|
||||||
|
if m.mtype == 0 {
|
||||||
|
return Err(SliceError("reserved motion type"));
|
||||||
|
}
|
||||||
|
} else if intra && p.pce.concealment_motion_vectors {
|
||||||
|
m.mtype = if frame_pic { MT_FRAME } else { MT_FIELD };
|
||||||
|
}
|
||||||
|
let dct_type = p.mpeg2 && frame_pic && !p.pce.frame_pred_frame_dct && (intra || flags & MB_PATTERN != 0) && b.bit();
|
||||||
|
if flags & MB_QUANT != 0 {
|
||||||
|
st.qscale = qscale(p, b.read(5) as usize);
|
||||||
|
}
|
||||||
|
// motion vectors
|
||||||
|
let (count, field_fmt, dual) = match (frame_pic, m.mtype) {
|
||||||
|
(true, MT_FIELD) => (2, true, false),
|
||||||
|
(true, MT_DUAL) | (false, MT_DUAL) => (1, true, true),
|
||||||
|
(true, _) => (1, false, false),
|
||||||
|
(false, MT_16X8) => (2, true, false),
|
||||||
|
(false, _) => (1, true, false),
|
||||||
|
};
|
||||||
|
let concealment = intra && p.pce.concealment_motion_vectors;
|
||||||
|
for s in 0..2 {
|
||||||
|
let present = if s == 0 { flags & MB_FWD != 0 || concealment } else { flags & MB_BWD != 0 };
|
||||||
|
if !present {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for r in 0..count {
|
||||||
|
if count == 2 || (field_fmt && !dual) {
|
||||||
|
m.select[r][s] = b.read(1) as usize;
|
||||||
|
}
|
||||||
|
for c in 0..2 {
|
||||||
|
let fc = p.pce.f_code[s][c] as i32;
|
||||||
|
let code = tb.motion.decode(b).ok_or(SliceError("bad motion_code"))? as i32;
|
||||||
|
let r_size = (fc - 1).max(0);
|
||||||
|
let residual = if fc != 1 && code != 0 { b.read(r_size as u32) as i32 } else { 0 };
|
||||||
|
if dual {
|
||||||
|
m.dmv[c] = tb.dmv.decode(b).ok_or(SliceError("bad dmvector"))? as i32;
|
||||||
|
}
|
||||||
|
let f = 1 << r_size;
|
||||||
|
let delta = if f == 1 || code == 0 { code } else { (((code.abs() - 1) * f) + residual + 1) * code.signum() };
|
||||||
|
let halve = p.mpeg2 && field_fmt && c == 1 && frame_pic;
|
||||||
|
let pred = if halve { st.pmv[r][s][c] >> 1 } else { st.pmv[r][s][c] };
|
||||||
|
let (low, high, range) = (-16 * f, 16 * f - 1, 32 * f);
|
||||||
|
let mut v = pred + delta;
|
||||||
|
if v < low {
|
||||||
|
v += range;
|
||||||
|
} else if v > high {
|
||||||
|
v -= range;
|
||||||
|
}
|
||||||
|
st.pmv[r][s][c] = if halve { v * 2 } else { v };
|
||||||
|
m.mv[r][s][c] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count == 1 {
|
||||||
|
st.pmv[1][s] = st.pmv[0][s];
|
||||||
|
}
|
||||||
|
if !p.mpeg2 && p.full_pel[s] {
|
||||||
|
for c in 0..2 {
|
||||||
|
m.mv[0][s][c] *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if concealment {
|
||||||
|
b.skip(1); // marker_bit
|
||||||
|
}
|
||||||
|
// coded_block_pattern
|
||||||
|
let nblocks = 4 + p.chroma_blocks();
|
||||||
|
let cbp: u32 = if flags & MB_PATTERN != 0 {
|
||||||
|
let v = tb.cbp.decode(b).ok_or(SliceError("bad coded_block_pattern"))? as u32;
|
||||||
|
if nblocks == 8 { (v << 2) | b.read(2) } else { v }
|
||||||
|
} else if intra {
|
||||||
|
(1 << nblocks) - 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let (mbx, mby) = (addr % p.mb_width, addr / p.mb_width);
|
||||||
|
if intra {
|
||||||
|
if !concealment {
|
||||||
|
st.pmv = [[[0; 2]; 2]; 2];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
st.dc_pred = [dc_reset(p); 3];
|
||||||
|
if p.ptype == PictureType::P && flags & MB_FWD == 0 {
|
||||||
|
// "No MC": zero vector from the same position (same-parity field in field pictures)
|
||||||
|
st.pmv = [[[0; 2]; 2]; 2];
|
||||||
|
m.flags |= MB_FWD;
|
||||||
|
m.mtype = if frame_pic { MT_FRAME } else { MT_FIELD };
|
||||||
|
m.select[0][0] = p.parity;
|
||||||
|
}
|
||||||
|
predict_mb(p, &m, mbx, mby, buf);
|
||||||
|
}
|
||||||
|
// blocks
|
||||||
|
let (cbw, cbh) = p.cmb();
|
||||||
|
for i in 0..nblocks {
|
||||||
|
if cbp & (1 << (nblocks - 1 - i)) == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let cc = if i < 4 { 0 } else { 1 + (i & 1) };
|
||||||
|
let dc_only = block(p, st, b, cc, intra, blk)?;
|
||||||
|
idct(blk, dc_only);
|
||||||
|
// placement
|
||||||
|
let (dst, stride, x0, y0, step) = if i < 4 {
|
||||||
|
let x0 = (i & 1) * 8;
|
||||||
|
let (y0, step) = if dct_type { (i >> 1, 2) } else { ((i >> 1) * 8, 1) };
|
||||||
|
(&mut buf.y[..], 16, x0, y0, step)
|
||||||
|
} else {
|
||||||
|
let k = (i - 4) >> 1; // 0 for 4:2:0; 0/1 (top/bottom) for 4:2:2
|
||||||
|
let (y0, step) = if cbh == 16 && dct_type { (k, 2) } else { (k * 8, 1) };
|
||||||
|
(&mut buf.c[cc - 1][..], cbw, 0, y0, step)
|
||||||
|
};
|
||||||
|
for r in 0..8 {
|
||||||
|
let at = (y0 + r * step) * stride + x0;
|
||||||
|
let row = &mut dst[at..at + 8];
|
||||||
|
let src = &blk[r * 8..r * 8 + 8];
|
||||||
|
if intra {
|
||||||
|
for (o, &v) in row.iter_mut().zip(src) {
|
||||||
|
*o = v.clamp(0, 255) as u8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (o, &v) in row.iter_mut().zip(src) {
|
||||||
|
*o = (*o as i32 + v).clamp(0, 255) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ptype == PictureType::D {
|
||||||
|
b.skip(1); // end_of_macroblock
|
||||||
|
}
|
||||||
|
t.put(mbx, mby, buf, (cbw, cbh));
|
||||||
|
st.last = m;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode and dequantise one block into `blk` (raster order). Returns whether only the DC
|
||||||
|
/// coefficient is non-zero.
|
||||||
|
fn block(p: &PicParams, st: &mut SliceState, b: &mut Bits, cc: usize, intra: bool, blk: &mut [i32; 64]) -> R<bool> {
|
||||||
|
let tb = tables();
|
||||||
|
blk.fill(0);
|
||||||
|
let scan = &SCAN[p.pce.alternate_scan as usize];
|
||||||
|
let w = &p.matrices[(cc > 0 && p.shy == 0) as usize * 2 + (!intra) as usize];
|
||||||
|
let qs = st.qscale;
|
||||||
|
let mut sum: i32 = 0;
|
||||||
|
let mut i: usize;
|
||||||
|
let mut ac = false;
|
||||||
|
if intra {
|
||||||
|
let size = if cc == 0 { tb.dc_luma.decode(b) } else { tb.dc_chroma.decode(b) }.ok_or(SliceError("bad dct_dc_size"))? as u32;
|
||||||
|
let diff = if size == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let v = b.read(size) as i32;
|
||||||
|
if v & (1 << (size - 1)) == 0 { v - (1 << size) + 1 } else { v }
|
||||||
|
};
|
||||||
|
st.dc_pred[cc] += diff;
|
||||||
|
let dc = if p.mpeg2 { st.dc_pred[cc] * (8 >> p.pce.intra_dc_precision) } else { st.dc_pred[cc] * 8 };
|
||||||
|
blk[0] = dc;
|
||||||
|
sum = dc;
|
||||||
|
if p.ptype == PictureType::D {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
} else {
|
||||||
|
i = usize::MAX; // the first coefficient lands at run
|
||||||
|
// "1s": run 0, level ±1 as the first coefficient of a non-intra block
|
||||||
|
if b.peek(1) == 1 {
|
||||||
|
b.skip(1);
|
||||||
|
let level = if b.bit() { -1 } else { 1 };
|
||||||
|
i = 0;
|
||||||
|
let v = dequant(p, level, qs, w[scan[0] as usize] as i32, false);
|
||||||
|
blk[scan[0] as usize] = v;
|
||||||
|
sum += v;
|
||||||
|
ac = scan[0] != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let table = if intra && p.pce.intra_vlc_format { &tb.dct_one } else { &tb.dct_zero };
|
||||||
|
loop {
|
||||||
|
let code = table.decode(b).ok_or(SliceError("bad DCT coefficient code"))?;
|
||||||
|
if code == DCT_EOB {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (run, level) = if code == DCT_ESCAPE {
|
||||||
|
let run = b.read(6) as usize;
|
||||||
|
let level = if p.mpeg2 {
|
||||||
|
b.read_signed(12)
|
||||||
|
} else {
|
||||||
|
match b.read(8) as i32 {
|
||||||
|
0 => b.read(8) as i32,
|
||||||
|
128 => b.read(8) as i32 - 256,
|
||||||
|
v => (v << 24) >> 24,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if level == 0 {
|
||||||
|
return Err(SliceError("zero escape level"));
|
||||||
|
}
|
||||||
|
(run, level)
|
||||||
|
} else {
|
||||||
|
let run = (code & 63) as usize;
|
||||||
|
let level = (code >> 6) as i32;
|
||||||
|
(run, if b.bit() { -level } else { level })
|
||||||
|
};
|
||||||
|
i = i.wrapping_add(run + 1);
|
||||||
|
if i > 63 {
|
||||||
|
return Err(SliceError("coefficient index past 63"));
|
||||||
|
}
|
||||||
|
let pos = scan[i] as usize;
|
||||||
|
let v = dequant(p, level, qs, w[pos] as i32, intra);
|
||||||
|
blk[pos] = v;
|
||||||
|
sum += v;
|
||||||
|
ac = true;
|
||||||
|
if b.overrun() {
|
||||||
|
return Err(SliceError("truncated block"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.mpeg2 && sum & 1 == 0 {
|
||||||
|
// mismatch control (§7.4.4)
|
||||||
|
blk[63] ^= 1;
|
||||||
|
ac = true;
|
||||||
|
}
|
||||||
|
Ok(!ac && blk[1..].iter().all(|&v| v == 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn dequant(p: &PicParams, level: i32, qs: i32, w: i32, intra: bool) -> i32 {
|
||||||
|
if p.mpeg2 {
|
||||||
|
let k = if intra { 0 } else { level.signum() };
|
||||||
|
((2 * level + k) * w * qs / 32).clamp(-2048, 2047)
|
||||||
|
} else {
|
||||||
|
let mut v = if intra { 2 * level * qs * w / 16 } else { (2 * level + level.signum()) * qs * w / 16 };
|
||||||
|
if v & 1 == 0 {
|
||||||
|
v -= v.signum();
|
||||||
|
}
|
||||||
|
v.clamp(-2048, 2047)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reference field (or frame) for direction `s` and field `sel` (field prediction).
|
||||||
|
fn reference<'a>(p: &PicParams<'a>, s: usize, sel: usize) -> Option<&'a Frame> {
|
||||||
|
if s == 1 {
|
||||||
|
return p.bwd.or(p.fwd);
|
||||||
|
}
|
||||||
|
if p.ptype == PictureType::P && !p.frame_picture() && sel != p.parity && p.first_field.is_some() {
|
||||||
|
return p.first_field;
|
||||||
|
}
|
||||||
|
p.fwd.or(p.bwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Form the prediction of a non-intra macroblock in `buf` (§7.6).
|
||||||
|
fn predict_mb(p: &PicParams, m: &Motion, mbx: usize, mby: usize, buf: &mut MbBuf) {
|
||||||
|
let (cbw, cbh) = p.cmb();
|
||||||
|
let frame_pic = p.frame_picture();
|
||||||
|
let mut avg = false;
|
||||||
|
for s in 0..2 {
|
||||||
|
let dir = if s == 0 { MB_FWD } else { MB_BWD };
|
||||||
|
if m.flags & dir == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if frame_pic {
|
||||||
|
match m.mtype {
|
||||||
|
MT_FIELD => {
|
||||||
|
for r in 0..2 {
|
||||||
|
let Some(f) = reference(p, s, m.select[r][s]) else { return gray(buf) };
|
||||||
|
pred_block(p, buf, f, Some(m.select[r][s]), m.mv[r][s], mbx, mby, 8, r, 2, (cbw, cbh), avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MT_DUAL => {
|
||||||
|
let Some(f) = reference(p, s, 0) else { return gray(buf) };
|
||||||
|
for par in 0..2 {
|
||||||
|
let opp = 1 - par;
|
||||||
|
let (mm, e) = if par == 0 { (if p.pce.top_field_first { 1 } else { 3 }, -1) } else { (if p.pce.top_field_first { 3 } else { 1 }, 1) };
|
||||||
|
let d = dual_vector(m.mv[0][s], m.dmv, mm, e);
|
||||||
|
pred_block(p, buf, f, Some(par), m.mv[0][s], mbx, mby, 8, par, 2, (cbw, cbh), avg);
|
||||||
|
pred_block(p, buf, f, Some(opp), d, mbx, mby, 8, par, 2, (cbw, cbh), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let Some(f) = reference(p, s, 0) else { return gray(buf) };
|
||||||
|
pred_block(p, buf, f, None, m.mv[0][s], mbx, mby, 16, 0, 1, (cbw, cbh), avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match m.mtype {
|
||||||
|
MT_16X8 => {
|
||||||
|
for r in 0..2 {
|
||||||
|
let Some(f) = reference(p, s, m.select[r][s]) else { return gray(buf) };
|
||||||
|
pred_half(p, buf, f, m.select[r][s], m.mv[r][s], mbx, mby, r, (cbw, cbh), avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MT_DUAL => {
|
||||||
|
let same = p.parity;
|
||||||
|
let opp = 1 - same;
|
||||||
|
let Some(fs) = reference(p, s, same) else { return gray(buf) };
|
||||||
|
let Some(fo) = reference(p, s, opp) else { return gray(buf) };
|
||||||
|
let e = if same == 1 { 1 } else { -1 };
|
||||||
|
let d = dual_vector(m.mv[0][s], m.dmv, 1, e);
|
||||||
|
pred_block(p, buf, fs, Some(same), m.mv[0][s], mbx, mby, 16, 0, 1, (cbw, cbh), avg);
|
||||||
|
pred_block(p, buf, fo, Some(opp), d, mbx, mby, 16, 0, 1, (cbw, cbh), true);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let Some(f) = reference(p, s, m.select[0][s]) else { return gray(buf) };
|
||||||
|
pred_block(p, buf, f, Some(m.select[0][s]), m.mv[0][s], mbx, mby, 16, 0, 1, (cbw, cbh), avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avg = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gray(buf: &mut MbBuf) {
|
||||||
|
buf.y.fill(128);
|
||||||
|
buf.c[0].fill(128);
|
||||||
|
buf.c[1].fill(128);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dual-prime opposite-parity vector (§7.6.3.6).
|
||||||
|
fn dual_vector(v: [i32; 2], dmv: [i32; 2], m: i32, e: i32) -> [i32; 2] {
|
||||||
|
let scale = |x: i32| (x * m + (x > 0) as i32) >> 1;
|
||||||
|
[scale(v[0]) + dmv[0], scale(v[1]) + e + dmv[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Predict a 16-wide luma block of `lh` lines (plus chroma) from `f` (one field when `field`),
|
||||||
|
/// into `buf` lines `first + k·step`. `lh` is 16 (frame or field-picture prediction) or 8 (one
|
||||||
|
/// field of a frame macroblock).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn pred_block(
|
||||||
|
p: &PicParams,
|
||||||
|
buf: &mut MbBuf,
|
||||||
|
f: &Frame,
|
||||||
|
field: Option<usize>,
|
||||||
|
mv: [i32; 2],
|
||||||
|
mbx: usize,
|
||||||
|
mby: usize,
|
||||||
|
lh: usize,
|
||||||
|
first: usize,
|
||||||
|
step: usize,
|
||||||
|
(cbw, cbh): (usize, usize),
|
||||||
|
avg: bool,
|
||||||
|
) {
|
||||||
|
// luma: base position in the reference plane's own (frame or field) lines
|
||||||
|
let by = if lh == 8 { mby * 8 } else { mby * 16 };
|
||||||
|
let (x, y) = ((mbx * 16) as i32 + (mv[0] >> 1), by as i32 + (mv[1] >> 1));
|
||||||
|
predict(&mut buf.y, first * 16, 16 * step, f.plane(0, field), x, y, mv[0] & 1 != 0, mv[1] & 1 != 0, 16, lh, avg);
|
||||||
|
let cmv = [if p.shx == 1 { mv[0] / 2 } else { mv[0] }, if p.shy == 1 { mv[1] / 2 } else { mv[1] }];
|
||||||
|
let clh = if lh == 8 { cbh / 2 } else { cbh };
|
||||||
|
let cby = mby * clh;
|
||||||
|
let (cx, cy) = ((mbx * cbw) as i32 + (cmv[0] >> 1), cby as i32 + (cmv[1] >> 1));
|
||||||
|
for c in 0..2 {
|
||||||
|
predict(&mut buf.c[c], first * cbw, cbw * step, f.plane(1 + c, field), cx, cy, cmv[0] & 1 != 0, cmv[1] & 1 != 0, cbw, clh, avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 16x8 motion compensation in a field picture: half `r` (0 upper, 1 lower) of the macroblock.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn pred_half(p: &PicParams, buf: &mut MbBuf, f: &Frame, field: usize, mv: [i32; 2], mbx: usize, mby: usize, r: usize, (cbw, cbh): (usize, usize), avg: bool) {
|
||||||
|
let (x, y) = ((mbx * 16) as i32 + (mv[0] >> 1), (mby * 16 + r * 8) as i32 + (mv[1] >> 1));
|
||||||
|
predict(&mut buf.y, r * 8 * 16, 16, f.plane(0, Some(field)), x, y, mv[0] & 1 != 0, mv[1] & 1 != 0, 16, 8, avg);
|
||||||
|
let cmv = [if p.shx == 1 { mv[0] / 2 } else { mv[0] }, if p.shy == 1 { mv[1] / 2 } else { mv[1] }];
|
||||||
|
let ch = cbh / 2;
|
||||||
|
let (cx, cy) = ((mbx * cbw) as i32 + (cmv[0] >> 1), (mby * cbh + r * ch) as i32 + (cmv[1] >> 1));
|
||||||
|
for c in 0..2 {
|
||||||
|
predict(&mut buf.c[c], r * ch * cbw, cbw, f.plane(1 + c, Some(field)), cx, cy, cmv[0] & 1 != 0, cmv[1] & 1 != 0, cbw, ch, avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The macroblock row a slice starts in (slice_vertical_position and its extension).
|
||||||
|
pub(crate) fn slice_row(vertical_size_extension: bool, code: u8, payload: &[u8]) -> usize {
|
||||||
|
let mut row = code as usize - 1;
|
||||||
|
if vertical_size_extension && !payload.is_empty() {
|
||||||
|
row += ((payload[0] >> 5) as usize) << 7;
|
||||||
|
}
|
||||||
|
row
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
//! Unit tests over hand-built streams (headers, robustness). Decoding accuracy against ffmpeg is
|
||||||
|
//! in `tests/oracle.rs`.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A minimal bit writer for building streams.
|
||||||
|
struct W {
|
||||||
|
out: Vec<u8>,
|
||||||
|
acc: u64,
|
||||||
|
n: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl W {
|
||||||
|
fn new() -> W {
|
||||||
|
W { out: Vec::new(), acc: 0, n: 0 }
|
||||||
|
}
|
||||||
|
fn put(&mut self, v: u32, bits: u32) {
|
||||||
|
for i in (0..bits).rev() {
|
||||||
|
self.acc = (self.acc << 1) | ((v >> i) & 1) as u64;
|
||||||
|
self.n += 1;
|
||||||
|
if self.n == 8 {
|
||||||
|
self.out.push(self.acc as u8);
|
||||||
|
self.acc = 0;
|
||||||
|
self.n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn code(&mut self, s: &str) {
|
||||||
|
for c in s.chars().filter(|c| *c != ' ') {
|
||||||
|
self.put((c == '1') as u32, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn align(&mut self) {
|
||||||
|
while self.n != 0 {
|
||||||
|
self.put(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn start(&mut self, code: u8) {
|
||||||
|
self.align();
|
||||||
|
self.out.extend_from_slice(&[0, 0, 1, code]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An MPEG-2 4:2:0 progressive 32×16 I picture whose macroblocks all have DC `dc` (luma) and
|
||||||
|
/// no AC: two macroblocks, one slice.
|
||||||
|
fn intra_stream(dc_luma: i32) -> Vec<u8> {
|
||||||
|
let mut w = W::new();
|
||||||
|
w.start(0xB3);
|
||||||
|
w.put(32, 12);
|
||||||
|
w.put(16, 12);
|
||||||
|
w.put(1, 4); // square samples
|
||||||
|
w.put(3, 4); // 25 fps
|
||||||
|
w.put(1000, 18);
|
||||||
|
w.put(1, 1);
|
||||||
|
w.put(10, 10);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.start(0xB5);
|
||||||
|
w.put(1, 4);
|
||||||
|
w.put(0x48, 8); // Main@Main
|
||||||
|
w.put(1, 1); // progressive
|
||||||
|
w.put(1, 2); // 4:2:0
|
||||||
|
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.start(0x00);
|
||||||
|
w.put(0, 10);
|
||||||
|
w.put(1, 3); // I
|
||||||
|
w.put(0xFFFF, 16);
|
||||||
|
w.put(0, 1); // extra_bit_picture
|
||||||
|
w.start(0xB5);
|
||||||
|
w.put(8, 4);
|
||||||
|
for _ in 0..4 {
|
||||||
|
w.put(15, 4);
|
||||||
|
}
|
||||||
|
w.put(0, 2); // 8-bit DC
|
||||||
|
w.put(3, 2); // frame picture
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(1, 1); // frame_pred_frame_dct
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(0, 1);
|
||||||
|
w.put(1, 1);
|
||||||
|
w.put(1, 1); // progressive_frame
|
||||||
|
w.put(0, 1);
|
||||||
|
w.start(0x01);
|
||||||
|
w.put(8, 5); // quantiser_scale_code
|
||||||
|
w.put(0, 1); // extra_bit_slice
|
||||||
|
for mb in 0..2 {
|
||||||
|
w.code("1"); // address increment 1
|
||||||
|
w.code("1"); // macroblock_type: intra
|
||||||
|
for b in 0..6 {
|
||||||
|
// DC differential: the first luma block of the slice carries dc - 128
|
||||||
|
let diff = if b == 0 && mb == 0 { dc_luma - 128 } else { 0 };
|
||||||
|
if b < 4 {
|
||||||
|
if diff == 0 {
|
||||||
|
w.code("100");
|
||||||
|
} else {
|
||||||
|
// size 6 covers |diff| < 64
|
||||||
|
w.code("1111 0");
|
||||||
|
let v = if diff > 0 { diff as u32 } else { (diff + 63) as u32 };
|
||||||
|
w.put(v, 6);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.code("00");
|
||||||
|
}
|
||||||
|
w.code("10"); // EOB (table zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.start(0xB7);
|
||||||
|
w.out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decodes_a_hand_built_intra_picture() {
|
||||||
|
let s = intra_stream(160);
|
||||||
|
let info = probe(&s).unwrap();
|
||||||
|
assert_eq!((info.width, info.height, info.mpeg2, info.frame_rate), (32, 16, true, Some((25, 1))));
|
||||||
|
assert_eq!(info.codec_name(), "MPEG-2 Video (Main@Main)");
|
||||||
|
let mut d = Decoder::new();
|
||||||
|
let mut pics = d.decode(&s, 7).unwrap();
|
||||||
|
pics.extend(d.flush());
|
||||||
|
assert_eq!(pics.len(), 1);
|
||||||
|
let p = &pics[0];
|
||||||
|
assert_eq!(d.errors(), 0);
|
||||||
|
assert_eq!((p.width, p.height, p.pts, p.picture_type), (32, 16, 7, PictureType::I));
|
||||||
|
// DC 160 everywhere (the mismatch-control toggle of F[7][7] is far below rounding)
|
||||||
|
assert!(p.y.iter().all(|&v| v == 160), "{:?}", &p.y[..8]);
|
||||||
|
assert!(p.cb.iter().chain(&p.cr).all(|&v| v == 128));
|
||||||
|
assert_eq!(p.field_order(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn truncation_and_mutation_never_panic() {
|
||||||
|
let s = intra_stream(100);
|
||||||
|
for cut in 0..s.len() {
|
||||||
|
let mut d = Decoder::new();
|
||||||
|
let _ = d.decode(&s[..cut], 0);
|
||||||
|
let _ = d.flush();
|
||||||
|
}
|
||||||
|
let mut x = 0x1234_5678u32;
|
||||||
|
for _ in 0..3000 {
|
||||||
|
let mut m = s.clone();
|
||||||
|
for _ in 0..3 {
|
||||||
|
x ^= x << 13;
|
||||||
|
x ^= x >> 17;
|
||||||
|
x ^= x << 5;
|
||||||
|
let i = x as usize % m.len();
|
||||||
|
m[i] ^= (x >> 8) as u8;
|
||||||
|
}
|
||||||
|
let mut d = Decoder::new();
|
||||||
|
let _ = d.decode(&m, 0);
|
||||||
|
let _ = d.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn access_unit_scan() {
|
||||||
|
let s = intra_stream(128);
|
||||||
|
let au = scan_access_unit(&s);
|
||||||
|
assert!(au.sequence_header && au.is_intra() && !au.is_disposable());
|
||||||
|
assert_eq!(au.pictures[0].1.unwrap().picture_structure, 3);
|
||||||
|
}
|
||||||
@@ -0,0 +1,627 @@
|
|||||||
|
//! Variable-length code tables of ITU-T H.262 Annex B (B.1-B.4, B.9-B.15), which include those
|
||||||
|
//! of ISO/IEC 11172-2 Annex B. Codes are written as in the standard (spaces ignored) and turned
|
||||||
|
//! into two-level lookup tables once.
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use crate::bits::Bits;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct Entry {
|
||||||
|
/// Decoded value, or the subtable offset when `sub > 0`.
|
||||||
|
val: i16,
|
||||||
|
/// Code length (0: invalid code).
|
||||||
|
len: u8,
|
||||||
|
/// Index bits of the subtable this entry points to.
|
||||||
|
sub: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A two-level lookup table.
|
||||||
|
pub(crate) struct Vlc {
|
||||||
|
root: u32,
|
||||||
|
table: Vec<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_code(s: &str) -> (u32, u32) {
|
||||||
|
let mut v = 0u32;
|
||||||
|
let mut n = 0u32;
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'0' | '1' => {
|
||||||
|
v = (v << 1) | (c as u32 - '0' as u32);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
' ' => {}
|
||||||
|
_ => panic!("bad code {s}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vlc {
|
||||||
|
pub fn new(root: u32, codes: &[(&str, i16)]) -> Vlc {
|
||||||
|
let codes: Vec<(u32, u32, i16)> = codes
|
||||||
|
.iter()
|
||||||
|
.map(|(s, v)| {
|
||||||
|
let (c, n) = parse_code(s);
|
||||||
|
(c, n, *v)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut table = vec![Entry::default(); 1 << root];
|
||||||
|
// subtable sizes per root prefix
|
||||||
|
let mut sub_bits = vec![0u32; 1 << root];
|
||||||
|
for &(c, n, _) in &codes {
|
||||||
|
if n > root {
|
||||||
|
let p = (c >> (n - root)) as usize;
|
||||||
|
sub_bits[p] = sub_bits[p].max(n - root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p, &b) in sub_bits.iter().enumerate() {
|
||||||
|
if b > 0 {
|
||||||
|
let off = table.len();
|
||||||
|
table.extend(std::iter::repeat_n(Entry::default(), 1 << b));
|
||||||
|
table[p] = Entry { val: off as i16, len: 0, sub: b as u8 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &(c, n, v) in &codes {
|
||||||
|
if n <= root {
|
||||||
|
let shift = root - n;
|
||||||
|
for k in 0..(1u32 << shift) {
|
||||||
|
let e = &mut table[((c << shift) | k) as usize];
|
||||||
|
assert!(e.len == 0 && e.sub == 0, "VLC prefix clash");
|
||||||
|
*e = Entry { val: v, len: n as u8, sub: 0 };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let p = (c >> (n - root)) as usize;
|
||||||
|
let Entry { val: off, sub: b, .. } = table[p];
|
||||||
|
let b = b as u32;
|
||||||
|
let rest = c & ((1 << (n - root)) - 1);
|
||||||
|
let shift = b - (n - root);
|
||||||
|
for k in 0..(1u32 << shift) {
|
||||||
|
let e = &mut table[off as usize + ((rest << shift) | k) as usize];
|
||||||
|
assert!(e.len == 0, "VLC prefix clash");
|
||||||
|
*e = Entry { val: v, len: (n - root) as u8, sub: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vlc { root, table }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode one code; `None` for an invalid code.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn decode(&self, b: &mut Bits) -> Option<i16> {
|
||||||
|
let w = b.peek32();
|
||||||
|
let e = self.table[(w >> (32 - self.root)) as usize];
|
||||||
|
if e.sub == 0 {
|
||||||
|
if e.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
b.skip(e.len as u32);
|
||||||
|
return Some(e.val);
|
||||||
|
}
|
||||||
|
let idx = ((w << self.root) >> (32 - e.sub as u32)) as usize;
|
||||||
|
let e2 = self.table[e.val as usize + idx];
|
||||||
|
if e2.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
b.skip(self.root + e2.len as u32);
|
||||||
|
Some(e2.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// macroblock_address_increment (B.1): 1..=33, [`MBA_ESCAPE`], [`MBA_STUFFING`].
|
||||||
|
pub(crate) const MBA_ESCAPE: i16 = 34;
|
||||||
|
pub(crate) const MBA_STUFFING: i16 = 35;
|
||||||
|
const MBA: &[(&str, i16)] = &[
|
||||||
|
("1", 1),
|
||||||
|
("011", 2),
|
||||||
|
("010", 3),
|
||||||
|
("0011", 4),
|
||||||
|
("0010", 5),
|
||||||
|
("0001 1", 6),
|
||||||
|
("0001 0", 7),
|
||||||
|
("0000 111", 8),
|
||||||
|
("0000 110", 9),
|
||||||
|
("0000 1011", 10),
|
||||||
|
("0000 1010", 11),
|
||||||
|
("0000 1001", 12),
|
||||||
|
("0000 1000", 13),
|
||||||
|
("0000 0111", 14),
|
||||||
|
("0000 0110", 15),
|
||||||
|
("0000 0101 11", 16),
|
||||||
|
("0000 0101 10", 17),
|
||||||
|
("0000 0101 01", 18),
|
||||||
|
("0000 0101 00", 19),
|
||||||
|
("0000 0100 11", 20),
|
||||||
|
("0000 0100 10", 21),
|
||||||
|
("0000 0100 011", 22),
|
||||||
|
("0000 0100 010", 23),
|
||||||
|
("0000 0100 001", 24),
|
||||||
|
("0000 0100 000", 25),
|
||||||
|
("0000 0011 111", 26),
|
||||||
|
("0000 0011 110", 27),
|
||||||
|
("0000 0011 101", 28),
|
||||||
|
("0000 0011 100", 29),
|
||||||
|
("0000 0011 011", 30),
|
||||||
|
("0000 0011 010", 31),
|
||||||
|
("0000 0011 001", 32),
|
||||||
|
("0000 0011 000", 33),
|
||||||
|
("0000 0001 000", MBA_ESCAPE),
|
||||||
|
("0000 0001 111", MBA_STUFFING),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// macroblock_type flags.
|
||||||
|
pub(crate) const MB_QUANT: i16 = 1;
|
||||||
|
pub(crate) const MB_FWD: i16 = 2;
|
||||||
|
pub(crate) const MB_BWD: i16 = 4;
|
||||||
|
pub(crate) const MB_PATTERN: i16 = 8;
|
||||||
|
pub(crate) const MB_INTRA: i16 = 16;
|
||||||
|
|
||||||
|
/// B.2: I pictures.
|
||||||
|
const MBTYPE_I: &[(&str, i16)] = &[("1", MB_INTRA), ("01", MB_INTRA | MB_QUANT)];
|
||||||
|
/// B.3: P pictures.
|
||||||
|
const MBTYPE_P: &[(&str, i16)] = &[
|
||||||
|
("1", MB_FWD | MB_PATTERN),
|
||||||
|
("01", MB_PATTERN),
|
||||||
|
("001", MB_FWD),
|
||||||
|
("0001 1", MB_INTRA),
|
||||||
|
("0001 0", MB_QUANT | MB_FWD | MB_PATTERN),
|
||||||
|
("0000 1", MB_QUANT | MB_PATTERN),
|
||||||
|
("0000 01", MB_QUANT | MB_INTRA),
|
||||||
|
];
|
||||||
|
/// B.4: B pictures.
|
||||||
|
const MBTYPE_B: &[(&str, i16)] = &[
|
||||||
|
("10", MB_FWD | MB_BWD),
|
||||||
|
("11", MB_FWD | MB_BWD | MB_PATTERN),
|
||||||
|
("010", MB_BWD),
|
||||||
|
("011", MB_BWD | MB_PATTERN),
|
||||||
|
("0010", MB_FWD),
|
||||||
|
("0011", MB_FWD | MB_PATTERN),
|
||||||
|
("0001 1", MB_INTRA),
|
||||||
|
("0001 0", MB_QUANT | MB_FWD | MB_BWD | MB_PATTERN),
|
||||||
|
("0000 11", MB_QUANT | MB_FWD | MB_PATTERN),
|
||||||
|
("0000 10", MB_QUANT | MB_BWD | MB_PATTERN),
|
||||||
|
("0000 01", MB_QUANT | MB_INTRA),
|
||||||
|
];
|
||||||
|
/// ISO/IEC 11172-2 D pictures (DC intra-coded).
|
||||||
|
const MBTYPE_D: &[(&str, i16)] = &[("1", MB_INTRA)];
|
||||||
|
|
||||||
|
/// B.9: coded_block_pattern (0 only in ISO/IEC 13818-2).
|
||||||
|
const CBP: &[(&str, i16)] = &[
|
||||||
|
("111", 60),
|
||||||
|
("1101", 4),
|
||||||
|
("1100", 8),
|
||||||
|
("1011", 16),
|
||||||
|
("1010", 32),
|
||||||
|
("1001 1", 12),
|
||||||
|
("1001 0", 48),
|
||||||
|
("1000 1", 20),
|
||||||
|
("1000 0", 40),
|
||||||
|
("0111 1", 28),
|
||||||
|
("0111 0", 44),
|
||||||
|
("0110 1", 52),
|
||||||
|
("0110 0", 56),
|
||||||
|
("0101 1", 1),
|
||||||
|
("0101 0", 61),
|
||||||
|
("0100 1", 2),
|
||||||
|
("0100 0", 62),
|
||||||
|
("0011 11", 24),
|
||||||
|
("0011 10", 36),
|
||||||
|
("0011 01", 3),
|
||||||
|
("0011 00", 63),
|
||||||
|
("0010 111", 5),
|
||||||
|
("0010 110", 9),
|
||||||
|
("0010 101", 17),
|
||||||
|
("0010 100", 33),
|
||||||
|
("0010 011", 6),
|
||||||
|
("0010 010", 10),
|
||||||
|
("0010 001", 18),
|
||||||
|
("0010 000", 34),
|
||||||
|
("0001 1111", 7),
|
||||||
|
("0001 1110", 11),
|
||||||
|
("0001 1101", 19),
|
||||||
|
("0001 1100", 35),
|
||||||
|
("0001 1011", 13),
|
||||||
|
("0001 1010", 49),
|
||||||
|
("0001 1001", 21),
|
||||||
|
("0001 1000", 41),
|
||||||
|
("0001 0111", 14),
|
||||||
|
("0001 0110", 50),
|
||||||
|
("0001 0101", 22),
|
||||||
|
("0001 0100", 42),
|
||||||
|
("0001 0011", 15),
|
||||||
|
("0001 0010", 51),
|
||||||
|
("0001 0001", 23),
|
||||||
|
("0001 0000", 43),
|
||||||
|
("0000 1111", 25),
|
||||||
|
("0000 1110", 37),
|
||||||
|
("0000 1101", 26),
|
||||||
|
("0000 1100", 38),
|
||||||
|
("0000 1011", 29),
|
||||||
|
("0000 1010", 45),
|
||||||
|
("0000 1001", 53),
|
||||||
|
("0000 1000", 57),
|
||||||
|
("0000 0111", 30),
|
||||||
|
("0000 0110", 46),
|
||||||
|
("0000 0101", 54),
|
||||||
|
("0000 0100", 58),
|
||||||
|
("0000 0011 1", 31),
|
||||||
|
("0000 0011 0", 47),
|
||||||
|
("0000 0010 1", 55),
|
||||||
|
("0000 0010 0", 59),
|
||||||
|
("0000 0001 1", 27),
|
||||||
|
("0000 0001 0", 39),
|
||||||
|
("0000 0000 1", 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// B.10: motion_code (-16..=16).
|
||||||
|
const MOTION: &[(&str, i16)] = &[
|
||||||
|
("0000 0011 001", -16),
|
||||||
|
("0000 0011 011", -15),
|
||||||
|
("0000 0011 101", -14),
|
||||||
|
("0000 0011 111", -13),
|
||||||
|
("0000 0100 001", -12),
|
||||||
|
("0000 0100 011", -11),
|
||||||
|
("0000 0100 11", -10),
|
||||||
|
("0000 0101 01", -9),
|
||||||
|
("0000 0101 11", -8),
|
||||||
|
("0000 0111", -7),
|
||||||
|
("0000 1001", -6),
|
||||||
|
("0000 1011", -5),
|
||||||
|
("0000 111", -4),
|
||||||
|
("0001 1", -3),
|
||||||
|
("0011", -2),
|
||||||
|
("011", -1),
|
||||||
|
("1", 0),
|
||||||
|
("010", 1),
|
||||||
|
("0010", 2),
|
||||||
|
("0001 0", 3),
|
||||||
|
("0000 110", 4),
|
||||||
|
("0000 1010", 5),
|
||||||
|
("0000 1000", 6),
|
||||||
|
("0000 0110", 7),
|
||||||
|
("0000 0101 10", 8),
|
||||||
|
("0000 0101 00", 9),
|
||||||
|
("0000 0100 10", 10),
|
||||||
|
("0000 0100 010", 11),
|
||||||
|
("0000 0100 000", 12),
|
||||||
|
("0000 0011 110", 13),
|
||||||
|
("0000 0011 100", 14),
|
||||||
|
("0000 0011 010", 15),
|
||||||
|
("0000 0011 000", 16),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// B.11: dmvector.
|
||||||
|
const DMV: &[(&str, i16)] = &[("11", -1), ("0", 0), ("10", 1)];
|
||||||
|
|
||||||
|
/// B.12: dct_dc_size_luminance.
|
||||||
|
const DC_LUMA: &[(&str, i16)] = &[
|
||||||
|
("100", 0),
|
||||||
|
("00", 1),
|
||||||
|
("01", 2),
|
||||||
|
("101", 3),
|
||||||
|
("110", 4),
|
||||||
|
("1110", 5),
|
||||||
|
("1111 0", 6),
|
||||||
|
("1111 10", 7),
|
||||||
|
("1111 110", 8),
|
||||||
|
("1111 1110", 9),
|
||||||
|
("1111 1111 0", 10),
|
||||||
|
("1111 1111 1", 11),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// B.13: dct_dc_size_chrominance.
|
||||||
|
const DC_CHROMA: &[(&str, i16)] = &[
|
||||||
|
("00", 0),
|
||||||
|
("01", 1),
|
||||||
|
("10", 2),
|
||||||
|
("110", 3),
|
||||||
|
("1110", 4),
|
||||||
|
("1111 0", 5),
|
||||||
|
("1111 10", 6),
|
||||||
|
("1111 110", 7),
|
||||||
|
("1111 1110", 8),
|
||||||
|
("1111 1111 0", 9),
|
||||||
|
("1111 1111 10", 10),
|
||||||
|
("1111 1111 11", 11),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// DCT coefficient table values: `run | level << 6`, or these.
|
||||||
|
pub(crate) const DCT_EOB: i16 = -1;
|
||||||
|
pub(crate) const DCT_ESCAPE: i16 = -2;
|
||||||
|
|
||||||
|
const fn rl(run: i16, level: i16) -> i16 {
|
||||||
|
run | (level << 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Table zero's codes for levels 12-15 at run 0 (table one has shorter ones).
|
||||||
|
const DCT_ZERO_LONG: &[(&str, i16)] =
|
||||||
|
&[("0000 0000 1101 0", rl(0, 12)), ("0000 0000 1100 1", rl(0, 13)), ("0000 0000 1100 0", rl(0, 14)), ("0000 0000 1011 1", rl(0, 15))];
|
||||||
|
|
||||||
|
/// Codes from "0000 0000 1" on, common to tables zero and one (sign bit excluded).
|
||||||
|
const DCT_LONG: &[(&str, i16)] = &[
|
||||||
|
("0000 0000 1011 0", rl(1, 6)),
|
||||||
|
("0000 0000 1010 1", rl(1, 7)),
|
||||||
|
("0000 0000 1010 0", rl(2, 5)),
|
||||||
|
("0000 0000 1001 1", rl(3, 4)),
|
||||||
|
("0000 0000 1001 0", rl(5, 3)),
|
||||||
|
("0000 0000 1000 1", rl(9, 2)),
|
||||||
|
("0000 0000 1000 0", rl(10, 2)),
|
||||||
|
("0000 0000 1111 1", rl(22, 1)),
|
||||||
|
("0000 0000 1111 0", rl(23, 1)),
|
||||||
|
("0000 0000 1110 1", rl(24, 1)),
|
||||||
|
("0000 0000 1110 0", rl(25, 1)),
|
||||||
|
("0000 0000 1101 1", rl(26, 1)),
|
||||||
|
("0000 0000 0111 11", rl(0, 16)),
|
||||||
|
("0000 0000 0111 10", rl(0, 17)),
|
||||||
|
("0000 0000 0111 01", rl(0, 18)),
|
||||||
|
("0000 0000 0111 00", rl(0, 19)),
|
||||||
|
("0000 0000 0110 11", rl(0, 20)),
|
||||||
|
("0000 0000 0110 10", rl(0, 21)),
|
||||||
|
("0000 0000 0110 01", rl(0, 22)),
|
||||||
|
("0000 0000 0110 00", rl(0, 23)),
|
||||||
|
("0000 0000 0101 11", rl(0, 24)),
|
||||||
|
("0000 0000 0101 10", rl(0, 25)),
|
||||||
|
("0000 0000 0101 01", rl(0, 26)),
|
||||||
|
("0000 0000 0101 00", rl(0, 27)),
|
||||||
|
("0000 0000 0100 11", rl(0, 28)),
|
||||||
|
("0000 0000 0100 10", rl(0, 29)),
|
||||||
|
("0000 0000 0100 01", rl(0, 30)),
|
||||||
|
("0000 0000 0100 00", rl(0, 31)),
|
||||||
|
("0000 0000 0011 000", rl(0, 32)),
|
||||||
|
("0000 0000 0010 111", rl(0, 33)),
|
||||||
|
("0000 0000 0010 110", rl(0, 34)),
|
||||||
|
("0000 0000 0010 101", rl(0, 35)),
|
||||||
|
("0000 0000 0010 100", rl(0, 36)),
|
||||||
|
("0000 0000 0010 011", rl(0, 37)),
|
||||||
|
("0000 0000 0010 010", rl(0, 38)),
|
||||||
|
("0000 0000 0010 001", rl(0, 39)),
|
||||||
|
("0000 0000 0010 000", rl(0, 40)),
|
||||||
|
("0000 0000 0011 111", rl(1, 8)),
|
||||||
|
("0000 0000 0011 110", rl(1, 9)),
|
||||||
|
("0000 0000 0011 101", rl(1, 10)),
|
||||||
|
("0000 0000 0011 100", rl(1, 11)),
|
||||||
|
("0000 0000 0011 011", rl(1, 12)),
|
||||||
|
("0000 0000 0011 010", rl(1, 13)),
|
||||||
|
("0000 0000 0011 001", rl(1, 14)),
|
||||||
|
("0000 0000 0001 0011", rl(1, 15)),
|
||||||
|
("0000 0000 0001 0010", rl(1, 16)),
|
||||||
|
("0000 0000 0001 0001", rl(1, 17)),
|
||||||
|
("0000 0000 0001 0000", rl(1, 18)),
|
||||||
|
("0000 0000 0001 0100", rl(6, 3)),
|
||||||
|
("0000 0000 0001 1010", rl(11, 2)),
|
||||||
|
("0000 0000 0001 1001", rl(12, 2)),
|
||||||
|
("0000 0000 0001 1000", rl(13, 2)),
|
||||||
|
("0000 0000 0001 0111", rl(14, 2)),
|
||||||
|
("0000 0000 0001 0110", rl(15, 2)),
|
||||||
|
("0000 0000 0001 0101", rl(16, 2)),
|
||||||
|
("0000 0000 0001 1111", rl(27, 1)),
|
||||||
|
("0000 0000 0001 1110", rl(28, 1)),
|
||||||
|
("0000 0000 0001 1101", rl(29, 1)),
|
||||||
|
("0000 0000 0001 1100", rl(30, 1)),
|
||||||
|
("0000 0000 0001 1011", rl(31, 1)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// "0000 0001 xxxx" codes of both tables (table one replaces the six marked `*` with shorter ones).
|
||||||
|
const DCT_MID_SHARED: &[(&str, i16)] = &[
|
||||||
|
("0000 0001 1100", rl(3, 3)),
|
||||||
|
("0000 0001 0010", rl(4, 3)),
|
||||||
|
("0000 0001 1110", rl(6, 2)),
|
||||||
|
("0000 0001 0101", rl(7, 2)),
|
||||||
|
("0000 0001 0001", rl(8, 2)),
|
||||||
|
("0000 0001 1111", rl(17, 1)),
|
||||||
|
("0000 0001 1010", rl(18, 1)),
|
||||||
|
("0000 0001 1001", rl(19, 1)),
|
||||||
|
("0000 0001 0111", rl(20, 1)),
|
||||||
|
("0000 0001 0110", rl(21, 1)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// B.14: DCT coefficients table zero (the "1s" first-coefficient code is handled by the caller).
|
||||||
|
const DCT_ZERO: &[(&str, i16)] = &[
|
||||||
|
("10", DCT_EOB),
|
||||||
|
("11", rl(0, 1)),
|
||||||
|
("011", rl(1, 1)),
|
||||||
|
("0100", rl(0, 2)),
|
||||||
|
("0101", rl(2, 1)),
|
||||||
|
("0010 1", rl(0, 3)),
|
||||||
|
("0011 1", rl(3, 1)),
|
||||||
|
("0011 0", rl(4, 1)),
|
||||||
|
("0001 10", rl(1, 2)),
|
||||||
|
("0001 11", rl(5, 1)),
|
||||||
|
("0001 01", rl(6, 1)),
|
||||||
|
("0001 00", rl(7, 1)),
|
||||||
|
("0000 110", rl(0, 4)),
|
||||||
|
("0000 100", rl(2, 2)),
|
||||||
|
("0000 111", rl(8, 1)),
|
||||||
|
("0000 101", rl(9, 1)),
|
||||||
|
("0000 01", DCT_ESCAPE),
|
||||||
|
("0010 0110", rl(0, 5)),
|
||||||
|
("0010 0001", rl(0, 6)),
|
||||||
|
("0010 0101", rl(1, 3)),
|
||||||
|
("0010 0100", rl(3, 2)),
|
||||||
|
("0010 0111", rl(10, 1)),
|
||||||
|
("0010 0011", rl(11, 1)),
|
||||||
|
("0010 0010", rl(12, 1)),
|
||||||
|
("0010 0000", rl(13, 1)),
|
||||||
|
("0000 0010 10", rl(0, 7)),
|
||||||
|
("0000 0011 00", rl(1, 4)),
|
||||||
|
("0000 0010 11", rl(2, 3)),
|
||||||
|
("0000 0011 11", rl(4, 2)),
|
||||||
|
("0000 0010 01", rl(5, 2)),
|
||||||
|
("0000 0011 10", rl(14, 1)),
|
||||||
|
("0000 0011 01", rl(15, 1)),
|
||||||
|
("0000 0010 00", rl(16, 1)),
|
||||||
|
("0000 0001 1101", rl(0, 8)),
|
||||||
|
("0000 0001 1000", rl(0, 9)),
|
||||||
|
("0000 0001 0011", rl(0, 10)),
|
||||||
|
("0000 0001 0000", rl(0, 11)),
|
||||||
|
("0000 0001 1011", rl(1, 5)),
|
||||||
|
("0000 0001 0100", rl(2, 4)),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// B.15: DCT coefficients table one (intra blocks with intra_vlc_format = 1).
|
||||||
|
const DCT_ONE: &[(&str, i16)] = &[
|
||||||
|
("0110", DCT_EOB),
|
||||||
|
("10", rl(0, 1)),
|
||||||
|
("010", rl(1, 1)),
|
||||||
|
("110", rl(0, 2)),
|
||||||
|
("0010 1", rl(2, 1)),
|
||||||
|
("0111", rl(0, 3)),
|
||||||
|
("0011 1", rl(3, 1)),
|
||||||
|
("0001 10", rl(4, 1)),
|
||||||
|
("0011 0", rl(1, 2)),
|
||||||
|
("0001 11", rl(5, 1)),
|
||||||
|
("0000 110", rl(6, 1)),
|
||||||
|
("0000 100", rl(7, 1)),
|
||||||
|
("1110 0", rl(0, 4)),
|
||||||
|
("0000 111", rl(2, 2)),
|
||||||
|
("0000 101", rl(8, 1)),
|
||||||
|
("1111 000", rl(9, 1)),
|
||||||
|
("0000 01", DCT_ESCAPE),
|
||||||
|
("1110 1", rl(0, 5)),
|
||||||
|
("0001 01", rl(0, 6)),
|
||||||
|
("1111 001", rl(1, 3)),
|
||||||
|
("0010 0110", rl(3, 2)),
|
||||||
|
("1111 010", rl(10, 1)),
|
||||||
|
("0010 0001", rl(11, 1)),
|
||||||
|
("0010 0101", rl(12, 1)),
|
||||||
|
("0010 0100", rl(13, 1)),
|
||||||
|
("0001 00", rl(0, 7)),
|
||||||
|
("0010 0111", rl(1, 4)),
|
||||||
|
("1111 1100", rl(2, 3)),
|
||||||
|
("1111 1101", rl(4, 2)),
|
||||||
|
("0000 0010 0", rl(5, 2)),
|
||||||
|
("0000 0010 1", rl(14, 1)),
|
||||||
|
("0000 0011 1", rl(15, 1)),
|
||||||
|
("0000 0011 01", rl(16, 1)),
|
||||||
|
("1111 011", rl(0, 8)),
|
||||||
|
("1111 100", rl(0, 9)),
|
||||||
|
("0010 0011", rl(0, 10)),
|
||||||
|
("0010 0010", rl(0, 11)),
|
||||||
|
("0010 0000", rl(1, 5)),
|
||||||
|
("0000 0011 00", rl(2, 4)),
|
||||||
|
("1111 1010", rl(0, 12)),
|
||||||
|
("1111 1011", rl(0, 13)),
|
||||||
|
("1111 1110", rl(0, 14)),
|
||||||
|
("1111 1111", rl(0, 15)),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) struct Tables {
|
||||||
|
pub mba: Vlc,
|
||||||
|
pub mbtype: [Vlc; 4],
|
||||||
|
pub cbp: Vlc,
|
||||||
|
pub motion: Vlc,
|
||||||
|
pub dmv: Vlc,
|
||||||
|
pub dc_luma: Vlc,
|
||||||
|
pub dc_chroma: Vlc,
|
||||||
|
pub dct_zero: Vlc,
|
||||||
|
pub dct_one: Vlc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A complete DCT coefficient table: its own short codes plus the shared long ones.
|
||||||
|
fn dct_table(short: &'static [(&'static str, i16)], extra: &'static [(&'static str, i16)]) -> Vec<(&'static str, i16)> {
|
||||||
|
let mut v = short.to_vec();
|
||||||
|
v.extend_from_slice(extra);
|
||||||
|
v.extend_from_slice(DCT_MID_SHARED);
|
||||||
|
v.extend_from_slice(DCT_LONG);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tables() -> &'static Tables {
|
||||||
|
static T: OnceLock<Tables> = OnceLock::new();
|
||||||
|
T.get_or_init(|| Tables {
|
||||||
|
mba: Vlc::new(8, MBA),
|
||||||
|
mbtype: [Vlc::new(2, MBTYPE_I), Vlc::new(6, MBTYPE_P), Vlc::new(6, MBTYPE_B), Vlc::new(1, MBTYPE_D)],
|
||||||
|
cbp: Vlc::new(9, CBP),
|
||||||
|
motion: Vlc::new(8, MOTION),
|
||||||
|
dmv: Vlc::new(2, DMV),
|
||||||
|
dc_luma: Vlc::new(9, DC_LUMA),
|
||||||
|
dc_chroma: Vlc::new(10, DC_CHROMA),
|
||||||
|
dct_zero: Vlc::new(9, &dct_table(DCT_ZERO, DCT_ZERO_LONG)),
|
||||||
|
dct_one: Vlc::new(9, &dct_table(DCT_ONE, &[])),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Kraft sum of a code list (2^-len summed).
|
||||||
|
fn kraft(codes: &[(&str, i16)]) -> f64 {
|
||||||
|
codes.iter().map(|(c, _)| 0.5f64.powi(parse_code(c).1 as i32)).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tables_build_without_prefix_clashes() {
|
||||||
|
let _ = tables();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kraft_sums_match_the_reserved_codes() {
|
||||||
|
// B.1: "0000 0000", "0000 0010" and "0000 0001 001/010/011/100/101/110" are unused
|
||||||
|
let unused = 2.0 * 0.5f64.powi(8) + 6.0 * 0.5f64.powi(11);
|
||||||
|
assert!((kraft(MBA) + unused - 1.0).abs() < 1e-12);
|
||||||
|
assert!((kraft(MBTYPE_I) - 0.75).abs() < 1e-12, "01 is the last I code: 00 is forbidden");
|
||||||
|
assert!((kraft(MBTYPE_P) + 0.5f64.powi(6) - 1.0).abs() < 1e-12);
|
||||||
|
assert!((kraft(MBTYPE_B) + 0.5f64.powi(6) - 1.0).abs() < 1e-12);
|
||||||
|
// B.9: only "0000 0000 0" is forbidden
|
||||||
|
assert!((kraft(CBP) + 0.5f64.powi(9) - 1.0).abs() < 1e-12);
|
||||||
|
// B.10: "0000 0000", "0000 0001" and "0000 0010" are unused
|
||||||
|
assert!((kraft(MOTION) + 3.0 * 0.5f64.powi(8) - 1.0).abs() < 1e-12);
|
||||||
|
assert!((kraft(DMV) - 1.0).abs() < 1e-12);
|
||||||
|
assert!((kraft(DC_LUMA) - 1.0).abs() < 1e-12);
|
||||||
|
assert!((kraft(DC_CHROMA) - 1.0).abs() < 1e-12);
|
||||||
|
// table zero is complete except "0000 0000 0000 xxxx"
|
||||||
|
let z = kraft(&dct_table(DCT_ZERO, DCT_ZERO_LONG));
|
||||||
|
assert!((z + 0.5f64.powi(12) - 1.0).abs() < 1e-12, "{z}");
|
||||||
|
// table one leaves six 12-bit and four 13-bit codes of table zero unused
|
||||||
|
let o = kraft(&dct_table(DCT_ONE, &[]));
|
||||||
|
assert!((o + 0.5f64.powi(12) + 6.0 * 0.5f64.powi(12) + 4.0 * 0.5f64.powi(13) - 1.0).abs() < 1e-12, "{o}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_value_appears_once() {
|
||||||
|
let mut seen = [false; 64];
|
||||||
|
for &(_, v) in CBP {
|
||||||
|
assert!(!seen[v as usize]);
|
||||||
|
seen[v as usize] = true;
|
||||||
|
}
|
||||||
|
assert!(seen.iter().all(|&s| s));
|
||||||
|
let mut m: Vec<i16> = MOTION.iter().map(|x| x.1).collect();
|
||||||
|
m.sort();
|
||||||
|
assert_eq!(m, (-16..=16).collect::<Vec<_>>());
|
||||||
|
// motion codes are the address increment codes with a trailing sign bit
|
||||||
|
for &(c, v) in MOTION {
|
||||||
|
if v == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (code, n) = parse_code(c);
|
||||||
|
let mba = MBA.iter().find(|(m, _)| parse_code(m) == (code, n)).map(|m| m.1).unwrap();
|
||||||
|
assert_eq!(mba / 2, v.abs());
|
||||||
|
assert_eq!(mba % 2 == 0, v < 0, "{c}");
|
||||||
|
}
|
||||||
|
// run/level pairs are distinct within each table
|
||||||
|
for t in [dct_table(DCT_ZERO, DCT_ZERO_LONG), dct_table(DCT_ONE, &[])] {
|
||||||
|
let mut vals: Vec<i16> = t.iter().map(|x| x.1).filter(|&v| v >= 0).collect();
|
||||||
|
let n = vals.len();
|
||||||
|
vals.sort();
|
||||||
|
vals.dedup();
|
||||||
|
assert_eq!(vals.len(), n);
|
||||||
|
assert_eq!(n, 111, "111 run/level pairs besides EOB and escape");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decodes_codes() {
|
||||||
|
let t = tables();
|
||||||
|
// 0000 0011 000 (33) then 1 (1) then 0000 0001 000 (escape)
|
||||||
|
let d = [0b0000_0011, 0b0001_0000, 0b0001_0000, 0];
|
||||||
|
let mut b = Bits::new(&d);
|
||||||
|
assert_eq!(t.mba.decode(&mut b), Some(33));
|
||||||
|
assert_eq!(t.mba.decode(&mut b), Some(1));
|
||||||
|
assert_eq!(t.mba.decode(&mut b), Some(MBA_ESCAPE));
|
||||||
|
let d = [0b0000_0000, 0b0001_1111, 0];
|
||||||
|
let mut b = Bits::new(&d);
|
||||||
|
assert_eq!(t.dct_zero.decode(&mut b), Some(rl(27, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
//! Shared helpers for the MPEG video oracle tests. ffmpeg is used only as an external fixture
|
||||||
|
//! generator and reference decoder (never linked).
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use filmcraft_mpeg2v::{ChromaFormat, Decoder, Picture};
|
||||||
|
|
||||||
|
pub fn fixture_dir() -> PathBuf {
|
||||||
|
filmcraft_testkit::fixtures_dir("mpeg2v")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An elementary-stream fixture: a lavfi source encoded by ffmpeg's MPEG-1/2 video encoder.
|
||||||
|
pub struct Spec {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub lavfi: &'static str,
|
||||||
|
pub frames: u32,
|
||||||
|
pub enc: &'static [&'static str],
|
||||||
|
/// ffmpeg rawvideo pixel format of the decoded frames.
|
||||||
|
pub pix_fmt: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(spec: &Spec) -> PathBuf {
|
||||||
|
fixture_dir().join(format!("{}.{}", spec.name, if spec.enc.contains(&"mpeg1video") { "m1v" } else { "m2v" }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate (or reuse) the fixture.
|
||||||
|
pub fn make(ff: &Path, spec: &Spec) -> Option<PathBuf> {
|
||||||
|
let out = path(spec);
|
||||||
|
filmcraft_testkit::fixtures::generate(&out, |tmp| {
|
||||||
|
let frames = spec.frames.to_string();
|
||||||
|
let fmt = if spec.enc.contains(&"mpeg1video") { "mpeg1video" } else { "mpeg2video" };
|
||||||
|
let st = Command::new(ff)
|
||||||
|
.args(["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", spec.lavfi, "-frames:v", &frames])
|
||||||
|
.args(spec.enc)
|
||||||
|
.args(["-f", fmt])
|
||||||
|
.arg(tmp)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.status();
|
||||||
|
matches!(st, Ok(s) if s.success())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ffmpeg's decode of `file` as raw frames.
|
||||||
|
pub fn ffmpeg_frames(ff: &Path, file: &Path, pix_fmt: &str) -> Vec<u8> {
|
||||||
|
let o = Command::new(ff)
|
||||||
|
.args(["-hide_banner", "-loglevel", "error", "-i"])
|
||||||
|
.arg(file)
|
||||||
|
.args(["-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", pix_fmt, "-"])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.expect("run ffmpeg");
|
||||||
|
assert!(o.status.success(), "ffmpeg: {}", String::from_utf8_lossy(&o.stderr));
|
||||||
|
o.stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode an elementary stream access unit by access unit (pts = index), flushing at the end.
|
||||||
|
pub fn decode_es(es: &[u8], threads: bool) -> (Vec<Picture>, Decoder) {
|
||||||
|
let mut d = Decoder::new();
|
||||||
|
d.set_threads(threads);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for (i, r) in filmcraft_mpeg2v::access_units(es).into_iter().enumerate() {
|
||||||
|
out.extend(d.decode(&es[r], i as i64).expect("decode"));
|
||||||
|
}
|
||||||
|
out.extend(d.flush());
|
||||||
|
(out, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-frame comparison statistics against raw planes.
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
pub struct Stats {
|
||||||
|
pub max: u8,
|
||||||
|
pub sse: f64,
|
||||||
|
pub n: u64,
|
||||||
|
/// Samples differing at all.
|
||||||
|
pub diff: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
pub fn psnr(&self) -> f64 {
|
||||||
|
if self.sse == 0.0 { f64::INFINITY } else { 10.0 * (255.0 * 255.0 * self.n as f64 / self.sse).log10() }
|
||||||
|
}
|
||||||
|
pub fn add(&mut self, a: &[u8], b: &[u8]) {
|
||||||
|
assert_eq!(a.len(), b.len(), "plane size");
|
||||||
|
for (x, y) in a.iter().zip(b) {
|
||||||
|
let d = x.abs_diff(*y);
|
||||||
|
self.max = self.max.max(d);
|
||||||
|
self.sse += (d as f64) * (d as f64);
|
||||||
|
self.diff += (d != 0) as u64;
|
||||||
|
}
|
||||||
|
self.n += a.len() as u64;
|
||||||
|
}
|
||||||
|
pub fn merge(&mut self, o: &Stats) {
|
||||||
|
self.max = self.max.max(o.max);
|
||||||
|
self.sse += o.sse;
|
||||||
|
self.n += o.n;
|
||||||
|
self.diff += o.diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare decoded pictures with ffmpeg's raw frames; returns per-frame stats.
|
||||||
|
pub fn compare(pics: &[Picture], raw: &[u8]) -> Vec<Stats> {
|
||||||
|
let p0 = &pics[0];
|
||||||
|
let (w, h) = (p0.width as usize, p0.height as usize);
|
||||||
|
let (cw, ch) = (p0.chroma_width() as usize, p0.chroma_height() as usize);
|
||||||
|
let fsize = w * h + 2 * cw * ch;
|
||||||
|
assert_eq!(raw.len() % fsize, 0, "raw size");
|
||||||
|
assert_eq!(raw.len() / fsize, pics.len(), "frame count = ffmpeg's");
|
||||||
|
pics.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, p)| {
|
||||||
|
let f = &raw[i * fsize..(i + 1) * fsize];
|
||||||
|
let mut s = Stats::default();
|
||||||
|
s.add(&p.y, &f[..w * h]);
|
||||||
|
s.add(&p.cb, &f[w * h..w * h + cw * ch]);
|
||||||
|
s.add(&p.cr, &f[w * h + cw * ch..]);
|
||||||
|
s
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pix_fmt_of(c: ChromaFormat) -> &'static str {
|
||||||
|
match c {
|
||||||
|
ChromaFormat::Yuv420 => "yuv420p",
|
||||||
|
ChromaFormat::Yuv422 => "yuv422p",
|
||||||
|
ChromaFormat::Yuv444 => "yuv444p",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
//! MPEG-1/2 video against ffmpeg: ffmpeg encodes lavfi test patterns into elementary streams,
|
||||||
|
//! ffmpeg's decode of the same file is the reference. MPEG-2's IDCT is not bit-exact by
|
||||||
|
//! definition (any IEEE 1180-accurate IDCT conforms), so frames are compared within a tolerance;
|
||||||
|
//! frame count and display order are exact.
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
const SPECS: &[Spec] = &[
|
||||||
|
// progressive 4:2:0 IBBP, default VLCs
|
||||||
|
Spec {
|
||||||
|
name: "m2v_cif_ibbp",
|
||||||
|
lavfi: "testsrc2=size=352x288:rate=25",
|
||||||
|
frames: 30,
|
||||||
|
enc: &["-c:v", "mpeg2video", "-bf", "2", "-g", "12", "-b:v", "3M"],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
// interlaced (frame pictures, field MC + field DCT), top field first, alternate scan
|
||||||
|
Spec {
|
||||||
|
name: "m2v_576i_tff",
|
||||||
|
lavfi: "testsrc2=size=720x576:rate=50,tinterlace=mode=interleave_top,setfield=tff",
|
||||||
|
frames: 16,
|
||||||
|
enc: &["-c:v", "mpeg2video", "-flags", "+ilme+ildct", "-alternate_scan", "1", "-bf", "2", "-g", "8", "-b:v", "6M"],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
// bottom field first, intra VLC table one, non-linear quantiser, 10-bit DC
|
||||||
|
Spec {
|
||||||
|
name: "m2v_480i_bff_vlc1",
|
||||||
|
lavfi: "testsrc2=size=720x480:rate=60000/1001,tinterlace=mode=interleave_bottom,setfield=bff",
|
||||||
|
frames: 12,
|
||||||
|
enc: &[
|
||||||
|
"-c:v",
|
||||||
|
"mpeg2video",
|
||||||
|
"-flags",
|
||||||
|
"+ilme+ildct",
|
||||||
|
"-intra_vlc",
|
||||||
|
"1",
|
||||||
|
"-non_linear_quant",
|
||||||
|
"1",
|
||||||
|
"-qmax",
|
||||||
|
"28",
|
||||||
|
"-dc",
|
||||||
|
"10",
|
||||||
|
"-bf",
|
||||||
|
"1",
|
||||||
|
"-g",
|
||||||
|
"6",
|
||||||
|
"-b:v",
|
||||||
|
"8M",
|
||||||
|
],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
// 4:2:2 intra-only, IMX / D-10 style (720x608, 9-bit... DC precision 10, low delay)
|
||||||
|
Spec {
|
||||||
|
name: "m2v_imx_422_intra",
|
||||||
|
lavfi: "testsrc2=size=720x608:rate=25",
|
||||||
|
frames: 6,
|
||||||
|
enc: &[
|
||||||
|
"-c:v",
|
||||||
|
"mpeg2video",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv422p",
|
||||||
|
"-g",
|
||||||
|
"1",
|
||||||
|
"-intra_vlc",
|
||||||
|
"1",
|
||||||
|
"-non_linear_quant",
|
||||||
|
"1",
|
||||||
|
"-dc",
|
||||||
|
"10",
|
||||||
|
"-flags",
|
||||||
|
"+ildct+low_delay",
|
||||||
|
"-qmin",
|
||||||
|
"1",
|
||||||
|
"-qmax",
|
||||||
|
"3",
|
||||||
|
"-b:v",
|
||||||
|
"30M",
|
||||||
|
"-minrate",
|
||||||
|
"30M",
|
||||||
|
"-maxrate",
|
||||||
|
"30M",
|
||||||
|
"-bufsize",
|
||||||
|
"1200000",
|
||||||
|
],
|
||||||
|
pix_fmt: "yuv422p",
|
||||||
|
},
|
||||||
|
// 4:2:2 long GOP interlaced HD (XDCAM HD422 style)
|
||||||
|
Spec {
|
||||||
|
name: "m2v_hd422_longgop",
|
||||||
|
lavfi: "testsrc2=size=1920x1080:rate=50,tinterlace=mode=interleave_top,setfield=tff",
|
||||||
|
frames: 8,
|
||||||
|
enc: &["-c:v", "mpeg2video", "-pix_fmt", "yuv422p", "-flags", "+ilme+ildct", "-bf", "2", "-g", "6", "-b:v", "50M"],
|
||||||
|
pix_fmt: "yuv422p",
|
||||||
|
},
|
||||||
|
// low quantiser: large levels (escape codes), custom quantiser matrices
|
||||||
|
Spec {
|
||||||
|
name: "m2v_escapes_qmatrix",
|
||||||
|
lavfi: "mandelbrot=size=320x240:rate=25",
|
||||||
|
frames: 10,
|
||||||
|
enc: &[
|
||||||
|
"-c:v",
|
||||||
|
"mpeg2video",
|
||||||
|
"-qscale:v",
|
||||||
|
"1",
|
||||||
|
"-qmin",
|
||||||
|
"1",
|
||||||
|
"-bf",
|
||||||
|
"2",
|
||||||
|
"-g",
|
||||||
|
"5",
|
||||||
|
"-intra_matrix",
|
||||||
|
"8,16,16,16,17,18,21,24,16,16,16,16,17,19,22,25,16,16,17,18,20,22,25,29,16,16,18,21,24,27,31,36,17,17,20,24,30,35,41,47,18,19,22,27,35,44,54,65,21,22,25,31,41,54,70,88,24,25,29,36,47,65,88,115",
|
||||||
|
"-inter_matrix",
|
||||||
|
"18,18,18,18,19,21,23,27,18,18,18,18,19,21,24,29,18,18,19,20,22,24,28,32,18,18,20,24,27,30,35,40,19,19,22,27,33,39,46,53,21,21,24,30,39,50,61,73,23,24,28,35,46,61,79,98,27,29,32,40,53,73,98,129",
|
||||||
|
],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
// no B pictures
|
||||||
|
Spec { name: "m2v_ipp", lavfi: "testsrc2=size=176x144:rate=30", frames: 20, enc: &["-c:v", "mpeg2video", "-bf", "0", "-g", "10"], pix_fmt: "yuv420p" },
|
||||||
|
// MPEG-1 IBBP, and MPEG-1 at q 1 (8/16-bit escapes)
|
||||||
|
Spec {
|
||||||
|
name: "m1v_sif",
|
||||||
|
lavfi: "testsrc2=size=352x240:rate=30000/1001",
|
||||||
|
frames: 24,
|
||||||
|
enc: &["-c:v", "mpeg1video", "-bf", "2", "-g", "12", "-b:v", "1500k"],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
Spec {
|
||||||
|
name: "m1v_q1",
|
||||||
|
lavfi: "mandelbrot=size=192x144:rate=25",
|
||||||
|
frames: 8,
|
||||||
|
enc: &["-c:v", "mpeg1video", "-qscale:v", "1", "-qmin", "1", "-bf", "1", "-g", "4"],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/// 1080i 4:2:0 (decode benchmark; Main@High).
|
||||||
|
pub const HD1080I: Spec = Spec {
|
||||||
|
name: "m2v_1080i",
|
||||||
|
lavfi: "testsrc2=size=1920x1080:rate=50,tinterlace=mode=interleave_top,setfield=tff",
|
||||||
|
frames: 25,
|
||||||
|
enc: &["-c:v", "mpeg2video", "-flags", "+ilme+ildct", "-bf", "2", "-g", "12", "-b:v", "25M", "-maxrate", "25M", "-bufsize", "9M"],
|
||||||
|
pix_fmt: "yuv420p",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tolerance against ffmpeg (whose integer IDCT differs from our IEEE 1180-accurate one, and the
|
||||||
|
/// difference propagates through prediction): per frame max |Δ| ≤ 4 and PSNR ≥ 58 dB; overall
|
||||||
|
/// at most 3 % of samples differ at all.
|
||||||
|
pub const TOL_MAX: u8 = 4;
|
||||||
|
pub const MIN_PSNR: f64 = 58.0;
|
||||||
|
pub const MAX_DIFFERING: f64 = 0.03;
|
||||||
|
|
||||||
|
/// Per displayed frame: (pict_type, interlaced_frame, top_field_first) from ffprobe.
|
||||||
|
fn ffprobe_frames(file: &std::path::Path) -> Option<Vec<(String, bool, bool)>> {
|
||||||
|
let fp = filmcraft_testkit::ffprobe()?;
|
||||||
|
let o = std::process::Command::new(fp)
|
||||||
|
.args(["-v", "error", "-select_streams", "v:0", "-show_entries", "frame=pict_type,interlaced_frame,top_field_first", "-of", "csv=p=0"])
|
||||||
|
.arg(file)
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
Some(
|
||||||
|
String::from_utf8_lossy(&o.stdout)
|
||||||
|
.lines()
|
||||||
|
.filter(|l| !l.is_empty())
|
||||||
|
.map(|l| {
|
||||||
|
let v: Vec<&str> = l.split(',').collect();
|
||||||
|
(v[0].to_string(), v.get(1) == Some(&"1"), v.get(2) == Some(&"1"))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(spec: &Spec) {
|
||||||
|
let ff = filmcraft_testkit::require_ffmpeg!();
|
||||||
|
let f = make(&ff, spec).unwrap_or_else(|| panic!("could not generate {}", spec.name));
|
||||||
|
let es = std::fs::read(&f).unwrap();
|
||||||
|
let (pics, dec) = decode_es(&es, true);
|
||||||
|
assert_eq!(dec.errors(), 0, "{}: slice errors ({:?})", spec.name, dec.last_error());
|
||||||
|
let raw = ffmpeg_frames(&ff, &f, spec.pix_fmt);
|
||||||
|
let stats = compare(&pics, &raw);
|
||||||
|
let mut all = Stats::default();
|
||||||
|
for (i, s) in stats.iter().enumerate() {
|
||||||
|
all.merge(s);
|
||||||
|
assert!(s.max <= TOL_MAX && s.psnr() >= MIN_PSNR, "{} frame {i} ({:?}): max {} psnr {:.2}", spec.name, pics[i].picture_type, s.max, s.psnr());
|
||||||
|
}
|
||||||
|
assert!((all.diff as f64) / (all.n as f64) <= MAX_DIFFERING, "{}: {} of {} samples differ", spec.name, all.diff, all.n);
|
||||||
|
// display order: picture types, interlacing and field order as ffprobe reports them
|
||||||
|
if let Some(fr) = ffprobe_frames(&f) {
|
||||||
|
assert_eq!(fr.len(), pics.len());
|
||||||
|
for (i, (p, (t, il, tff))) in pics.iter().zip(&fr).enumerate() {
|
||||||
|
assert_eq!(format!("{:?}", p.picture_type), *t, "{} frame {i} type", spec.name);
|
||||||
|
assert_eq!(!p.progressive_frame, *il, "{} frame {i} interlaced", spec.name);
|
||||||
|
if *il {
|
||||||
|
assert_eq!(p.top_field_first, *tff, "{} frame {i} field order", spec.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{}: {} frames, {} ({}), max diff {}, {:.4}% samples differ, PSNR {:.2} dB",
|
||||||
|
spec.name,
|
||||||
|
pics.len(),
|
||||||
|
pics[0].info.codec_name(),
|
||||||
|
pix_fmt_of(pics[0].chroma),
|
||||||
|
all.max,
|
||||||
|
100.0 * all.diff as f64 / all.n as f64,
|
||||||
|
all.psnr()
|
||||||
|
);
|
||||||
|
// the single-threaded decode is identical
|
||||||
|
let (single, _) = decode_es(&es, false);
|
||||||
|
assert!(single.iter().zip(&pics).all(|(a, b)| a.y == b.y && a.cb == b.cb && a.cr == b.cr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ffmpeg_fixtures_match() {
|
||||||
|
for s in SPECS {
|
||||||
|
check(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hd_1080i_matches() {
|
||||||
|
check(&HD1080I);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn generate_fixtures() {
|
||||||
|
let Some(ff) = filmcraft_testkit::ffmpeg_or_skip("mpeg2v fixtures") else { return };
|
||||||
|
for s in SPECS.iter().chain([&HD1080I]) {
|
||||||
|
filmcraft_testkit::fixtures::generate_and_report(&format!("mpeg2v/{}", s.name), &[path(s)], || make(&ff, s));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "filmcraft-mpegts"
|
||||||
|
description = "Clean-room MPEG-2 Systems (ISO/IEC 13818-1) demuxer: Transport Stream (188-byte, 192-byte M2TS) and Program Stream (MPEG-1/2 PS, VOB, MOD)"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
filmcraft-testkit = { workspace = true }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
//! MPEG-2 Systems demuxer (work in progress).
|
||||||
@@ -31,6 +31,9 @@ const LAYERS: &[(&str, u8)] = &[
|
|||||||
("isobmff", 0),
|
("isobmff", 0),
|
||||||
("matroska", 0),
|
("matroska", 0),
|
||||||
("mxf", 0),
|
("mxf", 0),
|
||||||
|
("mpegts", 0),
|
||||||
|
("mpeg2v", 0),
|
||||||
|
("ac3", 0),
|
||||||
("ogg", 0),
|
("ogg", 0),
|
||||||
("riff", 0),
|
("riff", 0),
|
||||||
("h264", 0),
|
("h264", 0),
|
||||||
|
|||||||
Reference in New Issue
Block a user