use crate::bitwriter::{BitWriter, leb128};
use crate::color::MasteringDisplay;
use crate::metadata::ContentLightLevel;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum ObuType {
SequenceHeader = 1,
TemporalDelimiter = 2,
FrameHeader = 3,
TileGroup = 4,
Metadata = 5,
Frame = 6,
}
pub(crate) fn wrap_obu(obu_type: ObuType, payload: &[u8]) -> Vec<u8> {
let mut header = BitWriter::new();
header.f(0, 1); header.f(obu_type as u32, 4); header.f(0, 1); header.f(1, 1); header.f(0, 1); let mut out = header.into_bytes(); out.extend_from_slice(&leb128(payload.len() as u64));
out.extend_from_slice(payload);
out
}
pub(crate) fn temporal_delimiter() -> Vec<u8> {
wrap_obu(ObuType::TemporalDelimiter, &[])
}
fn bits_for(v: u32) -> u8 {
let mut b = 1;
while (1u32 << b) <= v.saturating_sub(1) {
b += 1;
}
b
}
#[allow(unused)]
pub(crate) fn frame_header_lossless() -> Vec<u8> {
frame_header_lossless_tiled(1, 1)
}
pub(crate) fn frame_header_lossless_tiled(sb_cols: u32, sb_rows: u32) -> Vec<u8> {
let cols = if sb_cols > 1 { vec![false] } else { vec![] };
let rows = if sb_rows > 1 { vec![false] } else { vec![] };
frame_header_lossless_impl(&cols, &rows, 0, 0, false, false)
}
pub(crate) fn frame_header_lossless_multitile(
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Vec<u8> {
frame_header_lossless_impl(
cols_incr,
rows_incr,
tile_cols_log2,
tile_rows_log2,
false,
false,
)
}
pub(crate) fn frame_header_lossless_multitile_th(
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Vec<u8> {
frame_header_lossless_impl(
cols_incr,
rows_incr,
tile_cols_log2,
tile_rows_log2,
true,
false,
)
}
pub(crate) fn frame_header_lossless_mono_multitile(
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Vec<u8> {
frame_header_lossless_impl(
cols_incr,
rows_incr,
tile_cols_log2,
tile_rows_log2,
false,
true,
)
}
pub(crate) fn frame_header_lossless_mono_multitile_th(
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Vec<u8> {
frame_header_lossless_impl(
cols_incr,
rows_incr,
tile_cols_log2,
tile_rows_log2,
true,
true,
)
}
fn frame_header_lossless_impl(
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
trailing: bool,
mono: bool,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.flag(true); w.flag(true); w.flag(true); w.flag(false); w.flag(false); w.flag(true); for &b in cols_incr {
w.flag(b);
}
for &b in rows_incr {
w.flag(b);
}
if tile_cols_log2 + tile_rows_log2 > 0 {
w.f(0, (tile_rows_log2 + tile_cols_log2) as u8); w.f(3, 2); }
w.f(0, 8); w.flag(false); if !mono {
w.flag(false); w.flag(false); }
w.flag(false); w.flag(false); w.flag(false); if trailing {
w.trailing_bits(); }
w.into_bytes()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn frame_header_lossy_multitile(
base_q_idx: u8,
qm: crate::quant::QmLevels,
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
mono: bool,
aq: bool,
cdef: Option<&CdefParams>,
lr: Option<&LrParams>,
) -> Vec<u8> {
frame_header_lossy_impl(
base_q_idx,
qm,
cols_incr,
rows_incr,
false,
tile_cols_log2,
tile_rows_log2,
false,
mono,
aq,
cdef,
lr,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn frame_header_lossy_multitile_th(
base_q_idx: u8,
qm: crate::quant::QmLevels,
cols_incr: &[bool],
rows_incr: &[bool],
tile_cols_log2: u32,
tile_rows_log2: u32,
mono: bool,
aq: bool,
cdef: Option<&CdefParams>,
lr: Option<&LrParams>,
) -> Vec<u8> {
frame_header_lossy_impl(
base_q_idx,
qm,
cols_incr,
rows_incr,
false,
tile_cols_log2,
tile_rows_log2,
true,
mono,
aq,
cdef,
lr,
)
}
pub(crate) fn wrap_obu_frame_split(frame_header: &[u8], tile_group: &[u8]) -> Vec<u8> {
let mut out = wrap_obu(ObuType::FrameHeader, frame_header);
out.extend_from_slice(&wrap_obu(ObuType::TileGroup, tile_group));
out
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_filter_levels(base_q_idx: u8) -> (i32, i32) {
let q = base_q_idx as i32;
let lvl_y = (q / 8).clamp(0, 40);
let lvl_uv = if lvl_y > 0 {
(q / 10).max(1).clamp(0, 32)
} else {
0
};
(lvl_y, lvl_uv)
}
#[derive(Clone, Debug, Default)]
pub(crate) struct CdefParams {
pub bits: u8,
pub damping: u8,
pub strengths: Vec<(u8, u8, u8, u8)>,
}
fn write_cdef_params(w: &mut BitWriter, cdef: &CdefParams, mono: bool) {
w.f((cdef.damping as u32).saturating_sub(3), 2); w.f(cdef.bits as u32, 2); let n = 1usize << cdef.bits;
let map_sec = |s: u8| -> u32 {
debug_assert_ne!(s, 3, "secondary strength 3 is not codable");
let v = s as u32;
if v == 4 { 3 } else { v }
};
for i in 0..n {
let (yp, ys, up, us) = cdef.strengths.get(i).copied().unwrap_or((0, 0, 0, 0));
w.f(yp as u32, 4); w.f(map_sec(ys), 2); if !mono {
w.f(up as u32, 4); w.f(map_sec(us), 2); }
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct LrParams {
pub luma_wiener: bool,
}
fn write_lr_params(w: &mut BitWriter, lr: &LrParams, num_planes: usize) {
if lr.luma_wiener {
w.f(2, 2); } else {
w.f(0, 2); }
for _ in 1..num_planes {
w.f(0, 2);
}
if lr.luma_wiener {
w.f(0, 1); }
}
#[allow(clippy::too_many_arguments)]
fn frame_header_lossy_impl(
base_q_idx: u8,
qm: crate::quant::QmLevels,
cols_incr: &[bool],
rows_incr: &[bool],
disable_cdf_update: bool,
tile_cols_log2: u32,
tile_rows_log2: u32,
trailing: bool,
mono: bool,
aq: bool,
cdef: Option<&CdefParams>,
lr: Option<&LrParams>,
) -> Vec<u8> {
debug_assert!(base_q_idx != 0, "use frame_header_lossless() for q=0");
let mut w = BitWriter::new();
w.flag(disable_cdf_update); w.flag(true); w.flag(true); w.flag(false); w.flag(false); w.flag(true); for &b in cols_incr {
w.flag(b);
}
for &b in rows_incr {
w.flag(b);
}
if tile_cols_log2 + tile_rows_log2 > 0 {
w.f(0, (tile_rows_log2 + tile_cols_log2) as u8);
w.f(3, 2); }
w.f(base_q_idx as u32, 8); w.flag(false); if !mono {
let uv_dc = crate::quant::chroma_dc_delta(base_q_idx);
if uv_dc != 0 {
w.flag(true); w.f((uv_dc & 0x7f) as u32, 7); } else {
w.flag(false); }
w.flag(false); }
let using_qmatrix = qm.y < 15 || qm.u < 15 || qm.v < 15;
w.flag(using_qmatrix);
if using_qmatrix {
w.f(qm.y as u32, 4);
w.f(qm.u as u32, 4);
debug_assert_eq!(qm.v, qm.u);
}
w.flag(false); if aq {
w.flag(true); w.f(crate::coder::AQ_DELTA_Q_RES_LOG2 as u32, 2); w.flag(false); } else {
w.flag(false); }
let (lvl_y, lvl_uv) = loop_filter_levels(base_q_idx);
w.f(lvl_y as u32, 6); w.f(lvl_y as u32, 6); if lvl_y != 0 && !mono {
w.f(lvl_uv as u32, 6); w.f(lvl_uv as u32, 6); }
w.f(0, 3); w.flag(false);
let noop_cdef = CdefParams {
bits: 0,
damping: 3,
strengths: vec![(0, 0, 0, 0)],
};
let cdef = cdef.unwrap_or(&noop_cdef);
write_cdef_params(&mut w, cdef, mono);
let noop_lr = LrParams::default();
let lr = lr.unwrap_or(&noop_lr);
let num_planes = if mono { 1 } else { 3 };
write_lr_params(&mut w, lr, num_planes);
w.flag(true); w.flag(false); if trailing {
w.trailing_bits(); }
w.into_bytes()
}
pub(crate) fn wrap_obu_frame(frame_header: &[u8], tile_data: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(frame_header.len() + tile_data.len());
payload.extend_from_slice(frame_header);
payload.extend_from_slice(tile_data);
wrap_obu(ObuType::Frame, &payload)
}
pub(crate) fn sequence_header_cicp(
width: u32,
height: u32,
profile: u32,
bit_depth: u8,
color: Option<&crate::color::Cicp>,
enable_filter_intra: bool,
enable_intra_edge_filter: bool,
) -> Vec<u8> {
seq_header_ss(
width,
height,
profile,
bit_depth,
color,
0,
0,
enable_filter_intra,
enable_intra_edge_filter,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn sequence_header_cicp_ss(
width: u32,
height: u32,
profile: u32,
bit_depth: u8,
color: Option<&crate::color::Cicp>,
ss_x: u32,
ss_y: u32,
enable_filter_intra: bool,
enable_intra_edge_filter: bool,
) -> Vec<u8> {
seq_header_ss(
width,
height,
profile,
bit_depth,
color,
ss_x,
ss_y,
enable_filter_intra,
enable_intra_edge_filter,
)
}
pub(crate) fn sequence_header_mono(
width: u32,
height: u32,
bit_depth: u8,
full_range: bool,
enable_filter_intra: bool,
enable_intra_edge_filter: bool,
) -> Vec<u8> {
let profile: u32 = if bit_depth == 12 { 2 } else { 0 };
let mut w = BitWriter::new();
w.f(profile, 3); w.flag(true); w.flag(true); w.f(0, 5);
let wbits = bits_for(width);
let hbits = bits_for(height);
w.f((wbits - 1) as u32, 4); w.f((hbits - 1) as u32, 4); w.f(width - 1, wbits); w.f(height - 1, hbits);
w.flag(false); w.flag(enable_filter_intra);
w.flag(enable_intra_edge_filter);
w.flag(false); w.flag(true); w.flag(true);
let high_bitdepth = bit_depth > 8;
w.flag(high_bitdepth); if profile == 2 && high_bitdepth {
w.flag(bit_depth == 12); }
w.flag(true); w.flag(false); w.flag(full_range);
w.flag(false);
w.trailing_bits();
wrap_obu(ObuType::SequenceHeader, &w.into_bytes())
}
#[allow(clippy::too_many_arguments)]
fn seq_header_ss(
width: u32,
height: u32,
profile: u32,
bit_depth: u8,
color: Option<&crate::color::Cicp>,
ss_x: u32,
ss_y: u32,
enable_filter_intra: bool,
enable_intra_edge_filter: bool,
) -> Vec<u8> {
use crate::color::MatrixCoefficients;
let mut w = BitWriter::new();
w.f(profile, 3); w.flag(true); w.flag(true);
w.f(0, 5);
let wbits = bits_for(width);
let hbits = bits_for(height);
w.f((wbits - 1) as u32, 4); w.f((hbits - 1) as u32, 4); w.f(width - 1, wbits); w.f(height - 1, hbits);
w.flag(false); w.flag(enable_filter_intra);
w.flag(enable_intra_edge_filter);
w.flag(false); w.flag(true); w.flag(true);
let high_bitdepth = bit_depth > 8;
w.flag(high_bitdepth); if profile == 2 && high_bitdepth {
w.flag(bit_depth == 12); }
if profile != 1 {
w.flag(false); }
let is_identity = matches!(color, Some(c) if c.matrix == MatrixCoefficients::Identity);
if let Some(c) = color {
w.flag(true); w.f(c.primaries as u32, 8);
w.f(c.transfer as u32, 8);
w.f(c.matrix as u32, 8);
} else {
w.flag(false); }
if is_identity {
} else {
let full_range = color.map(|c| c.full_range).unwrap_or(false);
w.flag(full_range);
if profile == 2 && bit_depth == 12 {
w.flag(ss_x == 1); if ss_x == 1 {
w.flag(ss_y == 1); }
}
if ss_x == 1 && ss_y == 1 {
let csp = color.map(|c| c.chroma_sample_position as u32).unwrap_or(0);
w.f(csp, 2); }
}
w.flag(false);
w.flag(false);
w.trailing_bits();
wrap_obu(ObuType::SequenceHeader, &w.into_bytes())
}
pub(crate) mod metadata_type {
pub(crate) const HDR_CLL: u64 = 1;
pub(crate) const HDR_MDCV: u64 = 2;
pub(crate) const ITUT_T35: u64 = 4;
}
fn metadata_obu(metadata_type: u64, payload: &[u8]) -> Vec<u8> {
let mut body = leb128(metadata_type);
body.extend_from_slice(payload);
wrap_obu(ObuType::Metadata, &body)
}
pub(crate) fn metadata_hdr_cll(cll: &ContentLightLevel) -> Vec<u8> {
let mut w = BitWriter::new();
w.f(cll.max_content_light_level as u32, 16);
w.f(cll.max_pic_average_light_level as u32, 16);
w.trailing_bits();
metadata_obu(metadata_type::HDR_CLL, &w.into_bytes())
}
pub(crate) fn metadata_hdr_mdcv(m: &MasteringDisplay) -> Vec<u8> {
let mut w = BitWriter::new();
for (x, y) in m.primaries.iter() {
w.f(*x as u32, 16);
w.f(*y as u32, 16);
}
w.f(m.white_point.0 as u32, 16);
w.f(m.white_point.1 as u32, 16);
w.f(m.max_luminance, 32);
w.f(m.min_luminance, 32);
w.trailing_bits();
metadata_obu(metadata_type::HDR_MDCV, &w.into_bytes())
}
pub(crate) fn metadata_itut_t35(t35: &crate::color::ItutT35) -> Vec<u8> {
let mut body = Vec::new();
body.push(t35.country_code);
if t35.country_code == 0xFF {
body.push(t35.country_code_extension.unwrap_or(0));
}
body.extend_from_slice(&t35.payload);
body.push(0x80); metadata_obu(metadata_type::ITUT_T35, &body)
}
#[allow(unused)]
pub(crate) fn metadata_obus(
cll: Option<&ContentLightLevel>,
mdcv: Option<&MasteringDisplay>,
t35: &[crate::color::ItutT35],
) -> Vec<u8> {
let mut out = Vec::new();
if let Some(c) = cll {
out.extend_from_slice(&metadata_hdr_cll(c));
}
if let Some(m) = mdcv {
out.extend_from_slice(&metadata_hdr_mdcv(m));
}
for t in t35 {
out.extend_from_slice(&metadata_itut_t35(t));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::*;
#[test]
fn metadata_obus_well_formed() {
let obu_type = |b: &[u8]| (b[0] >> 3) & 0xF;
let cll = metadata_hdr_cll(&ContentLightLevel {
max_content_light_level: 1000,
max_pic_average_light_level: 400,
});
assert_eq!(obu_type(&cll), 5);
assert_eq!(cll[2], metadata_type::HDR_CLL as u8); assert_eq!(*cll.last().unwrap(), 0x80); assert_eq!(cll.len(), 8);
let mdcv = metadata_hdr_mdcv(&MasteringDisplay::from_floats(
[(0.708, 0.292), (0.170, 0.797), (0.131, 0.046)],
(0.3127, 0.3290),
1000.0,
0.005,
));
assert_eq!(obu_type(&mdcv), 5);
assert_eq!(mdcv[2], metadata_type::HDR_MDCV as u8);
assert_eq!(*mdcv.last().unwrap(), 0x80);
assert_eq!(mdcv.len(), 28);
let t35 = metadata_itut_t35(&ItutT35 {
country_code: 0xFF,
country_code_extension: Some(0x49),
payload: b"HDR10+".to_vec(),
});
assert_eq!(obu_type(&t35), 5);
assert_eq!(t35[2], metadata_type::ITUT_T35 as u8); assert_eq!(*t35.last().unwrap(), 0x80);
}
#[test]
fn obu_wrapping_roundtrips_size() {
let payload = vec![0xAA; 200];
let obu = wrap_obu(ObuType::Frame, &payload);
assert_eq!(obu[0] >> 3 & 0xf, ObuType::Frame as u8); assert_eq!(&obu[1..3], &leb128(200)[..]);
assert_eq!(obu.len(), 1 + 2 + 200);
}
#[test]
fn temporal_delimiter_is_two_bytes() {
assert_eq!(temporal_delimiter().len(), 2);
}
}