use crate::frame_header::{FrameHeader, FrameSize, FrameType, ALL_FRAMES_PUB, PRIMARY_REF_NONE};
use crate::sequence_header::{
ColorConfig, OperatingPoint, SequenceHeader, CP_UNSPECIFIED, CSP_UNKNOWN, MC_UNSPECIFIED,
SELECT_INTEGER_MV, SELECT_SCREEN_CONTENT_TOOLS, TC_UNSPECIFIED,
};
use crate::tile_info::TileInfo;
use crate::uncompressed_header_tail::{
CdefParams, DeltaLfParams, DeltaQParams, FilmGrainParams, FrameRestorationType,
GlobalMotionParams, LoopFilterParams, LrParams, QuantizationParams, SegmentationParams, TxMode,
};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChromaFormat {
Monochrome,
Yuv420,
Yuv422,
Yuv444,
}
impl ChromaFormat {
#[must_use]
pub fn subsampling(self) -> (u8, u8) {
match self {
ChromaFormat::Monochrome | ChromaFormat::Yuv420 => (1, 1),
ChromaFormat::Yuv422 => (1, 0),
ChromaFormat::Yuv444 => (0, 0),
}
}
#[must_use]
pub fn num_planes(self) -> u8 {
match self {
ChromaFormat::Monochrome => 1,
_ => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct YuvFrame {
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub format: ChromaFormat,
pub y: Vec<u16>,
pub u: Vec<u16>,
pub v: Vec<u16>,
}
impl YuvFrame {
#[must_use]
pub fn filled(width: u32, height: u32, bit_depth: u8, format: ChromaFormat, fill: u16) -> Self {
let (ssx, ssy) = format.subsampling();
let (cw, ch) = if format == ChromaFormat::Monochrome {
(0, 0)
} else {
(
(width + u32::from(ssx)) >> ssx,
(height + u32::from(ssy)) >> ssy,
)
};
Self {
width,
height,
bit_depth,
format,
y: vec![fill; (width * height) as usize],
u: vec![fill; (cw * ch) as usize],
v: vec![fill; (cw * ch) as usize],
}
}
#[must_use]
pub fn from_yuv420_8bit(input: &Yuv420Frame) -> Self {
let widen = |p: &[u8]| p.iter().map(|&s| u16::from(s)).collect::<Vec<u16>>();
Self {
width: input.width,
height: input.height,
bit_depth: 8,
format: ChromaFormat::Yuv420,
y: widen(&input.y),
u: widen(&input.u),
v: widen(&input.v),
}
}
#[must_use]
pub fn chroma_width(&self) -> u32 {
if self.format == ChromaFormat::Monochrome {
0
} else {
let ssx = u32::from(self.format.subsampling().0);
(self.width + ssx) >> ssx
}
}
#[must_use]
pub fn chroma_height(&self) -> u32 {
if self.format == ChromaFormat::Monochrome {
0
} else {
let ssy = u32::from(self.format.subsampling().1);
(self.height + ssy) >> ssy
}
}
pub fn validate_any_extent(&self) -> Result<(), Error> {
if self.width < 1
|| self.height < 1
|| self.width > crate::encoder::key_frame::KEY_FRAME_MAX_DIM
|| self.height > crate::encoder::key_frame::KEY_FRAME_MAX_DIM
{
return Err(Error::PartitionWalkOutOfRange);
}
self.validate_planes()
}
#[must_use]
pub fn padded_to_mi_grid(&self) -> Self {
let pw = (self.width + 7) & !7;
let ph = (self.height + 7) & !7;
if pw == self.width && ph == self.height {
return self.clone();
}
let pad = |src: &[u16], sw: u32, sh: u32, dw: u32, dh: u32| -> Vec<u16> {
if sw == 0 || sh == 0 {
return Vec::new();
}
let (sw, sh, dw, dh) = (sw as usize, sh as usize, dw as usize, dh as usize);
let mut out = vec![0u16; dw * dh];
for y in 0..dh {
let sy = y.min(sh - 1);
let row = &src[sy * sw..sy * sw + sw];
let dst = &mut out[y * dw..y * dw + dw];
dst[..sw].copy_from_slice(row);
let last = row[sw - 1];
for s in &mut dst[sw..] {
*s = last;
}
}
out
};
let padded_geom = Self::filled(pw, ph, self.bit_depth, self.format, 0);
let (pcw, pch) = (padded_geom.chroma_width(), padded_geom.chroma_height());
Self {
width: pw,
height: ph,
bit_depth: self.bit_depth,
format: self.format,
y: pad(&self.y, self.width, self.height, pw, ph),
u: pad(&self.u, self.chroma_width(), self.chroma_height(), pcw, pch),
v: pad(&self.v, self.chroma_width(), self.chroma_height(), pcw, pch),
}
}
fn validate_planes(&self) -> Result<(), Error> {
if !matches!(self.bit_depth, 8 | 10 | 12) {
return Err(Error::PartitionWalkOutOfRange);
}
let expected_y = (self.width * self.height) as usize;
let expected_uv = (self.chroma_width() * self.chroma_height()) as usize;
if self.y.len() != expected_y || self.u.len() != expected_uv || self.v.len() != expected_uv
{
return Err(Error::PartitionWalkOutOfRange);
}
let ceil = 1u16 << self.bit_depth;
let in_range = |p: &[u16]| p.iter().all(|&s| s < ceil);
if !in_range(&self.y) || !in_range(&self.u) || !in_range(&self.v) {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
pub fn validate(&self) -> Result<(), Error> {
if self.width < 8
|| self.height < 8
|| self.width > crate::encoder::key_frame::KEY_FRAME_MAX_DIM
|| self.height > crate::encoder::key_frame::KEY_FRAME_MAX_DIM
|| self.width % 8 != 0
|| self.height % 8 != 0
{
return Err(Error::PartitionWalkOutOfRange);
}
self.validate_planes()
}
}
pub fn elect_seq_profile(bit_depth: u8, format: ChromaFormat) -> Result<u8, Error> {
match (bit_depth, format) {
(12, _) => Ok(2),
(8 | 10, ChromaFormat::Yuv422) => Ok(2),
(8 | 10, ChromaFormat::Yuv444) => Ok(1),
(8 | 10, ChromaFormat::Yuv420 | ChromaFormat::Monochrome) => Ok(0),
_ => Err(Error::PartitionWalkOutOfRange),
}
}
#[must_use]
pub fn color_config_for(bit_depth: u8, format: ChromaFormat) -> ColorConfig {
let (ssx, ssy) = format.subsampling();
ColorConfig {
high_bitdepth: bit_depth >= 10,
twelve_bit: bit_depth == 12,
bit_depth,
mono_chrome: format == ChromaFormat::Monochrome,
num_planes: format.num_planes(),
color_description_present_flag: false,
color_primaries: crate::sequence_header::CP_UNSPECIFIED,
transfer_characteristics: crate::sequence_header::TC_UNSPECIFIED,
matrix_coefficients: crate::sequence_header::MC_UNSPECIFIED,
color_range: false,
subsampling_x: ssx == 1,
subsampling_y: ssy == 1,
chroma_sample_position: CSP_UNKNOWN,
separate_uv_delta_q: false,
}
}
pub fn build_intra_only_seq_yuv(
max_width: u32,
max_height: u32,
bit_depth: u8,
format: ChromaFormat,
) -> Result<SequenceHeader, Error> {
let mut seq = build_intra_only_yuv420_8bit_seq(max_width, max_height);
seq.seq_profile = elect_seq_profile(bit_depth, format)?;
seq.color_config = color_config_for(bit_depth, format);
Ok(seq)
}
pub const MIN_DIM: u32 = 8;
pub const MAX_DIM: u32 = 64;
pub const SB_SIZE4_64: u32 = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Yuv420Frame {
pub width: u32,
pub height: u32,
pub y: Vec<u8>,
pub u: Vec<u8>,
pub v: Vec<u8>,
}
impl Yuv420Frame {
#[must_use]
pub fn filled(width: u32, height: u32, fill: u8) -> Self {
let cw = width.div_ceil(2);
let ch = height.div_ceil(2);
Self {
width,
height,
y: vec![fill; (width * height) as usize],
u: vec![fill; (cw * ch) as usize],
v: vec![fill; (cw * ch) as usize],
}
}
#[must_use]
pub fn chroma_width(&self) -> u32 {
self.width.div_ceil(2)
}
#[must_use]
pub fn chroma_height(&self) -> u32 {
self.height.div_ceil(2)
}
pub fn validate(&self) -> Result<(), Error> {
if self.width < MIN_DIM
|| self.height < MIN_DIM
|| self.width > MAX_DIM
|| self.height > MAX_DIM
{
return Err(Error::PartitionWalkOutOfRange);
}
if self.width % MIN_DIM != 0 || self.height % MIN_DIM != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let expected_y = (self.width * self.height) as usize;
let expected_uv = (self.chroma_width() * self.chroma_height()) as usize;
if self.y.len() != expected_y || self.u.len() != expected_uv || self.v.len() != expected_uv
{
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
}
pub const ENCODER_ORDER_HINT_BITS: u8 = 7;
#[must_use]
pub fn build_intra_only_yuv420_8bit_seq(max_width: u32, max_height: u32) -> SequenceHeader {
debug_assert!((1..=0xFFFF).contains(&max_width));
debug_assert!((1..=0xFFFF).contains(&max_height));
let max_w_minus_1 = max_width - 1;
let max_h_minus_1 = max_height - 1;
let bits_for = |v: u32| -> u8 {
if v == 0 {
0
} else {
(32 - v.leading_zeros() - 1) as u8
}
};
let frame_width_bits_minus_1 = bits_for(max_w_minus_1);
let frame_height_bits_minus_1 = bits_for(max_h_minus_1);
SequenceHeader {
seq_profile: 0,
still_picture: false,
reduced_still_picture_header: false,
timing_info_present_flag: false,
timing_info: None,
decoder_model_info_present_flag: false,
decoder_model_info: None,
initial_display_delay_present_flag: false,
operating_points_cnt_minus_1: 0,
operating_points: vec![OperatingPoint {
operating_point_idc: 0,
seq_level_idx: 0,
seq_tier: 0,
decoder_model_present_for_this_op: false,
operating_parameters_info: None,
initial_display_delay_present_for_this_op: false,
initial_display_delay_minus_1: None,
}],
frame_width_bits_minus_1,
frame_height_bits_minus_1,
max_frame_width_minus_1: max_w_minus_1,
max_frame_height_minus_1: max_h_minus_1,
frame_id_numbers_present_flag: false,
delta_frame_id_length_minus_2: 0,
additional_frame_id_length_minus_1: 0,
use_128x128_superblock: false,
enable_filter_intra: false,
enable_intra_edge_filter: false,
enable_interintra_compound: true,
enable_masked_compound: true,
enable_warped_motion: true,
enable_dual_filter: false,
enable_order_hint: true,
enable_jnt_comp: true,
enable_ref_frame_mvs: true,
seq_force_screen_content_tools: SELECT_SCREEN_CONTENT_TOOLS,
seq_force_integer_mv: SELECT_INTEGER_MV,
order_hint_bits: ENCODER_ORDER_HINT_BITS,
enable_superres: false,
enable_cdef: true,
enable_restoration: true,
color_config: ColorConfig {
high_bitdepth: false,
twelve_bit: false,
bit_depth: 8,
mono_chrome: false,
num_planes: 3,
color_description_present_flag: false,
color_primaries: CP_UNSPECIFIED,
transfer_characteristics: TC_UNSPECIFIED,
matrix_coefficients: MC_UNSPECIFIED,
color_range: false,
subsampling_x: true,
subsampling_y: true,
chroma_sample_position: CSP_UNKNOWN,
separate_uv_delta_q: false,
},
film_grain_params_present: false,
bits_consumed: 0,
}
}
#[must_use]
pub fn build_intra_only_yuv420_8bit_fh(
seq: &SequenceHeader,
width: u32,
height: u32,
) -> FrameHeader {
build_intra_only_yuv420_8bit_fh_with_q(seq, width, height, 0)
}
#[must_use]
pub fn build_intra_only_yuv420_8bit_fh_with_q(
seq: &SequenceHeader,
width: u32,
height: u32,
base_q_idx: u8,
) -> FrameHeader {
let fs = FrameSize {
frame_width: width,
frame_height: height,
render_width: width,
render_height: height,
superres_denom: 8, upscaled_width: width,
mi_cols: 2 * ((width + 7) >> 3),
mi_rows: 2 * ((height + 7) >> 3),
use_superres: false,
coded_denom: 0,
render_and_frame_size_different: false,
};
let ti = TileInfo {
uniform_tile_spacing_flag: true,
tile_cols: 1,
tile_rows: 1,
tile_cols_log2: 0,
tile_rows_log2: 0,
context_update_tile_id: 0,
tile_size_bytes: 1,
mi_col_starts: vec![0, fs.mi_cols],
mi_row_starts: vec![0, fs.mi_rows],
};
let qp = QuantizationParams {
base_q_idx,
delta_q_y_dc: 0,
diff_uv_delta: false,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
};
FrameHeader {
show_existing_frame: false,
frame_to_show_map_idx: None,
display_frame_id: None,
frame_type: FrameType::Key,
frame_is_intra: true,
show_frame: true,
showable_frame: false,
error_resilient_mode: true,
disable_cdf_update: false,
allow_screen_content_tools: seq.seq_force_screen_content_tools != 0,
force_integer_mv: true,
current_frame_id: 0,
frame_size_override_flag: false,
order_hint: 0,
primary_ref_frame: PRIMARY_REF_NONE,
refresh_frame_flags: ALL_FRAMES_PUB,
ref_order_hints: None,
frame_size: Some(fs),
allow_intrabc: false,
disable_frame_end_update_cdf: false,
tile_info: Some(ti),
quantization_params: Some(qp),
segmentation_params: Some(SegmentationParams::disabled()),
delta_q_params: Some(DeltaQParams::default()),
delta_lf_params: Some(DeltaLfParams::default()),
loop_filter_params: Some(LoopFilterParams::short_circuit()),
cdef_params: Some(if base_q_idx == 0 || !seq.enable_cdef {
CdefParams::short_circuit()
} else {
CdefParams {
short_circuited: false,
..CdefParams::short_circuit()
}
}),
lr_params: Some(LrParams {
frame_restoration_type: [FrameRestorationType::None; 3],
uses_lr: false,
uses_chroma_lr: false,
lr_unit_shift: 0,
lr_uv_shift: 0,
loop_restoration_size: [0; 3],
short_circuited: base_q_idx == 0 || !seq.enable_restoration,
}),
tx_mode: Some(if base_q_idx == 0 {
TxMode::Only4x4
} else {
TxMode::TxModeLargest
}),
reference_select: Some(false),
skip_mode_present: Some(false),
skip_mode_frame: None,
allow_warped_motion: Some(false),
reduced_tx_set: Some(false),
global_motion_params: Some(GlobalMotionParams::identity()),
film_grain_params: Some(FilmGrainParams::reset()),
inter_refs: None,
bits_consumed: 0,
}
}
#[must_use]
pub fn sb_grid_origins(mi_rows: u32, mi_cols: u32) -> Vec<(u32, u32)> {
let mut out = Vec::new();
let mut r = 0u32;
while r < mi_rows {
let mut c = 0u32;
while c < mi_cols {
out.push((r, c));
c += SB_SIZE4_64;
}
r += SB_SIZE4_64;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sequence_header::parse_sequence_header;
#[test]
fn profile_election_matches_the_6_4_1_table() {
assert_eq!(elect_seq_profile(8, ChromaFormat::Yuv420).unwrap(), 0);
assert_eq!(elect_seq_profile(10, ChromaFormat::Yuv420).unwrap(), 0);
assert_eq!(elect_seq_profile(8, ChromaFormat::Monochrome).unwrap(), 0);
assert_eq!(elect_seq_profile(10, ChromaFormat::Monochrome).unwrap(), 0);
assert_eq!(elect_seq_profile(8, ChromaFormat::Yuv444).unwrap(), 1);
assert_eq!(elect_seq_profile(10, ChromaFormat::Yuv444).unwrap(), 1);
assert_eq!(elect_seq_profile(8, ChromaFormat::Yuv422).unwrap(), 2);
assert_eq!(elect_seq_profile(10, ChromaFormat::Yuv422).unwrap(), 2);
for fmt in [
ChromaFormat::Yuv420,
ChromaFormat::Yuv422,
ChromaFormat::Yuv444,
ChromaFormat::Monochrome,
] {
assert_eq!(elect_seq_profile(12, fmt).unwrap(), 2);
}
assert!(elect_seq_profile(9, ChromaFormat::Yuv420).is_err());
assert!(elect_seq_profile(16, ChromaFormat::Yuv444).is_err());
}
#[test]
fn general_seq_builder_round_trips_every_pairing() {
let pairings: &[(u8, ChromaFormat)] = &[
(8, ChromaFormat::Yuv420),
(10, ChromaFormat::Yuv420),
(12, ChromaFormat::Yuv420),
(8, ChromaFormat::Yuv422),
(10, ChromaFormat::Yuv422),
(12, ChromaFormat::Yuv422),
(8, ChromaFormat::Yuv444),
(10, ChromaFormat::Yuv444),
(12, ChromaFormat::Yuv444),
(8, ChromaFormat::Monochrome),
(10, ChromaFormat::Monochrome),
(12, ChromaFormat::Monochrome),
];
for &(bd, fmt) in pairings {
let seq = build_intra_only_seq_yuv(128, 96, bd, fmt).unwrap();
let payload = crate::encoder::sequence_obu::write_sequence_header_obu(&seq);
let parsed = parse_sequence_header(&payload).unwrap();
let (ssx, ssy) = fmt.subsampling();
assert_eq!(parsed.seq_profile, elect_seq_profile(bd, fmt).unwrap());
assert_eq!(parsed.color_config.bit_depth, bd, "{bd} {fmt:?}");
assert_eq!(
parsed.color_config.mono_chrome,
fmt == ChromaFormat::Monochrome
);
assert_eq!(parsed.color_config.num_planes, fmt.num_planes());
assert_eq!(parsed.color_config.subsampling_x, ssx == 1, "{bd} {fmt:?}");
assert_eq!(parsed.color_config.subsampling_y, ssy == 1, "{bd} {fmt:?}");
}
}
#[test]
fn yuv_frame_validation_rejects_bad_shapes() {
for fmt in [
ChromaFormat::Yuv420,
ChromaFormat::Yuv422,
ChromaFormat::Yuv444,
ChromaFormat::Monochrome,
] {
for bd in [8u8, 10, 12] {
let f = YuvFrame::filled(64, 32, bd, fmt, (1 << bd) - 1);
f.validate().unwrap_or_else(|e| {
panic!("filled({bd}, {fmt:?}) must validate: {e:?}");
});
}
}
let mut f = YuvFrame::filled(8, 8, 10, ChromaFormat::Yuv444, 0);
f.y[3] = 1 << 10;
assert!(f.validate().is_err());
let mut f = YuvFrame::filled(16, 16, 8, ChromaFormat::Yuv422, 0);
assert_eq!(f.u.len(), 8 * 16);
f.u.truncate(8 * 8);
assert!(f.validate().is_err());
let mut f = YuvFrame::filled(16, 16, 8, ChromaFormat::Monochrome, 0);
assert!(f.u.is_empty() && f.v.is_empty());
f.u = vec![0; 64];
assert!(f.validate().is_err());
let f = YuvFrame::filled(16, 16, 9, ChromaFormat::Yuv420, 0);
assert!(f.validate().is_err());
let f = YuvFrame::filled(12, 16, 8, ChromaFormat::Yuv420, 0);
assert!(f.validate().is_err());
}
#[test]
fn widening_from_yuv420_8bit_is_lossless() {
let mut base = Yuv420Frame::filled(16, 8, 0);
for (i, s) in base.y.iter_mut().enumerate() {
*s = (i % 251) as u8;
}
for (i, s) in base.u.iter_mut().enumerate() {
*s = (i % 249) as u8;
}
for (i, s) in base.v.iter_mut().enumerate() {
*s = (i % 247) as u8;
}
let wide = YuvFrame::from_yuv420_8bit(&base);
wide.validate().unwrap();
assert_eq!(wide.bit_depth, 8);
assert_eq!(wide.format, ChromaFormat::Yuv420);
assert!(wide.y.iter().zip(&base.y).all(|(&w, &b)| w == u16::from(b)));
assert!(wide.u.iter().zip(&base.u).all(|(&w, &b)| w == u16::from(b)));
assert!(wide.v.iter().zip(&base.v).all(|(&w, &b)| w == u16::from(b)));
}
}