use crate::encoder::inter_frame::{
encode_inter_frame_generic, narrow_gop_8bit, EncodedGop, EncodedGopYuv, GopFrameReconYuv,
InterFrameConfig, RefSlotCarry, SavedMotionField,
};
use crate::encoder::ivf::{IvfWriter, FOURCC_AV01};
use crate::encoder::key_frame::encode_key_frame_yuv_seg_carry_tiles;
use crate::encoder::obu::{
build_temporal_unit, write_obu_with_size, ObuExtensionHeader, ObuHeader,
};
use crate::encoder::pyramid_gop::validate_gop_input;
use crate::encoder::rate_twin::RateModel;
use crate::encoder::sequence_obu::write_sequence_header_obu;
use crate::encoder::yuv_frame::{Yuv420Frame, YuvFrame};
use crate::frame_header::PRIMARY_REF_NONE;
use crate::obu::{ObuIter, ObuType};
use crate::sequence_header::OperatingPoint;
use crate::Error;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct TemporalLayeredGopYuv {
pub gop: EncodedGopYuv,
pub temporal_ids: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct TemporalLayeredGop {
pub gop: EncodedGop,
pub temporal_ids: Vec<u8>,
}
#[must_use]
pub fn temporal_layer_of(i: usize, layers: u8) -> u8 {
let period = 1usize << (layers - 1);
let m = i % period;
if m == 0 {
0
} else {
layers - 1 - (m.trailing_zeros() as u8)
}
}
fn layer_q_off(tid: u8) -> i32 {
match tid {
0 => 0,
1 => 4,
2 => 6,
_ => 8,
}
}
fn operating_points_for(base: OperatingPoint, layers: u8) -> Vec<OperatingPoint> {
(0..layers)
.map(|k| OperatingPoint {
operating_point_idc: 0x100 | ((1u16 << (layers - k)) - 1),
..base
})
.collect()
}
fn repack_tu_with_extension(tu: &[u8], temporal_id: u8, seq_payload: Option<&[u8]>) -> Vec<u8> {
let mut out = Vec::new();
for desc in ObuIter::new(tu) {
let desc = desc.expect("own temporal unit walks");
match desc.obu_type {
ObuType::TemporalDelimiter => {
write_obu_with_size(
&mut out,
&ObuHeader::new(ObuType::TemporalDelimiter),
desc.payload,
);
}
ObuType::SequenceHeader => {
let body: &[u8] = seq_payload.unwrap_or(desc.payload);
write_obu_with_size(&mut out, &ObuHeader::new(ObuType::SequenceHeader), body);
}
other => {
let header =
ObuHeader::new(other).with_extension(ObuExtensionHeader::new(temporal_id, 0));
write_obu_with_size(&mut out, &header, desc.payload);
}
}
}
out
}
pub fn encode_temporal_layered_gop_yuv_with_q(
frames: &[YuvFrame],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<TemporalLayeredGopYuv, Error> {
encode_temporal_layered_gop_yuv_with_q_tiles(frames, base_q_idx, temporal_layers, (0, 0), 1)
}
pub fn encode_temporal_layered_gop_yuv_with_q_tiles(
frames: &[YuvFrame],
base_q_idx: u8,
temporal_layers: u8,
tiles: (u32, u32),
tile_groups: u32,
) -> Result<TemporalLayeredGopYuv, Error> {
if !(2..=4).contains(&temporal_layers) {
return Err(Error::PartitionWalkOutOfRange);
}
let (width, height) = validate_gop_input(frames)?;
let n = frames.len();
let donor_armed = tiles != (0, 0);
let (key, key_carry) = encode_key_frame_yuv_seg_carry_tiles(
&frames[0],
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
tiles,
tile_groups,
None,
true,
true,
true,
donor_armed,
)?;
let mut seq = key.seq.clone();
seq.operating_points = operating_points_for(seq.operating_points[0], temporal_layers);
seq.operating_points_cnt_minus_1 = temporal_layers - 1;
let seq_payload = write_sequence_header_obu(&seq);
let key_tu = repack_tu_with_extension(&key.temporal_unit_bytes, 0, Some(&seq_payload));
let key_mi = {
let fs = key.fh.frame_size.as_ref().expect("KEY builder sizes");
(fs.mi_rows, fs.mi_cols)
};
let key_carry = Rc::new(key_carry);
let mut mf_store: [SavedMotionField; 8] =
core::array::from_fn(|_| SavedMotionField::intra(key_mi.0, key_mi.1));
let mut carry_store: [Rc<RefSlotCarry>; 8] = core::array::from_fn(|_| key_carry.clone());
let mut carry_tu: [usize; 8] = [0; 8];
let mut slot_hints = [0u32; 8];
let mut slot_display = [0usize; 8];
let mut recons: Vec<GopFrameReconYuv> = Vec::with_capacity(n);
recons.push(GopFrameReconYuv {
y: key.recon_y,
u: key.recon_u,
v: key.recon_v,
});
let mut temporal_units: Vec<Vec<u8>> = vec![key_tu];
let mut temporal_ids: Vec<u8> = vec![0];
#[allow(clippy::needless_range_loop)] for i in 1..n {
let tid = temporal_layer_of(i, temporal_layers);
let last_display = i - (1usize << (temporal_layers - 1 - tid));
let last_slot = slot_display
.iter()
.position(|&d| d == last_display)
.expect("dyadic ladder keeps every LAST target stored");
let golden_slot = 0usize;
let mut ref_frame_idx = [last_slot as u8; 7];
ref_frame_idx[3] = golden_slot as u8;
let singles: Vec<i8> = if slot_display[golden_slot] == last_display {
vec![1]
} else {
vec![1, 4]
};
let mut displays: Vec<usize> = Vec::new();
let mut slot_to_plane = [0usize; 8];
for (plane, &d) in slot_to_plane.iter_mut().zip(&slot_display) {
*plane = displays.iter().position(|&x| x == d).unwrap_or_else(|| {
displays.push(d);
displays.len() - 1
});
}
let refs: Vec<&GopFrameReconYuv> = displays.iter().map(|&d| &recons[d]).collect();
let last_carry = carry_store[last_slot].clone();
let alt_primaries: Vec<(u8, Option<&RefSlotCarry>)> = vec![(PRIMARY_REF_NONE, None)];
let q = if base_q_idx == 0 {
0
} else {
(i32::from(base_q_idx) + layer_q_off(tid)).clamp(1, 255) as u8
};
let cfg = InterFrameConfig {
order_hint: i as u32,
show_frame: true,
refresh_frame_flags: if tid + 1 < temporal_layers {
1u8 << tid
} else {
0
},
ref_frame_idx,
short_ref_signaling: false,
slot_hints,
single_refs: singles,
compound_pairs: Vec::new(),
refs,
slot_to_plane,
primary_ref_frame: 0,
primary_carry: Some(&*last_carry),
allow_temporal_seg: false,
alt_primaries,
exact_mask: None,
auto_lossless: false,
seg_extras: None,
high_precision_mv: true,
delta_q: true,
cdef: true,
cdef_units: true,
lr: true,
freeze_cdfs: false,
tiles,
tile_groups,
collect_donor_cdfs: donor_armed && tid + 1 < temporal_layers,
elect_donor: donor_armed,
explicit_tiles: None,
qm: true,
film_grain: None,
s_frame: false,
error_resilient: false,
tile_spans: false,
superres: None,
superres_source: None,
ref_dims: Vec::new(),
};
let (mut obus, recon, saved, carry, aux) =
encode_inter_frame_generic(&frames[i], &seq, q, &cfg, &[], &mf_store, RateModel::Twin)?;
if carry_store[last_slot].donor_cdfs.len() > 1 {
let consumed = carry_store[last_slot].clone();
let fixed_cdfs = match aux.donor_elected {
Some(t) => {
let span = consumed
.ctx_update_span
.expect("donor election only fires on multi-tile primaries");
crate::encoder::inter_frame::patch_ctx_update_in_tu(
&mut temporal_units[carry_tu[last_slot]],
span,
t,
);
consumed.donor_cdfs[t as usize].clone()
}
None => consumed.cdfs.clone(),
};
let fixed = Rc::new(RefSlotCarry {
cdfs: fixed_cdfs,
segment_ids: consumed.segment_ids.clone(),
mi_rows: consumed.mi_rows,
mi_cols: consumed.mi_cols,
gm_params: consumed.gm_params,
donor_cdfs: Vec::new(),
ctx_update_span: None,
});
for slot in carry_store.iter_mut() {
if Rc::ptr_eq(slot, &consumed) {
*slot = fixed.clone();
}
}
}
for obu in &mut obus {
obu.header.extension = Some(ObuExtensionHeader::new(tid, 0));
}
temporal_units.push(build_temporal_unit(None, &obus));
temporal_ids.push(tid);
recons.push(recon);
if tid + 1 < temporal_layers {
let s = usize::from(tid);
mf_store[s] = saved;
carry_store[s] = Rc::new(carry);
carry_tu[s] = i;
slot_hints[s] = i as u32;
slot_display[s] = i;
}
}
let mut ivf_bytes: Vec<u8> = Vec::new();
{
let cursor = std::io::Cursor::new(&mut ivf_bytes);
let mut iw = IvfWriter::new(cursor, FOURCC_AV01, width as u16, height as u16, 25, 1)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
for (idx, tu) in temporal_units.iter().enumerate() {
iw.write_frame(tu, idx as u64)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
}
iw.patch_frame_count()
.map_err(|_| Error::PartitionWalkOutOfRange)?;
}
Ok(TemporalLayeredGopYuv {
gop: EncodedGopYuv {
ivf_bytes,
temporal_units,
recon: recons,
seq,
},
temporal_ids,
})
}
#[derive(Debug, Clone)]
pub struct SpatialLayeredGopYuv {
pub ivf_bytes: Vec<u8>,
pub temporal_units: Vec<Vec<u8>>,
pub seq: crate::sequence_header::SequenceHeader,
pub layer_recons: Vec<Vec<GopFrameReconYuv>>,
pub layer_dims: Vec<(u32, u32)>,
pub temporal_ids: Vec<u8>,
pub operating_points: Vec<(u8, u8)>,
}
#[derive(Debug, Clone)]
pub struct SpatialLayeredGop {
pub ivf_bytes: Vec<u8>,
pub temporal_units: Vec<Vec<u8>>,
pub seq: crate::sequence_header::SequenceHeader,
pub layer_recons: Vec<Vec<crate::encoder::inter_frame::GopFrameRecon>>,
pub layer_dims: Vec<(u32, u32)>,
pub temporal_ids: Vec<u8>,
pub operating_points: Vec<(u8, u8)>,
}
fn spatial_operating_points_for(base: OperatingPoint, s_count: u8) -> Vec<OperatingPoint> {
(0..s_count)
.map(|k| OperatingPoint {
operating_point_idc: (((1u16 << (s_count - k)) - 1) << 8) | 1,
..base
})
.collect()
}
pub fn encode_spatial_layered_gop_yuv_with_q(
layers: &[Vec<YuvFrame>],
base_q_idx: u8,
) -> Result<SpatialLayeredGopYuv, Error> {
encode_spatial_layered_gop_yuv_with_q_tiles(layers, base_q_idx, None, 1)
}
pub fn encode_spatial_layered_gop_yuv_with_q_tiles(
layers: &[Vec<YuvFrame>],
base_q_idx: u8,
layer_tiles: Option<&[(u32, u32)]>,
tile_groups: u32,
) -> Result<SpatialLayeredGopYuv, Error> {
encode_spatial_layered_core(layers, base_q_idx, layer_tiles, tile_groups, 1, false)
}
pub fn encode_spatial_layered_gop_yuv_with_q_inter_layer(
layers: &[Vec<YuvFrame>],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<SpatialLayeredGopYuv, Error> {
if !(1..=4).contains(&temporal_layers) {
return Err(Error::PartitionWalkOutOfRange);
}
encode_spatial_layered_core(layers, base_q_idx, None, 1, temporal_layers, true)
}
pub fn encode_spatial_layered_gop_yuv420_with_q_inter_layer(
layers: &[Vec<Yuv420Frame>],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<SpatialLayeredGop, Error> {
let wide: Vec<Vec<YuvFrame>> = layers
.iter()
.map(|l| l.iter().map(YuvFrame::from_yuv420_8bit).collect())
.collect();
encode_spatial_layered_gop_yuv_with_q_inter_layer(&wide, base_q_idx, temporal_layers)
.map(narrow_spatial_gop)
}
pub fn encode_spatial_temporal_layered_gop_yuv_with_q(
layers: &[Vec<YuvFrame>],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<SpatialLayeredGopYuv, Error> {
if !(2..=4).contains(&temporal_layers) {
return Err(Error::PartitionWalkOutOfRange);
}
encode_spatial_layered_core(layers, base_q_idx, None, 1, temporal_layers, false)
}
pub fn encode_spatial_temporal_layered_gop_yuv420_with_q(
layers: &[Vec<Yuv420Frame>],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<SpatialLayeredGop, Error> {
let wide: Vec<Vec<YuvFrame>> = layers
.iter()
.map(|l| l.iter().map(YuvFrame::from_yuv420_8bit).collect())
.collect();
encode_spatial_temporal_layered_gop_yuv_with_q(&wide, base_q_idx, temporal_layers)
.map(narrow_spatial_gop)
}
fn encode_spatial_layered_core(
layers: &[Vec<YuvFrame>],
base_q_idx: u8,
layer_tiles: Option<&[(u32, u32)]>,
tile_groups: u32,
temporal_layers: u8,
inter_layer: bool,
) -> Result<SpatialLayeredGopYuv, Error> {
let s_count = layers.len();
let budget: usize = if temporal_layers <= 1 {
2
} else {
8 / s_count.max(2)
};
if temporal_layers > 1 && usize::from(temporal_layers) - 1 > budget {
return Err(Error::PartitionWalkOutOfRange);
}
if !(2..=4).contains(&s_count) {
return Err(Error::PartitionWalkOutOfRange);
}
if layer_tiles.is_some_and(|t| t.len() != s_count) {
return Err(Error::PartitionWalkOutOfRange);
}
let tiles_of = |s: usize| layer_tiles.map_or((0, 0), |t| t[s]);
let n = layers[0].len();
if n == 0 || n > crate::encoder::inter_frame::GOP_MAX_FRAMES {
return Err(Error::PartitionWalkOutOfRange);
}
let (bit_depth, format) = (layers[0][0].bit_depth, layers[0][0].format);
for layer in layers {
if layer.len() != n {
return Err(Error::PartitionWalkOutOfRange);
}
let (w, h) = (layer[0].width, layer[0].height);
for f in layer {
if f.width != w || f.height != h || f.bit_depth != bit_depth || f.format != format {
return Err(Error::PartitionWalkOutOfRange);
}
f.validate()?;
}
}
let (top_w, top_h) = (layers[s_count - 1][0].width, layers[s_count - 1][0].height);
if layers
.iter()
.any(|l| l[0].width > top_w || l[0].height > top_h)
{
return Err(Error::PartitionWalkOutOfRange);
}
let mut layer_sr: Vec<Option<(u32, u32)>> = vec![None; s_count];
if base_q_idx > 0 && tile_groups <= 1 {
for (s, layer) in layers.iter().enumerate() {
let f0 = &layer[0];
if tiles_of(s) != (0, 0)
|| !crate::encoder::superres_elect::superres_arm_allowed(
base_q_idx,
f0.width as usize,
f0.height as usize,
)
|| !crate::encoder::superres_elect::superres_probe(f0)
{
continue;
}
let (pre, _) = crate::encoder::key_frame::encode_key_frame_yuv_full(
f0,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
&crate::encoder::key_frame::KeyExtras {
delta_q: true,
qm: true,
superres_elect: true,
..Default::default()
},
)?;
if let Some(fs) = pre.fh.frame_size.as_ref() {
if fs.use_superres {
layer_sr[s] = Some((fs.upscaled_width, fs.superres_denom));
}
}
}
}
let mut seq =
crate::encoder::yuv_frame::build_intra_only_seq_yuv(top_w, top_h, bit_depth, format)?;
seq.enable_filter_intra = true;
seq.enable_superres = layer_sr.iter().any(|p| p.is_some());
let op_counts: Vec<(u8, u8)> = if temporal_layers <= 1 {
(0..s_count as u8).map(|k| (s_count as u8 - k, 1)).collect()
} else {
(0..s_count as u8)
.flat_map(|k| {
(0..temporal_layers).map(move |j| (s_count as u8 - k, temporal_layers - j))
})
.collect()
};
seq.operating_points = if temporal_layers <= 1 {
spatial_operating_points_for(seq.operating_points[0], s_count as u8)
} else {
let base = seq.operating_points[0];
op_counts
.iter()
.map(|&(sc, tc)| OperatingPoint {
operating_point_idc: (((1u16 << sc) - 1) << 8) | ((1u16 << tc) - 1),
..base
})
.collect()
};
seq.operating_points_cnt_minus_1 = (op_counts.len() - 1) as u8;
let seq_payload = write_sequence_header_obu(&seq);
let mut layer_recons: Vec<Vec<GopFrameReconYuv>> = vec![Vec::with_capacity(n); s_count];
let mut mf_store: Vec<SavedMotionField> = Vec::new();
let mut carry_store: Vec<Option<Rc<RefSlotCarry>>> = vec![None; 8];
let mut slot_hints = [0u32; 8];
let mut temporal_units: Vec<Vec<u8>> = Vec::with_capacity(n);
let donor_armed_layer = |s: usize| tiles_of(s) != (0, 0);
let mut carry_wire: [Option<(usize, usize)>; 8] = [None; 8];
let mut slot_display = [0usize; 8];
let mut temporal_ids: Vec<u8> = vec![0];
let mut tu0: Vec<u8> = Vec::new();
write_obu_with_size(
&mut tu0,
&crate::encoder::obu::ObuHeader::new(ObuType::TemporalDelimiter),
&[],
);
write_obu_with_size(
&mut tu0,
&crate::encoder::obu::ObuHeader::new(ObuType::SequenceHeader),
&seq_payload,
);
for (s, layer) in layers.iter().enumerate() {
let sr_pair = layer_sr[s];
let downscaled: Option<YuvFrame> = sr_pair.map(|(up_w, denom)| {
let wd = crate::encoder::superres_elect::superres_coded_width(up_w, denom);
crate::encoder::superres_elect::downscale_width(&layer[0], wd)
});
let opener_input: &YuvFrame = downscaled.as_ref().unwrap_or(&layer[0]);
let extras = crate::encoder::key_frame::KeyExtras {
tiles: tiles_of(s),
tile_groups,
tile_spans: false,
explicit_tiles: None,
seq_override: Some(&seq),
intra_only_refresh: (s > 0).then_some(((1u8 << budget) - 1) << (budget * s)),
delta_q: true,
delta_plan: None,
qm: true,
qm_level: None,
collect_donor_cdfs: donor_armed_layer(s),
superres_elect: false,
superres: sr_pair,
film_grain: None,
superres_source: sr_pair.is_some().then(|| &layer[0]),
superres_gate: false,
still: false,
still_full_header: false,
full_range: false,
coded_size: None,
color_description: None,
search: Default::default(),
};
let (k, carry) = crate::encoder::key_frame::encode_key_frame_yuv_full(
opener_input,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
&extras,
)?;
let fs = k.fh.frame_size.as_ref().expect("intra driver sizes");
let (mi_rows, mi_cols) = (fs.mi_rows, fs.mi_cols);
let carry = Rc::new(carry);
if s == 0 {
mf_store = (0..8)
.map(|_| SavedMotionField::intra(mi_rows, mi_cols))
.collect();
for c in carry_store.iter_mut() {
*c = Some(carry.clone());
}
slot_hints = [0; 8];
carry_wire = [Some((0, 0)); 8];
} else {
for b in 0..budget {
mf_store[budget * s + b] = SavedMotionField::intra(mi_rows, mi_cols);
carry_store[budget * s + b] = Some(carry.clone());
slot_hints[budget * s + b] = 0;
carry_wire[budget * s + b] = Some((0, s));
}
}
let mut wrote_frame = false;
for desc in ObuIter::new(&k.temporal_unit_bytes) {
let desc = desc.expect("own temporal unit walks");
match desc.obu_type {
ObuType::TemporalDelimiter | ObuType::SequenceHeader => {}
other => {
let header = crate::encoder::obu::ObuHeader::new(other)
.with_extension(ObuExtensionHeader::new(0, s as u8));
write_obu_with_size(&mut tu0, &header, desc.payload);
wrote_frame = true;
}
}
}
if !wrote_frame {
return Err(Error::PartitionWalkOutOfRange);
}
layer_recons[s].push(GopFrameReconYuv {
y: k.recon_y,
u: k.recon_u,
v: k.recon_v,
});
}
temporal_units.push(tu0);
for i in 1..n {
let mut tu: Vec<u8> = Vec::new();
write_obu_with_size(
&mut tu,
&crate::encoder::obu::ObuHeader::new(ObuType::TemporalDelimiter),
&[],
);
let tid = temporal_layer_of(i, temporal_layers.max(1));
temporal_ids.push(tid);
for (s, layer) in layers.iter().enumerate() {
let (last_slot, refresh_slot, last_display): (usize, Option<usize>, usize) =
if temporal_layers <= 1 {
(2 * s + ((i - 1) & 1), Some(2 * s + (i & 1)), i - 1)
} else {
let want = i - (1usize << (temporal_layers - 1 - tid));
let base = budget * s;
let ls = (base..base + budget)
.find(|&slot| slot_display[slot] == want)
.ok_or(Error::PartitionWalkOutOfRange)?;
let rs = (tid + 1 < temporal_layers).then_some(base + usize::from(tid));
(ls, rs, want)
};
let prev = layer_recons[s][last_display].clone();
let lower: Option<(usize, GopFrameReconYuv, (u32, u32))> = if inter_layer && s > 0 {
let l = s - 1;
let lower_slot = if temporal_layers <= 1 {
Some(2 * l + (i & 1))
} else {
(tid + 1 < temporal_layers).then_some(budget * l + usize::from(tid))
};
lower_slot.map(|ls| {
(
ls,
layer_recons[l][i].clone(),
(layers[l][0].width, layers[l][0].height),
)
})
} else {
None
};
let mut il_ref_frame_idx = [last_slot as u8; 7];
let mut il_slot_to_plane = [0usize; 8];
let mut il_single_refs: Vec<i8> = vec![1];
let mut il_refs: Vec<&GopFrameReconYuv> = vec![&prev];
let mut il_ref_dims: Vec<(u32, u32)> = vec![(layer[0].width, layer[0].height)];
if let Some((ls, lower_recon, dims)) = lower.as_ref() {
il_ref_frame_idx[3] = *ls as u8;
il_slot_to_plane[*ls] = 1;
il_single_refs.push(4);
il_refs.push(lower_recon);
il_ref_dims.push(*dims);
}
let last_carry = carry_store[last_slot]
.clone()
.expect("layer slots seeded at instant 0");
let mf: [SavedMotionField; 8] = core::array::from_fn(|k| mf_store[k].clone());
let cfg = InterFrameConfig {
order_hint: i as u32,
show_frame: true,
refresh_frame_flags: refresh_slot.map_or(0, |rs| 1u8 << rs),
ref_frame_idx: il_ref_frame_idx,
short_ref_signaling: false,
slot_hints,
single_refs: il_single_refs,
compound_pairs: Vec::new(),
refs: il_refs,
slot_to_plane: il_slot_to_plane,
primary_ref_frame: 0,
primary_carry: Some(&last_carry),
allow_temporal_seg: false,
alt_primaries: vec![(PRIMARY_REF_NONE, None)],
exact_mask: None,
auto_lossless: false,
seg_extras: None,
high_precision_mv: true,
delta_q: true,
cdef: true,
cdef_units: true,
lr: true,
freeze_cdfs: false,
tiles: tiles_of(s),
tile_groups,
collect_donor_cdfs: donor_armed_layer(s),
elect_donor: donor_armed_layer(s),
explicit_tiles: None,
qm: true,
film_grain: None,
s_frame: false,
error_resilient: false,
tile_spans: false,
superres: None,
superres_source: None,
ref_dims: il_ref_dims,
};
let q = if base_q_idx == 0 {
0
} else {
(i32::from(base_q_idx) + layer_q_off(tid)).clamp(1, 255) as u8
};
let (obus, recon, saved, carry, aux) =
encode_inter_frame_generic(&layer[i], &seq, q, &cfg, &[], &mf, RateModel::Twin)?;
if last_carry.donor_cdfs.len() > 1 {
let fixed_cdfs = match aux.donor_elected {
Some(t) => {
let span = last_carry
.ctx_update_span
.expect("donor election only fires on multi-tile frames");
let (tu_idx, ord) =
carry_wire[last_slot].expect("consumed frame located on the wire");
crate::encoder::inter_frame::patch_ctx_update_in_tu_frame(
&mut temporal_units[tu_idx],
ord,
span,
t,
);
last_carry.donor_cdfs[t as usize].clone()
}
None => last_carry.cdfs.clone(),
};
let fixed = Rc::new(RefSlotCarry {
cdfs: fixed_cdfs,
segment_ids: last_carry.segment_ids.clone(),
mi_rows: last_carry.mi_rows,
mi_cols: last_carry.mi_cols,
gm_params: last_carry.gm_params,
donor_cdfs: Vec::new(),
ctx_update_span: None,
});
for slot in carry_store.iter_mut() {
if let Some(c) = slot {
if Rc::ptr_eq(c, &last_carry) {
*slot = Some(fixed.clone());
}
}
}
}
for mut obu in obus {
obu.header.extension = Some(ObuExtensionHeader::new(tid, s as u8));
write_obu_with_size(&mut tu, &obu.header, &obu.body);
}
layer_recons[s].push(recon);
if let Some(rs) = refresh_slot {
mf_store[rs] = saved;
carry_store[rs] = Some(Rc::new(carry));
slot_hints[rs] = i as u32;
carry_wire[rs] = Some((i, s));
slot_display[rs] = i;
}
}
temporal_units.push(tu);
}
let mut ivf_bytes: Vec<u8> = Vec::new();
{
let cursor = std::io::Cursor::new(&mut ivf_bytes);
let mut iw = IvfWriter::new(cursor, FOURCC_AV01, top_w as u16, top_h as u16, 25, 1)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
for (idx, tu) in temporal_units.iter().enumerate() {
iw.write_frame(tu, idx as u64)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
}
iw.patch_frame_count()
.map_err(|_| Error::PartitionWalkOutOfRange)?;
}
Ok(SpatialLayeredGopYuv {
ivf_bytes,
temporal_units,
seq,
layer_recons,
layer_dims: layers.iter().map(|l| (l[0].width, l[0].height)).collect(),
temporal_ids,
operating_points: op_counts,
})
}
pub fn encode_spatial_layered_gop_yuv420_with_q(
layers: &[Vec<Yuv420Frame>],
base_q_idx: u8,
) -> Result<SpatialLayeredGop, Error> {
encode_spatial_layered_gop_yuv420_with_q_tiles(layers, base_q_idx, None, 1)
}
pub fn encode_spatial_layered_gop_yuv420_with_q_tiles(
layers: &[Vec<Yuv420Frame>],
base_q_idx: u8,
layer_tiles: Option<&[(u32, u32)]>,
tile_groups: u32,
) -> Result<SpatialLayeredGop, Error> {
let wide: Vec<Vec<YuvFrame>> = layers
.iter()
.map(|l| l.iter().map(YuvFrame::from_yuv420_8bit).collect())
.collect();
encode_spatial_layered_gop_yuv_with_q_tiles(&wide, base_q_idx, layer_tiles, tile_groups)
.map(narrow_spatial_gop)
}
fn narrow_spatial_gop(s: SpatialLayeredGopYuv) -> SpatialLayeredGop {
let narrow = |p: &[u16]| p.iter().map(|&v| v as u8).collect::<Vec<u8>>();
SpatialLayeredGop {
ivf_bytes: s.ivf_bytes,
temporal_units: s.temporal_units,
seq: s.seq,
layer_recons: s
.layer_recons
.iter()
.map(|lr| {
lr.iter()
.map(|r| crate::encoder::inter_frame::GopFrameRecon {
y: narrow(&r.y),
u: narrow(&r.u),
v: narrow(&r.v),
})
.collect()
})
.collect(),
layer_dims: s.layer_dims,
temporal_ids: s.temporal_ids,
operating_points: s.operating_points,
}
}
pub fn encode_temporal_layered_gop_yuv420_with_q(
frames: &[Yuv420Frame],
base_q_idx: u8,
temporal_layers: u8,
) -> Result<TemporalLayeredGop, Error> {
let wide: Vec<YuvFrame> = frames.iter().map(YuvFrame::from_yuv420_8bit).collect();
let t = encode_temporal_layered_gop_yuv_with_q(&wide, base_q_idx, temporal_layers)?;
Ok(TemporalLayeredGop {
gop: narrow_gop_8bit(t.gop),
temporal_ids: t.temporal_ids,
})
}
pub fn encode_temporal_layered_gop_yuv420_with_q_tiles(
frames: &[Yuv420Frame],
base_q_idx: u8,
temporal_layers: u8,
tiles: (u32, u32),
tile_groups: u32,
) -> Result<TemporalLayeredGop, Error> {
let wide: Vec<YuvFrame> = frames.iter().map(YuvFrame::from_yuv420_8bit).collect();
let t = encode_temporal_layered_gop_yuv_with_q_tiles(
&wide,
base_q_idx,
temporal_layers,
tiles,
tile_groups,
)?;
Ok(TemporalLayeredGop {
gop: narrow_gop_8bit(t.gop),
temporal_ids: t.temporal_ids,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dyadic_ladder_matches_the_module_doc() {
let expect = [0u8, 2, 1, 2, 0, 2, 1, 2, 0];
for (i, &e) in expect.iter().enumerate() {
assert_eq!(temporal_layer_of(i, 3), e, "i = {i}");
}
for i in 0..8 {
assert_eq!(temporal_layer_of(i, 2), (i % 2) as u8);
}
let expect4 = [0u8, 3, 2, 3, 1, 3, 2, 3];
for (i, &e) in expect4.iter().enumerate() {
assert_eq!(temporal_layer_of(i, 4), e, "i = {i}");
}
}
#[test]
fn operating_point_masks_cover_layer_prefixes() {
let base = 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,
};
let ops = operating_points_for(base, 3);
assert_eq!(ops.len(), 3);
assert_eq!(ops[0].operating_point_idc, 0x107);
assert_eq!(ops[1].operating_point_idc, 0x103);
assert_eq!(ops[2].operating_point_idc, 0x101);
assert!(ops.iter().all(|o| o.operating_point_idc != 0));
}
}