use crate::encoder::inter_frame::{
encode_inter_frame_generic, GopFrameRecon, GopFrameReconYuv, InterFrameConfig, SavedMotionField,
};
use crate::encoder::rate_twin::RateModel;
use crate::encoder::tile_group_obu::parse_tile_group_obu_body;
use crate::encoder::yuv_frame::{
build_intra_only_yuv420_8bit_fh_with_q, build_intra_only_yuv420_8bit_seq, Yuv420Frame, YuvFrame,
};
use crate::frame_header::{parse_frame_header_with_refs, FrameHeader, RefInfo, PRIMARY_REF_NONE};
use crate::sequence_header::SequenceHeader;
use crate::Error;
#[derive(Debug, Clone)]
pub struct CameraFrameEncode {
pub seq: SequenceHeader,
pub fh: FrameHeader,
pub frame_header_payload: Vec<u8>,
pub coded_tile_data: Vec<u8>,
pub coded_tiles: Vec<Vec<u8>>,
pub recon: GopFrameRecon,
}
#[must_use]
pub fn build_camera_seq_yuv420_8bit(max_width: u32, max_height: u32) -> SequenceHeader {
let mut seq = build_intra_only_yuv420_8bit_seq(max_width, max_height);
seq.enable_filter_intra = true;
seq.enable_order_hint = false;
seq.order_hint_bits = 0;
seq.enable_jnt_comp = false;
seq.enable_ref_frame_mvs = false;
seq.enable_cdef = false;
seq.enable_restoration = false;
seq.enable_superres = false;
seq
}
pub fn encode_camera_frame_yuv420(
input: &Yuv420Frame,
anchor: &Yuv420Frame,
base_q_idx: u8,
) -> Result<CameraFrameEncode, Error> {
encode_camera_frame_yuv420_tiles(input, anchor, base_q_idx, 0)
}
pub fn encode_camera_frame_yuv420_tiles(
input: &Yuv420Frame,
anchor: &Yuv420Frame,
base_q_idx: u8,
tile_cols_log2: u32,
) -> Result<CameraFrameEncode, Error> {
encode_camera_frame_yuv420_tiles_qm(input, anchor, base_q_idx, tile_cols_log2, false)
}
pub fn encode_camera_frame_yuv420_tiles_qm(
input: &Yuv420Frame,
anchor: &Yuv420Frame,
base_q_idx: u8,
tile_cols_log2: u32,
qm: bool,
) -> Result<CameraFrameEncode, Error> {
if input.width != anchor.width
|| input.height != anchor.height
|| input.height == 0
|| input.height % 64 != 0
|| input.height > 4096
|| input.width == 0
|| input.width % 64 != 0
|| input.width > 4096
{
return Err(Error::PartitionWalkOutOfRange);
}
let wide = YuvFrame::from_yuv420_8bit(input);
let anchor_recon = GopFrameReconYuv {
y: anchor.y.iter().map(|&v| u16::from(v)).collect(),
u: anchor.u.iter().map(|&v| u16::from(v)).collect(),
v: anchor.v.iter().map(|&v| u16::from(v)).collect(),
};
let seq = build_camera_seq_yuv420_8bit(input.width, input.height);
let mi = {
let fh0 = build_intra_only_yuv420_8bit_fh_with_q(&seq, input.width, input.height, 0);
let fs0 = fh0.frame_size.expect("builder always sizes");
(fs0.mi_rows, fs0.mi_cols)
};
let mf_store: [SavedMotionField; 8] =
core::array::from_fn(|_| SavedMotionField::intra(mi.0, mi.1));
let cfg = InterFrameConfig {
order_hint: 0,
show_frame: true,
refresh_frame_flags: 0,
ref_frame_idx: [0u8; 7],
short_ref_signaling: false,
slot_hints: [0u32; 8],
single_refs: vec![1],
compound_pairs: Vec::new(),
refs: vec![&anchor_recon],
slot_to_plane: [0usize; 8],
primary_ref_frame: PRIMARY_REF_NONE,
primary_carry: None,
allow_temporal_seg: false,
alt_primaries: Vec::new(),
exact_mask: None,
auto_lossless: false,
seg_extras: None,
high_precision_mv: false,
delta_q: false,
cdef: false,
cdef_units: false,
lr: false,
freeze_cdfs: true,
tiles: (tile_cols_log2, {
let sb_rows = input.height / 64;
if sb_rows <= 1 {
0
} else {
32 - (sb_rows - 1).leading_zeros()
}
}),
tile_groups: 1,
collect_donor_cdfs: false,
elect_donor: false,
qm,
explicit_tiles: None,
film_grain: None,
s_frame: false,
error_resilient: false,
tile_spans: false,
superres: None,
superres_source: None,
ref_dims: Vec::new(),
};
let (obus, recon, _saved, _carry, _aux) = encode_inter_frame_generic(
&wide,
&seq,
base_q_idx,
&cfg,
&[],
&mf_store,
RateModel::Twin,
)?;
debug_assert_eq!(obus.len(), 1, "tile_groups = 1 packs one OBU_FRAME");
let obu = obus
.into_iter()
.next()
.ok_or(Error::PartitionWalkOutOfRange)?;
let fh = parse_frame_header_with_refs(&obu.body, &seq, &RefInfo::default())?;
let tg_offset = fh.bits_consumed.div_ceil(8);
if tg_offset > obu.body.len() {
return Err(Error::UnexpectedEnd);
}
let ti = fh
.tile_info
.as_ref()
.ok_or(Error::PartitionWalkOutOfRange)?;
let parsed = parse_tile_group_obu_body(
&obu.body[tg_offset..],
ti.tile_cols * ti.tile_rows,
ti.tile_cols_log2,
ti.tile_rows_log2,
u32::from(ti.tile_size_bytes),
)?;
let coded_tiles: Vec<Vec<u8>> = parsed.tiles.into_iter().map(|t| t.bytes).collect();
let coded_tile_data = coded_tiles
.first()
.ok_or(Error::PartitionWalkOutOfRange)?
.clone();
let frame_header_payload = crate::encoder::frame_obu::write_frame_header_obu(&fh, &seq);
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(CameraFrameEncode {
seq,
fh,
frame_header_payload,
coded_tile_data,
coded_tiles,
recon: GopFrameRecon {
y: narrow(recon.y),
u: narrow(recon.u),
v: narrow(recon.v),
},
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn camera_seq_satisfies_the_7_3_sequence_constraints() {
let seq = build_camera_seq_yuv420_8bit(128, 64);
assert!(!seq.enable_superres);
assert!(!seq.enable_order_hint);
assert!(!seq.still_picture);
assert!(!seq.film_grain_params_present);
assert!(!seq.timing_info_present_flag);
assert!(!seq.decoder_model_info_present_flag);
assert!(!seq.initial_display_delay_present_flag);
assert!(!seq.enable_restoration);
assert!(!seq.enable_cdef);
assert!(!seq.color_config.mono_chrome);
assert!(!seq.enable_jnt_comp);
assert!(!seq.enable_ref_frame_mvs);
}
#[test]
fn camera_geometry_envelope_is_enforced() {
let f = Yuv420Frame::filled(64, 64, 100);
let tall = Yuv420Frame::filled(64, 128, 100);
assert!(encode_camera_frame_yuv420(&f, &tall, 60).is_err());
let e = encode_camera_frame_yuv420(&tall, &tall, 60).expect("two-row camera frame");
assert_eq!(e.coded_tiles.len(), 2, "one tile per superblock row");
let odd_h = Yuv420Frame::filled(64, 96, 100);
assert!(encode_camera_frame_yuv420(&odd_h, &odd_h, 60).is_err());
let odd_w = Yuv420Frame::filled(96, 64, 100);
assert!(encode_camera_frame_yuv420(&odd_w, &odd_w, 60).is_err());
}
}