use std::io::Write;
use byteorder_lite::{LittleEndian, WriteBytesExt};
use super::arithmetic_encoder::{tree_encode_path, ArithmeticEncoder};
use super::common::*;
use super::prediction::*;
use super::transform;
use super::yuv::convert_image_y;
use super::yuv::convert_image_yuv;
use super::Frame;
use crate::ColorType;
use crate::EncodingError;
#[derive(Clone, Copy, Default)]
struct Complexity {
y2: u8,
y: [u8; 4],
u: [u8; 2],
v: [u8; 2],
}
impl Complexity {
fn clear(&mut self, include_y2: bool) {
self.y = [0; 4];
self.u = [0; 2];
self.v = [0; 2];
if include_y2 {
self.y2 = 0;
}
}
}
#[derive(Default)]
struct QuantizationIndices {
yac_abs: u8,
ydc_delta: Option<i8>,
y2dc_delta: Option<i8>,
y2ac_delta: Option<i8>,
uvdc_delta: Option<i8>,
uvac_delta: Option<i8>,
}
#[derive(Clone, Copy, Default)]
#[cfg_attr(test, derive(Debug, PartialEq))]
struct MacroblockInfo {
luma_mode: LumaMode,
luma_bpred: Option<[IntraMode; 16]>,
chroma_mode: ChromaMode,
segment_id: Option<usize>,
coeffs_skipped: bool,
}
struct Luma16x16Coeffs {
y2_coeffs: [i32; 16],
y_coeffs: LumaYCoeffs,
}
type LumaYCoeffs = [i32; 16 * 16];
type ChromaCoeffs = [i32; 16 * 4];
fn coeff_rate_estimate(coeffs: &[i32]) -> i64 {
coeffs
.iter()
.filter(|&&c| c != 0)
.map(|&c| 1 + i64::from(c.abs()))
.sum()
}
fn tree_bit_cost(tree: &[i8], probabilities: &[Prob], value: i8) -> f64 {
let mut current_index = tree
.iter()
.position(|&x| x == -value)
.expect("value must be a leaf of this tree");
let mut bits = 0.0f64;
loop {
if current_index == 0 {
bits += branch_bit_cost(probabilities[0], false);
break;
}
if current_index == 1 {
bits += branch_bit_cost(probabilities[0], true);
break;
}
let (branch_is_true, node_index) = if current_index % 2 == 0 {
(false, current_index)
} else {
(true, current_index - 1)
};
bits += branch_bit_cost(probabilities[node_index / 2], branch_is_true);
current_index = tree
.iter()
.position(|&x| x == node_index as i8)
.expect("every non-root tree node is some other node's child");
}
bits
}
fn branch_bit_cost(prob: Prob, branch_is_true: bool) -> f64 {
let p_false = f64::from(prob) / 256.0;
let p = if branch_is_true {
1.0 - p_false
} else {
p_false
};
-p.clamp(f64::MIN_POSITIVE, 1.0).log2()
}
fn bpred_mode_bit_cost(top: IntraMode, left: IntraMode, mode: IntraMode) -> f64 {
let probs = &KEYFRAME_BPRED_MODE_PROBS[top as usize][left as usize];
tree_bit_cost(&KEYFRAME_BPRED_MODE_TREE, probs, mode as i8)
}
const LAMBDA_SCALE: f64 = 0.02;
fn mode_decision_lambda(segment: &Segment) -> f64 {
let qstep = f64::from(segment.yac);
LAMBDA_SCALE * qstep * qstep
}
fn skip_probability(skipped: u32, total: u32) -> u8 {
if total == 0 {
return 128;
}
let not_skipped = u64::from(total - skipped);
let scaled = (256 * not_skipped + u64::from(total) / 2) / u64::from(total);
scaled.clamp(1, 255) as u8
}
struct TokenEvent {
band: usize,
context: usize,
token: i8,
start_index: usize,
quantized_value: i32,
}
fn tokenize_block(
block: &[i32; 16],
plane: Plane,
complexity: usize,
dc_quant: i16,
ac_quant: i16,
) -> (Vec<TokenEvent>, bool) {
let first_coeff = if plane == Plane::YCoeff1 { 1 } else { 0 };
assert!(complexity <= 2);
let mut complexity = complexity;
let mut zigzag_block = [0i32; 16];
for i in first_coeff..16 {
let zigzag_index = usize::from(ZIGZAG[i]);
let quant = if zigzag_index > 0 { ac_quant } else { dc_quant };
zigzag_block[i] = block[zigzag_index] / i32::from(quant);
}
let end_of_block_index =
if let Some(last_non_zero_index) = zigzag_block.iter().rev().position(|x| *x != 0) {
(15 - last_non_zero_index) + 1
} else {
0
};
let mut events = Vec::new();
let mut skip_eob = false;
for index in first_coeff..end_of_block_index {
let coeff = zigzag_block[index];
let band = usize::from(COEFF_BANDS[index]);
let start_index = if skip_eob { 2 } else { 0 };
let token = match coeff.abs() {
0 => {
skip_eob = true;
DCT_0
}
literal @ 1..=4 => {
skip_eob = false;
literal as i8
}
value => {
skip_eob = false;
match value {
5..=6 => DCT_CAT1,
7..=10 => DCT_CAT2,
11..=18 => DCT_CAT3,
19..=34 => DCT_CAT4,
35..=66 => DCT_CAT5,
67..=2048 => DCT_CAT6,
_ => unreachable!(),
}
}
};
events.push(TokenEvent {
band,
context: complexity,
token,
start_index,
quantized_value: coeff,
});
complexity = match token {
DCT_0 => 0,
DCT_1 => 1,
_ => 2,
};
}
if end_of_block_index < 16 {
let band_index = usize::max(first_coeff, end_of_block_index);
let band = usize::from(COEFF_BANDS[band_index]);
events.push(TokenEvent {
band,
context: complexity,
token: DCT_EOB,
start_index: 0,
quantized_value: 0,
});
}
(events, end_of_block_index > 0)
}
type TokenCounts = [[[[[u64; 2]; NUM_DCT_TOKENS - 1]; 3]; 8]; 4];
fn accumulate_token_events(counts: &mut TokenCounts, plane: Plane, events: &[TokenEvent]) {
for event in events {
for (bit, prob_index) in tree_encode_path(&DCT_TOKEN_TREE, event.token, event.start_index) {
counts[plane as usize][event.band][event.context][prob_index][usize::from(bit)] += 1;
}
}
}
const MIN_TOKEN_PROB_OBSERVATIONS: u64 = 20;
fn total_branch_bit_cost(false_count: u64, true_count: u64, prob: Prob) -> f64 {
false_count as f64 * branch_bit_cost(prob, false)
+ true_count as f64 * branch_bit_cost(prob, true)
}
fn derive_updated_token_probs(counts: &TokenCounts) -> TokenProbTables {
let mut probs = COEFF_PROBS;
for (i, counts_i) in counts.iter().enumerate() {
for (j, counts_j) in counts_i.iter().enumerate() {
for (k, counts_k) in counts_j.iter().enumerate() {
for (l, &[false_count, true_count]) in counts_k.iter().enumerate() {
let total = false_count + true_count;
if total < MIN_TOKEN_PROB_OBSERVATIONS {
continue;
}
let default_prob = COEFF_PROBS[i][j][k][l];
let scaled = (256 * false_count + total / 2) / total;
let candidate_prob = scaled.clamp(1, 255) as u8;
if candidate_prob == default_prob {
continue;
}
let bits_with_default =
total_branch_bit_cost(false_count, true_count, default_prob);
let bits_with_candidate =
total_branch_bit_cost(false_count, true_count, candidate_prob);
let savings = bits_with_default - bits_with_candidate;
let update_flag_prob = COEFF_UPDATE_PROBS[i][j][k][l];
let flag_cost_true = total_branch_bit_cost(0, 1, update_flag_prob);
let flag_cost_false = total_branch_bit_cost(1, 0, update_flag_prob);
let overhead = 8.0 + flag_cost_true - flag_cost_false;
if savings > overhead {
probs[i][j][k][l] = candidate_prob;
}
}
}
}
}
probs
}
fn dc_quant(index: i32) -> i16 {
DC_QUANT[index.clamp(0, 127) as usize]
}
fn ac_quant(index: i32) -> i16 {
AC_QUANT[index.clamp(0, 127) as usize]
}
const SEGMENT_QUANT_DELTAS: [i8; MAX_SEGMENTS] = [-6, -2, 2, 7];
fn build_segment(yac_abs: u8, delta: i8, indices: &QuantizationIndices) -> Segment {
let base = i32::from(yac_abs) + i32::from(delta);
let ydc_delta = indices.ydc_delta.map_or(0, i32::from);
let y2dc_delta = indices.y2dc_delta.map_or(0, i32::from);
let y2ac_delta = indices.y2ac_delta.map_or(0, i32::from);
let uvdc_delta = indices.uvdc_delta.map_or(0, i32::from);
let uvac_delta = indices.uvac_delta.map_or(0, i32::from);
let y2ac = ((i32::from(ac_quant(base + y2ac_delta)) * 155 / 100) as i16).max(8);
let uvdc = dc_quant(base + uvdc_delta).min(132);
Segment {
ydc: dc_quant(base + ydc_delta),
yac: ac_quant(base),
y2dc: dc_quant(base + y2dc_delta) * 2,
y2ac,
uvdc,
uvac: ac_quant(base + uvac_delta),
delta_values: true,
quantizer_level: delta,
loopfilter_level: 0,
}
}
fn segment_tree_probs_for(segment_ids: &[u8]) -> [Prob; 3] {
let mut counts = [0u32; MAX_SEGMENTS];
for &id in segment_ids {
counts[id as usize] += 1;
}
let total = segment_ids.len() as u32;
let high_half = counts[2] + counts[3];
let prob0 = skip_probability(high_half, total);
let prob1 = skip_probability(counts[1], counts[0] + counts[1]);
let prob2 = skip_probability(counts[3], counts[2] + counts[3]);
[prob0, prob1, prob2]
}
struct Vp8Encoder<W> {
writer: W,
frame: Frame,
encoder: ArithmeticEncoder,
segments: [Segment; MAX_SEGMENTS],
segments_enabled: bool,
segment_tree_probs: [Prob; 3],
mb_segment_ids: Vec<u8>,
loop_filter_adjustments: bool,
macroblock_no_skip_coeff: Option<u8>,
quantization_indices: QuantizationIndices,
token_probs: TokenProbTables,
top_complexity: Vec<Complexity>,
left_complexity: Complexity,
top_b_pred: Vec<IntraMode>,
left_b_pred: [IntraMode; 4],
macroblock_width: u16,
macroblock_height: u16,
partitions: Vec<ArithmeticEncoder>,
left_border_y: [u8; 16 + 1],
left_border_u: [u8; 8 + 1],
left_border_v: [u8; 8 + 1],
top_border_y: Vec<u8>,
top_border_u: Vec<u8>,
top_border_v: Vec<u8>,
mb_info_cache: Vec<Option<MacroblockInfo>>,
}
impl<W: Write> Vp8Encoder<W> {
fn new(writer: W) -> Self {
let segment = Segment::default();
Self {
writer,
frame: Frame::default(),
encoder: ArithmeticEncoder::new(),
segments: [segment; MAX_SEGMENTS],
segments_enabled: false,
segment_tree_probs: [128; 3],
mb_segment_ids: Vec::new(),
loop_filter_adjustments: false,
macroblock_no_skip_coeff: None,
quantization_indices: QuantizationIndices::default(),
token_probs: Default::default(),
top_complexity: Vec::new(),
left_complexity: Complexity::default(),
top_b_pred: Vec::new(),
left_b_pred: [IntraMode::default(); 4],
macroblock_width: 0,
macroblock_height: 0,
partitions: vec![ArithmeticEncoder::new()],
left_border_y: [0u8; 16 + 1],
left_border_u: [0u8; 8 + 1],
left_border_v: [0u8; 8 + 1],
top_border_y: Vec::new(),
top_border_u: Vec::new(),
top_border_v: Vec::new(),
mb_info_cache: Vec::new(),
}
}
fn write_uncompressed_frame_header(
&mut self,
partition_size: u32,
) -> Result<(), EncodingError> {
let version = u32::from(self.frame.version);
let for_display = if self.frame.for_display { 1 } else { 0 };
let keyframe_bit = 0;
let tag = (partition_size << 5) | (for_display << 4) | (version << 1) | (keyframe_bit);
self.writer.write_u24::<LittleEndian>(tag)?;
let magic_bytes_buffer: [u8; 3] = [0x9d, 0x01, 0x2a];
self.writer.write_all(&magic_bytes_buffer)?;
let width = self.frame.width & 0x3FFF;
let height = self.frame.height & 0x3FFF;
self.writer.write_u16::<LittleEndian>(width)?;
self.writer.write_u16::<LittleEndian>(height)?;
Ok(())
}
fn encode_compressed_frame_header(&mut self) {
self.encoder.write_literal(1, 0);
self.encoder.write_literal(1, 0);
self.encoder.write_flag(self.segments_enabled);
if self.segments_enabled {
self.encode_segment_updates();
}
self.encoder.write_flag(self.frame.filter_type);
self.encoder.write_literal(6, self.frame.filter_level);
self.encoder.write_literal(3, self.frame.sharpness_level);
self.encoder.write_flag(self.loop_filter_adjustments);
if self.loop_filter_adjustments {
self.encode_loop_filter_adjustments();
}
let partitions_value: u8 = self.partitions.len().ilog2().try_into().unwrap();
self.encoder.write_literal(2, partitions_value);
self.encode_quantization_indices();
self.encoder.write_literal(1, 0);
self.encode_updated_token_probabilities();
let mb_no_skip_coeff = if self.macroblock_no_skip_coeff.is_some() {
1
} else {
0
};
self.encoder.write_literal(1, mb_no_skip_coeff);
if let Some(prob_skip_false) = self.macroblock_no_skip_coeff {
self.encoder.write_literal(8, prob_skip_false);
}
}
fn write_partitions(&mut self) -> Result<(), EncodingError> {
let partitions = std::mem::take(&mut self.partitions);
let partitions_bytes: Vec<Vec<u8>> = partitions
.into_iter()
.map(|x| x.flush_and_get_buffer())
.collect();
if partitions_bytes.len() > 1 {
for partition in partitions_bytes[..partitions_bytes.len() - 1].iter() {
self.writer
.write_u24::<LittleEndian>(partition.len() as u32)?;
self.writer.write_all(partition)?;
}
}
self.writer
.write_all(&partitions_bytes[partitions_bytes.len() - 1])?;
Ok(())
}
fn encode_segment_updates(&mut self) {
self.encoder.write_flag(true); self.encoder.write_flag(true);
self.encoder.write_flag(false);
for segment in &self.segments {
self.encoder
.write_optional_signed_value(7, Some(segment.quantizer_level));
}
for _ in 0..MAX_SEGMENTS {
self.encoder.write_optional_signed_value(6, None);
}
for &prob in &self.segment_tree_probs {
self.encoder.write_flag(true);
self.encoder.write_literal(8, prob);
}
}
fn encode_loop_filter_adjustments(&mut self) {
todo!();
}
fn encode_quantization_indices(&mut self) {
self.encoder
.write_literal(7, self.quantization_indices.yac_abs);
self.encoder
.write_optional_signed_value(4, self.quantization_indices.ydc_delta);
self.encoder
.write_optional_signed_value(4, self.quantization_indices.y2dc_delta);
self.encoder
.write_optional_signed_value(4, self.quantization_indices.y2ac_delta);
self.encoder
.write_optional_signed_value(4, self.quantization_indices.uvdc_delta);
self.encoder
.write_optional_signed_value(4, self.quantization_indices.uvac_delta);
}
fn encode_updated_token_probabilities(&mut self) {
for (i, is) in COEFF_UPDATE_PROBS.iter().enumerate() {
for (j, js) in is.iter().enumerate() {
for (k, ks) in js.iter().enumerate() {
for (l, update_flag_prob) in ks.iter().enumerate() {
let new_prob = self.token_probs[i][j][k][l];
let update = new_prob != COEFF_PROBS[i][j][k][l];
self.encoder.write_bool(update, *update_flag_prob);
if update {
self.encoder.write_literal(8, new_prob);
}
}
}
}
}
}
fn write_macroblock_header(&mut self, macroblock_info: &MacroblockInfo, mbx: usize) {
if self.segments_enabled {
let segment_id = macroblock_info
.segment_id
.expect("segments_enabled implies every macroblock has a segment_id");
self.encoder.write_with_tree(
&SEGMENT_ID_TREE,
&self.segment_tree_probs,
segment_id as i8,
);
}
if let Some(prob) = self.macroblock_no_skip_coeff {
self.encoder
.write_bool(macroblock_info.coeffs_skipped, prob);
}
self.encoder.write_with_tree(
&KEYFRAME_YMODE_TREE,
&KEYFRAME_YMODE_PROBS,
macroblock_info.luma_mode as i8,
);
match macroblock_info.luma_mode.into_intra() {
None => {
if let Some(bpred) = macroblock_info.luma_bpred {
for y in 0usize..4 {
let mut left = self.left_b_pred[y];
for x in 0usize..4 {
let top = self.top_b_pred[mbx * 4 + x];
let probs = &KEYFRAME_BPRED_MODE_PROBS[top as usize][left as usize];
let intra_mode = bpred[y * 4 + x];
self.encoder.write_with_tree(
&KEYFRAME_BPRED_MODE_TREE,
probs,
intra_mode as i8,
);
left = intra_mode;
self.top_b_pred[mbx * 4 + x] = intra_mode;
}
self.left_b_pred[y] = left;
}
} else {
panic!("Invalid, can't set luma mode to B without setting preds");
}
}
Some(intra_mode) => {
for (left, top) in self
.left_b_pred
.iter_mut()
.zip(self.top_b_pred[4 * mbx..][..4].iter_mut())
{
*left = intra_mode;
*top = intra_mode;
}
}
}
self.encoder.write_with_tree(
&KEYFRAME_UV_MODE_TREE,
&KEYFRAME_UV_MODE_PROBS,
macroblock_info.chroma_mode as i8,
);
}
fn advance_bpred_context(&mut self, macroblock_info: &MacroblockInfo, mbx: usize) {
match macroblock_info.luma_mode.into_intra() {
None => {
let bpred = macroblock_info
.luma_bpred
.expect("Invalid, can't set luma mode to B without setting preds");
for y in 0usize..4 {
let mut left = self.left_b_pred[y];
for x in 0usize..4 {
let intra_mode = bpred[y * 4 + x];
left = intra_mode;
self.top_b_pred[mbx * 4 + x] = intra_mode;
}
self.left_b_pred[y] = left;
}
}
Some(intra_mode) => {
for (left, top) in self
.left_b_pred
.iter_mut()
.zip(self.top_b_pred[4 * mbx..][..4].iter_mut())
{
*left = intra_mode;
*top = intra_mode;
}
}
}
}
fn encode_residual_data(
&mut self,
macroblock_info: &MacroblockInfo,
partition_index: usize,
mbx: usize,
y_block_data: &[i32; 16 * 16],
u_block_data: &[i32; 16 * 4],
v_block_data: &[i32; 16 * 4],
) {
let mut plane = if macroblock_info.luma_mode == LumaMode::B {
Plane::YCoeff0
} else {
Plane::Y2
};
let segment = self.segments[macroblock_info.segment_id.unwrap_or(0)];
if plane == Plane::Y2 {
let mut coeffs0 = get_coeffs0_from_block(y_block_data);
transform::wht4x4(&mut coeffs0);
let complexity = self.left_complexity.y2 + self.top_complexity[mbx].y2;
let has_coeffs = self.encode_coefficients(
&coeffs0,
partition_index,
plane,
complexity.into(),
segment.y2dc,
segment.y2ac,
);
self.left_complexity.y2 = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].y2 = if has_coeffs { 1 } else { 0 };
plane = Plane::YCoeff1;
}
for y in 0usize..4 {
let mut left = self.left_complexity.y[y];
for x in 0..4 {
let block = y_block_data[y * 4 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].y[x];
let complexity = left + top;
let has_coeffs = self.encode_coefficients(
&block,
partition_index,
plane,
complexity.into(),
segment.ydc,
segment.yac,
);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].y[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.y[y] = left;
}
plane = Plane::Chroma;
for y in 0usize..2 {
let mut left = self.left_complexity.u[y];
for x in 0usize..2 {
let block = u_block_data[y * 2 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].u[x];
let complexity = left + top;
let has_coeffs = self.encode_coefficients(
&block,
partition_index,
plane,
complexity.into(),
segment.uvdc,
segment.uvac,
);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].u[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.u[y] = left;
}
for y in 0usize..2 {
let mut left = self.left_complexity.v[y];
for x in 0usize..2 {
let block = v_block_data[y * 2 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].v[x];
let complexity = left + top;
let has_coeffs = self.encode_coefficients(
&block,
partition_index,
plane,
complexity.into(),
segment.uvdc,
segment.uvac,
);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].v[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.v[y] = left;
}
}
fn encode_coefficients(
&mut self,
block: &[i32; 16],
partition_index: usize,
plane: Plane,
complexity: usize,
dc_quant: i16,
ac_quant: i16,
) -> bool {
let (events, has_coeffs) = tokenize_block(block, plane, complexity, dc_quant, ac_quant);
let encoder = &mut self.partitions[partition_index];
let probs = &self.token_probs[plane as usize];
for event in &events {
let token_probs = &probs[event.band][event.context];
encoder.write_with_tree_start_index(
&DCT_TOKEN_TREE,
token_probs,
event.token,
event.start_index,
);
if event.token == DCT_EOB || event.token == DCT_0 {
continue;
}
if event.token >= DCT_CAT1 {
let category = event.token;
let category_probs = PROB_DCT_CAT[(category - DCT_CAT1) as usize];
let value = event.quantized_value.abs();
let extra = value - i32::from(DCT_CAT_BASE[(category - DCT_CAT1) as usize]);
let mut mask = if category == DCT_CAT6 {
1 << (11 - 1)
} else {
1 << (category - DCT_CAT1)
};
for &prob in category_probs.iter() {
if prob == 0 {
break;
}
let extra_bool = extra & mask > 0;
encoder.write_bool(extra_bool, prob);
mask >>= 1;
}
}
encoder.write_flag(!event.quantized_value.is_positive());
}
has_coeffs
}
fn encode_image(
&mut self,
data: &[u8],
color: ColorType,
width: u16,
height: u16,
lossy_quality: u8,
) -> Result<(), EncodingError> {
let (y_bytes, u_bytes, v_bytes) = match color {
ColorType::Rgb8 => convert_image_yuv::<3>(data, width, height),
ColorType::Rgba8 => convert_image_yuv::<4>(data, width, height),
ColorType::L8 => convert_image_y::<1>(data, width, height),
ColorType::La8 => convert_image_y::<2>(data, width, height),
};
let bytes_per_pixel = match color {
ColorType::L8 => 1,
ColorType::La8 => 2,
ColorType::Rgb8 => 3,
ColorType::Rgba8 => 4,
};
assert_eq!(
(u64::from(width) * u64::from(height)).saturating_mul(bytes_per_pixel),
data.len() as u64,
"width/height doesn't match data length of {} for the color type {:?}",
data.len(),
color
);
self.setup_encoding(lossy_quality, width, height, y_bytes, u_bytes, v_bytes);
let (skipped, total) = self.count_skipped_macroblocks();
self.reset_frame_state();
self.macroblock_no_skip_coeff = Some(skip_probability(skipped, total));
let token_counts = self.collect_token_counts();
self.reset_frame_state();
self.token_probs = derive_updated_token_probs(&token_counts);
self.encode_compressed_frame_header();
for mby in 0..self.macroblock_height {
let partition_index = usize::from(mby) % self.partitions.len();
self.left_complexity = Complexity::default();
self.left_b_pred = [IntraMode::default(); 4];
self.left_border_y = [129u8; 16 + 1];
self.left_border_u = [129u8; 8 + 1];
self.left_border_v = [129u8; 8 + 1];
for mbx in 0..self.macroblock_width {
let idx = usize::from(mby) * usize::from(self.macroblock_width) + usize::from(mbx);
let macroblock_info = self.mb_info_cache[idx].expect(
"count_skipped_macroblocks populates every macroblock's cached mode \
decision before encode_image's real pass runs",
);
self.write_macroblock_header(¯oblock_info, mbx.into());
let y_block_data =
self.transform_luma_block(mbx.into(), mby.into(), ¯oblock_info);
let (u_block_data, v_block_data) =
self.transform_chroma_blocks(mbx.into(), mby.into(), ¯oblock_info);
if !macroblock_info.coeffs_skipped {
self.encode_residual_data(
¯oblock_info,
partition_index,
mbx as usize,
&y_block_data,
&u_block_data,
&v_block_data,
);
} else {
self.left_complexity
.clear(macroblock_info.luma_mode != LumaMode::B);
self.top_complexity[usize::from(mbx)]
.clear(macroblock_info.luma_mode != LumaMode::B);
}
}
}
let compressed_header_encoder = std::mem::take(&mut self.encoder);
let compressed_header_bytes = compressed_header_encoder.flush_and_get_buffer();
self.write_uncompressed_frame_header(compressed_header_bytes.len() as u32)?;
self.writer.write_all(&compressed_header_bytes)?;
self.write_partitions()?;
Ok(())
}
const LUMA_MODE_CANDIDATES: [LumaMode; 4] =
[LumaMode::DC, LumaMode::V, LumaMode::H, LumaMode::TM];
const CHROMA_MODE_CANDIDATES: [ChromaMode; 4] =
[ChromaMode::DC, ChromaMode::V, ChromaMode::H, ChromaMode::TM];
const BPRED_MODE_CANDIDATES: [IntraMode; 10] = [
IntraMode::DC,
IntraMode::TM,
IntraMode::VE,
IntraMode::HE,
IntraMode::LD,
IntraMode::RD,
IntraMode::VR,
IntraMode::VL,
IntraMode::HD,
IntraMode::HU,
];
fn choose_macroblock_info(&self, mbx: usize, mby: usize) -> MacroblockInfo {
let segment_id = self.mb_segment_ids[mby * usize::from(self.macroblock_width) + mbx];
let segment = self.segments[segment_id as usize];
let lambda = mode_decision_lambda(&segment);
let mut best_luma: Option<(f64, LumaMode, Luma16x16Coeffs)> = None;
for &mode in &Self::LUMA_MODE_CANDIDATES {
let (distortion, rate, coeffs) = self.trial_luma_16x16(mode, mbx, mby, &segment);
let cost = distortion as f64 + lambda * rate as f64;
let better = match &best_luma {
None => true,
Some((best_cost, ..)) => cost < *best_cost,
};
if better {
best_luma = Some((cost, mode, coeffs));
}
}
let (best_16x16_cost, luma_mode_16x16, luma_coeffs_16x16) =
best_luma.expect("LUMA_MODE_CANDIDATES is non-empty");
let (bpred_distortion, bpred_rate, bpred_modes, bpred_coeffs) =
self.trial_luma_bpred(mbx, mby, &segment, lambda);
let bpred_cost = bpred_distortion as f64 + lambda * bpred_rate;
let (luma_mode, luma_bpred, luma_all_zero) = if bpred_cost < best_16x16_cost {
(
LumaMode::B,
Some(bpred_modes),
bpred_coeffs.iter().all(|&c| c == 0),
)
} else {
(
luma_mode_16x16,
None,
luma_coeffs_16x16.y2_coeffs.iter().all(|&c| c == 0)
&& luma_coeffs_16x16.y_coeffs.iter().all(|&c| c == 0),
)
};
let mut best_chroma: Option<(f64, ChromaMode, ChromaCoeffs, ChromaCoeffs)> = None;
for &mode in &Self::CHROMA_MODE_CANDIDATES {
let (distortion, rate, u_coeffs, v_coeffs) =
self.trial_chroma(mode, mbx, mby, &segment);
let cost = distortion as f64 + lambda * rate as f64;
let better = match &best_chroma {
None => true,
Some((best_cost, ..)) => cost < *best_cost,
};
if better {
best_chroma = Some((cost, mode, u_coeffs, v_coeffs));
}
}
let (_, chroma_mode, u_coeffs, v_coeffs) =
best_chroma.expect("CHROMA_MODE_CANDIDATES is non-empty");
let coeffs_skipped =
luma_all_zero && u_coeffs.iter().all(|&c| c == 0) && v_coeffs.iter().all(|&c| c == 0);
MacroblockInfo {
luma_mode,
luma_bpred,
chroma_mode,
segment_id: Some(segment_id as usize),
coeffs_skipped,
}
}
fn trial_luma_16x16(
&self,
luma_mode: LumaMode,
mbx: usize,
mby: usize,
segment: &Segment,
) -> (i64, i64, Luma16x16Coeffs) {
let y_with_border = self.get_predicted_luma_block_16x16(luma_mode, mbx, mby);
let luma_blocks = self.get_luma_blocks_from_predicted_16x16(&y_with_border, mbx, mby);
let mut coeffs = self.get_luma_block_coeffs_16x16(luma_blocks, segment);
let dequantized_blocks =
self.get_dequantized_blocks_from_coeffs_luma_16x16(&mut coeffs, segment);
let mut recon = y_with_border;
for y in 0usize..4 {
for x in 0usize..4 {
let i = x + y * 4;
let rb: &[i32; 16] = dequantized_blocks[i * 16..][..16].try_into().unwrap();
add_residue(&mut recon, rb, 1 + y * 4, 1 + x * 4, LUMA_STRIDE);
}
}
let distortion = self.luma_sse(&recon, mbx, mby);
let rate = coeff_rate_estimate(&coeffs.y2_coeffs) + coeff_rate_estimate(&coeffs.y_coeffs);
(distortion, rate, coeffs)
}
fn luma_sse(&self, recon: &[u8; LUMA_BLOCK_SIZE], mbx: usize, mby: usize) -> i64 {
let stride = LUMA_STRIDE;
let width = usize::from(self.macroblock_width) * 16;
let mut sse: i64 = 0;
for y in 0..16 {
for x in 0..16 {
let r = i64::from(recon[(y + 1) * stride + (x + 1)]);
let a = i64::from(self.frame.ybuf[(mby * 16 + y) * width + mbx * 16 + x]);
let d = r - a;
sse += d * d;
}
}
sse
}
fn trial_luma_bpred(
&self,
mbx: usize,
mby: usize,
segment: &Segment,
lambda: f64,
) -> (i64, f64, [IntraMode; 16], LumaYCoeffs) {
let stride = LUMA_STRIDE;
let mbw = self.macroblock_width;
let width = usize::from(mbw * 16);
let mut y_with_border = create_border_luma(
mbx,
mby,
mbw.into(),
&self.top_border_y,
&self.left_border_y,
);
let mut top_ctx: [IntraMode; 4] = self.top_b_pred[mbx * 4..][..4].try_into().unwrap();
let mut total_distortion: i64 = 0;
let mut total_rate: f64 = 0.0;
let mut chosen_modes = [IntraMode::default(); 16];
let mut y_coeffs: LumaYCoeffs = [0i32; 16 * 16];
for sby in 0usize..4 {
let mut left = self.left_b_pred[sby];
#[allow(clippy::needless_range_loop)]
for sbx in 0usize..4 {
let i = sby * 4 + sbx;
let y0 = sby * 4 + 1;
let x0 = sbx * 4 + 1;
let top = top_ctx[sbx];
let y_data_block_index = (mby * 16 + sby * 4) * width + mbx * 16 + sbx * 4;
#[allow(clippy::type_complexity)]
let mut best: Option<(
f64,
IntraMode,
i64,
f64,
[i32; 16],
[u8; 16],
[i32; 16],
)> = None;
for &mode in &Self::BPRED_MODE_CANDIDATES {
match mode {
IntraMode::TM => predict_tmpred(&mut y_with_border, 4, x0, y0, stride),
IntraMode::VE => predict_bvepred(&mut y_with_border, x0, y0, stride),
IntraMode::HE => predict_bhepred(&mut y_with_border, x0, y0, stride),
IntraMode::DC => predict_bdcpred(&mut y_with_border, x0, y0, stride),
IntraMode::LD => predict_bldpred(&mut y_with_border, x0, y0, stride),
IntraMode::RD => predict_brdpred(&mut y_with_border, x0, y0, stride),
IntraMode::VR => predict_bvrpred(&mut y_with_border, x0, y0, stride),
IntraMode::VL => predict_bvlpred(&mut y_with_border, x0, y0, stride),
IntraMode::HD => predict_bhdpred(&mut y_with_border, x0, y0, stride),
IntraMode::HU => predict_bhupred(&mut y_with_border, x0, y0, stride),
}
let mut predicted_block = [0u8; 16];
let mut residual = [0i32; 16];
for y in 0..4 {
for x in 0..4 {
let border_index = (y0 + y) * stride + x0 + x;
let predicted_value = y_with_border[border_index];
let actual_value = self.frame.ybuf[y_data_block_index + y * width + x];
predicted_block[y * 4 + x] = predicted_value;
residual[y * 4 + x] =
i32::from(actual_value) - i32::from(predicted_value);
}
}
transform::dct4x4(&mut residual);
let mut quantized = residual;
for (index, v) in quantized.iter_mut().enumerate() {
let quant = if index > 0 { segment.yac } else { segment.ydc };
*v /= i32::from(quant);
}
let mut dequantized = quantized;
for (index, v) in dequantized.iter_mut().enumerate() {
let quant = if index > 0 { segment.yac } else { segment.ydc };
*v *= i32::from(quant);
}
transform::idct4x4(&mut dequantized);
let mut distortion: i64 = 0;
for y in 0..4 {
for x in 0..4 {
let p = i32::from(predicted_block[y * 4 + x]);
let r = (p + dequantized[y * 4 + x]).clamp(0, 255);
let actual =
i64::from(self.frame.ybuf[y_data_block_index + y * width + x]);
let d = i64::from(r) - actual;
distortion += d * d;
}
}
let coeff_rate = coeff_rate_estimate(&quantized) as f64;
let submode_rate = bpred_mode_bit_cost(top, left, mode);
let rate = coeff_rate + submode_rate;
let cost = distortion as f64 + lambda * rate;
let better = match &best {
None => true,
Some((best_cost, ..)) => cost < *best_cost,
};
if better {
best = Some((
cost,
mode,
distortion,
rate,
quantized,
predicted_block,
dequantized,
));
}
}
let (_, mode, distortion, rate, quantized, predicted_block, dequantized) =
best.expect("BPRED_MODE_CANDIDATES is non-empty");
for y in 0..4 {
for x in 0..4 {
let border_index = (y0 + y) * stride + x0 + x;
let p = i32::from(predicted_block[y * 4 + x]);
y_with_border[border_index] =
(p + dequantized[y * 4 + x]).clamp(0, 255) as u8;
}
}
chosen_modes[i] = mode;
y_coeffs[i * 16..][..16].copy_from_slice(&quantized);
total_distortion += distortion;
total_rate += rate;
left = mode;
top_ctx[sbx] = mode;
}
}
(total_distortion, total_rate, chosen_modes, y_coeffs)
}
fn trial_chroma(
&self,
chroma_mode: ChromaMode,
mbx: usize,
mby: usize,
segment: &Segment,
) -> (i64, i64, ChromaCoeffs, ChromaCoeffs) {
let mut predicted_u = self.get_predicted_chroma_block(
chroma_mode,
mbx,
mby,
&self.top_border_u,
&self.left_border_u,
);
let mut predicted_v = self.get_predicted_chroma_block(
chroma_mode,
mbx,
mby,
&self.top_border_v,
&self.left_border_v,
);
let u_blocks =
self.get_chroma_blocks_from_predicted(&predicted_u, &self.frame.ubuf, mbx, mby);
let v_blocks =
self.get_chroma_blocks_from_predicted(&predicted_v, &self.frame.vbuf, mbx, mby);
let u_coeffs = self.get_chroma_block_coeffs(u_blocks, segment);
let v_coeffs = self.get_chroma_block_coeffs(v_blocks, segment);
let dequantized_u = self.get_dequantized_blocks_from_coeffs_chroma(&u_coeffs, segment);
let dequantized_v = self.get_dequantized_blocks_from_coeffs_chroma(&v_coeffs, segment);
for y in 0usize..2 {
for x in 0usize..2 {
let i = x + y * 2;
let urb: &[i32; 16] = dequantized_u[i * 16..][..16].try_into().unwrap();
add_residue(&mut predicted_u, urb, 1 + y * 4, 1 + x * 4, CHROMA_STRIDE);
let vrb: &[i32; 16] = dequantized_v[i * 16..][..16].try_into().unwrap();
add_residue(&mut predicted_v, vrb, 1 + y * 4, 1 + x * 4, CHROMA_STRIDE);
}
}
let distortion = self.chroma_sse(&predicted_u, &self.frame.ubuf, mbx, mby)
+ self.chroma_sse(&predicted_v, &self.frame.vbuf, mbx, mby);
let rate = coeff_rate_estimate(&u_coeffs) + coeff_rate_estimate(&v_coeffs);
(distortion, rate, u_coeffs, v_coeffs)
}
fn chroma_sse(
&self,
recon: &[u8; CHROMA_BLOCK_SIZE],
plane: &[u8],
mbx: usize,
mby: usize,
) -> i64 {
let stride = CHROMA_STRIDE;
let chroma_width = usize::from(self.macroblock_width) * 8;
let mut sse: i64 = 0;
for y in 0..8 {
for x in 0..8 {
let r = i64::from(recon[(y + 1) * stride + (x + 1)]);
let a = i64::from(plane[(mby * 8 + y) * chroma_width + mbx * 8 + x]);
let d = r - a;
sse += d * d;
}
}
sse
}
fn count_skipped_macroblocks(&mut self) -> (u32, u32) {
let mut total = 0u32;
let mut skipped = 0u32;
for mby in 0..self.macroblock_height {
self.left_complexity = Complexity::default();
self.left_b_pred = [IntraMode::default(); 4];
self.left_border_y = [129u8; 16 + 1];
self.left_border_u = [129u8; 8 + 1];
self.left_border_v = [129u8; 8 + 1];
for mbx in 0..self.macroblock_width {
let info = self.choose_macroblock_info(mbx.into(), mby.into());
self.advance_bpred_context(&info, mbx.into());
self.transform_luma_block(mbx.into(), mby.into(), &info);
self.transform_chroma_blocks(mbx.into(), mby.into(), &info);
let idx = usize::from(mby) * usize::from(self.macroblock_width) + usize::from(mbx);
self.mb_info_cache[idx] = Some(info);
total += 1;
if info.coeffs_skipped {
skipped += 1;
}
}
}
(skipped, total)
}
fn collect_token_counts(&mut self) -> TokenCounts {
let mut counts: TokenCounts = [[[[[0u64; 2]; NUM_DCT_TOKENS - 1]; 3]; 8]; 4];
for mby in 0..self.macroblock_height {
self.left_complexity = Complexity::default();
self.left_b_pred = [IntraMode::default(); 4];
self.left_border_y = [129u8; 16 + 1];
self.left_border_u = [129u8; 8 + 1];
self.left_border_v = [129u8; 8 + 1];
for mbx in 0..self.macroblock_width {
let mbx = usize::from(mbx);
let mby = usize::from(mby);
let idx = mby * usize::from(self.macroblock_width) + mbx;
let info = self.mb_info_cache[idx].expect(
"count_skipped_macroblocks populates every macroblock's cached mode \
decision before collect_token_counts runs",
);
self.advance_bpred_context(&info, mbx);
let y_block_data = self.transform_luma_block(mbx, mby, &info);
let (u_block_data, v_block_data) = self.transform_chroma_blocks(mbx, mby, &info);
if info.coeffs_skipped {
self.left_complexity.clear(info.luma_mode != LumaMode::B);
self.top_complexity[mbx].clear(info.luma_mode != LumaMode::B);
continue;
}
self.accumulate_residual_token_counts(
&info,
mbx,
&y_block_data,
&u_block_data,
&v_block_data,
&mut counts,
);
}
}
counts
}
fn accumulate_residual_token_counts(
&mut self,
macroblock_info: &MacroblockInfo,
mbx: usize,
y_block_data: &[i32; 16 * 16],
u_block_data: &[i32; 16 * 4],
v_block_data: &[i32; 16 * 4],
counts: &mut TokenCounts,
) {
let mut plane = if macroblock_info.luma_mode == LumaMode::B {
Plane::YCoeff0
} else {
Plane::Y2
};
let segment = self.segments[macroblock_info.segment_id.unwrap_or(0)];
if plane == Plane::Y2 {
let mut coeffs0 = get_coeffs0_from_block(y_block_data);
transform::wht4x4(&mut coeffs0);
let complexity = self.left_complexity.y2 + self.top_complexity[mbx].y2;
let (events, has_coeffs) = tokenize_block(
&coeffs0,
Plane::Y2,
complexity.into(),
segment.y2dc,
segment.y2ac,
);
accumulate_token_events(counts, Plane::Y2, &events);
self.left_complexity.y2 = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].y2 = if has_coeffs { 1 } else { 0 };
plane = Plane::YCoeff1;
}
for y in 0usize..4 {
let mut left = self.left_complexity.y[y];
for x in 0..4 {
let block = y_block_data[y * 4 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].y[x];
let complexity = left + top;
let (events, has_coeffs) =
tokenize_block(block, plane, complexity.into(), segment.ydc, segment.yac);
accumulate_token_events(counts, plane, &events);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].y[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.y[y] = left;
}
plane = Plane::Chroma;
for y in 0usize..2 {
let mut left = self.left_complexity.u[y];
for x in 0usize..2 {
let block = u_block_data[y * 2 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].u[x];
let complexity = left + top;
let (events, has_coeffs) =
tokenize_block(block, plane, complexity.into(), segment.uvdc, segment.uvac);
accumulate_token_events(counts, plane, &events);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].u[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.u[y] = left;
}
for y in 0usize..2 {
let mut left = self.left_complexity.v[y];
for x in 0usize..2 {
let block = v_block_data[y * 2 * 16 + x * 16..][..16]
.try_into()
.unwrap();
let top = self.top_complexity[mbx].v[x];
let complexity = left + top;
let (events, has_coeffs) =
tokenize_block(block, plane, complexity.into(), segment.uvdc, segment.uvac);
accumulate_token_events(counts, plane, &events);
left = if has_coeffs { 1 } else { 0 };
self.top_complexity[mbx].v[x] = if has_coeffs { 1 } else { 0 };
}
self.left_complexity.v[y] = left;
}
}
fn macroblock_luma_variance(&self, mbx: usize, mby: usize) -> f64 {
let width = usize::from(self.macroblock_width) * 16;
let mut sum: i64 = 0;
let mut sum_sq: i64 = 0;
for y in 0..16 {
let row_start = (mby * 16 + y) * width + mbx * 16;
for x in 0..16 {
let v = i64::from(self.frame.ybuf[row_start + x]);
sum += v;
sum_sq += v * v;
}
}
const N: f64 = 256.0;
let mean = sum as f64 / N;
let mean_sq = sum_sq as f64 / N;
(mean_sq - mean * mean).max(0.0)
}
fn classify_segments(&self) -> Vec<u8> {
let mb_w = usize::from(self.macroblock_width);
let mb_h = usize::from(self.macroblock_height);
let n = mb_w * mb_h;
let variances: Vec<f64> = (0..n)
.map(|i| self.macroblock_luma_variance(i % mb_w, i / mb_w))
.collect();
let mut by_variance: Vec<usize> = (0..n).collect();
by_variance.sort_by(|&a, &b| variances[a].total_cmp(&variances[b]));
let mut segment_ids = vec![0u8; n];
for (rank, &mb_index) in by_variance.iter().enumerate() {
let quartile = rank * MAX_SEGMENTS / n.max(1);
segment_ids[mb_index] = quartile.min(MAX_SEGMENTS - 1) as u8;
}
segment_ids
}
fn setup_encoding(
&mut self,
lossy_quality: u8,
width: u16,
height: u16,
y_buf: Vec<u8>,
u_buf: Vec<u8>,
v_buf: Vec<u8>,
) {
let mb_width = width.div_ceil(16);
let mb_height = height.div_ceil(16);
self.macroblock_width = mb_width;
self.macroblock_height = mb_height;
self.frame = Frame {
width,
height,
ybuf: y_buf,
ubuf: u_buf,
vbuf: v_buf,
version: 0,
keyframe: true,
for_display: true,
pixel_type: 0,
filter_type: false,
filter_level: 0,
sharpness_level: 7,
};
self.token_probs = COEFF_PROBS;
if lossy_quality > 100 {
panic!("lossy quality must be between 0 and 100");
}
let quant_index: u8 = (127 - u16::from(lossy_quality) * 127 / 100) as u8;
self.quantization_indices = QuantizationIndices {
yac_abs: quant_index,
..Default::default()
};
self.segments_enabled = true;
self.mb_segment_ids = self.classify_segments();
for (segment, &delta) in self.segments.iter_mut().zip(SEGMENT_QUANT_DELTAS.iter()) {
*segment = build_segment(quant_index, delta, &self.quantization_indices);
}
self.segment_tree_probs = segment_tree_probs_for(&self.mb_segment_ids);
self.mb_info_cache = vec![None; usize::from(mb_width) * usize::from(mb_height)];
self.reset_frame_state();
}
fn reset_frame_state(&mut self) {
let mb_width = self.macroblock_width;
self.top_complexity = vec![Complexity::default(); usize::from(mb_width)];
self.top_b_pred = vec![IntraMode::default(); 4 * usize::from(mb_width)];
self.left_b_pred = [IntraMode::default(); 4];
self.left_border_y = [129u8; 16 + 1];
self.left_border_u = [129u8; 8 + 1];
self.left_border_v = [129u8; 8 + 1];
self.top_border_y = vec![127u8; usize::from(mb_width) * 16 + 4];
self.top_border_u = vec![127u8; usize::from(mb_width) * 8];
self.top_border_v = vec![127u8; usize::from(mb_width) * 8];
}
fn get_predicted_luma_block_16x16(
&self,
luma_mode: LumaMode,
mbx: usize,
mby: usize,
) -> [u8; LUMA_BLOCK_SIZE] {
let stride = LUMA_STRIDE;
let mbw = self.macroblock_width;
let mut y_with_border = create_border_luma(
mbx,
mby,
mbw.into(),
&self.top_border_y,
&self.left_border_y,
);
match luma_mode {
LumaMode::V => predict_vpred(&mut y_with_border, 16, 1, 1, stride),
LumaMode::H => predict_hpred(&mut y_with_border, 16, 1, 1, stride),
LumaMode::TM => predict_tmpred(&mut y_with_border, 16, 1, 1, stride),
LumaMode::DC => predict_dcpred(&mut y_with_border, 16, stride, mby != 0, mbx != 0),
LumaMode::B => unreachable!(),
}
y_with_border
}
fn get_luma_blocks_from_predicted_16x16(
&self,
predicted_y_block: &[u8; LUMA_BLOCK_SIZE],
mbx: usize,
mby: usize,
) -> [i32; 16 * 16] {
let stride = LUMA_STRIDE;
let width = usize::from(self.macroblock_width * 16);
let mut luma_blocks = [0i32; 16 * 16];
for block_y in 0..4 {
for block_x in 0..4 {
let block_index = block_y * 16 * 4 + block_x * 16;
let border_block_index = (block_y * 4 + 1) * stride + block_x * 4 + 1;
let y_data_block_index = (mby * 16 + block_y * 4) * width + mbx * 16 + block_x * 4;
let mut block = [0i32; 16];
for y in 0..4 {
for x in 0..4 {
let predicted_index = border_block_index + y * stride + x;
let predicted_value = predicted_y_block[predicted_index];
let actual_index = y_data_block_index + y * width + x;
let actual_value = self.frame.ybuf[actual_index];
block[y * 4 + x] = i32::from(actual_value) - i32::from(predicted_value);
}
}
transform::dct4x4(&mut block);
luma_blocks[block_index..][..16].copy_from_slice(&block);
}
}
luma_blocks
}
fn get_luma_block_coeffs_16x16(
&self,
mut luma_blocks: [i32; 16 * 16],
segment: &Segment,
) -> Luma16x16Coeffs {
let mut coeffs0 = get_coeffs0_from_block(&luma_blocks);
transform::wht4x4(&mut coeffs0);
for (index, value) in coeffs0.iter_mut().enumerate() {
let quant = if index > 0 {
segment.y2ac
} else {
segment.y2dc
};
*value /= i32::from(quant);
}
for y_block in luma_blocks.chunks_exact_mut(16) {
for (index, y_value) in y_block.iter_mut().enumerate() {
if index == 0 {
*y_value = 0;
} else {
*y_value /= i32::from(segment.yac);
}
}
}
Luma16x16Coeffs {
y2_coeffs: coeffs0,
y_coeffs: luma_blocks,
}
}
fn get_dequantized_blocks_from_coeffs_luma_16x16(
&self,
coeffs: &mut Luma16x16Coeffs,
segment: &Segment,
) -> [i32; 16 * 16] {
let mut dequantized_luma_residue = [0i32; 16 * 16];
for (k, y2_coeff) in coeffs.y2_coeffs.iter_mut().enumerate() {
let quant = if k > 0 { segment.y2ac } else { segment.y2dc };
*y2_coeff *= i32::from(quant);
}
transform::iwht4x4(&mut coeffs.y2_coeffs);
for (k, luma_block) in coeffs.y_coeffs.chunks_exact_mut(16).enumerate() {
for y_value in luma_block[1..].iter_mut() {
*y_value *= i32::from(segment.yac);
}
luma_block[0] = coeffs.y2_coeffs[k];
transform::idct4x4(luma_block);
dequantized_luma_residue[k * 16..][..16].copy_from_slice(luma_block);
}
dequantized_luma_residue
}
fn transform_luma_block(
&mut self,
mbx: usize,
mby: usize,
macroblock_info: &MacroblockInfo,
) -> [i32; 16 * 16] {
let segment = self.segments[macroblock_info.segment_id.unwrap_or(0)];
if macroblock_info.luma_mode == LumaMode::B {
if let Some(bpred_modes) = macroblock_info.luma_bpred {
return self.transform_luma_blocks_4x4(bpred_modes, mbx, mby, &segment);
} else {
panic!("Invalid, need bpred modes for luma mode B");
}
}
let mut y_with_border =
self.get_predicted_luma_block_16x16(macroblock_info.luma_mode, mbx, mby);
let luma_blocks = self.get_luma_blocks_from_predicted_16x16(&y_with_border, mbx, mby);
let mut coeffs = self.get_luma_block_coeffs_16x16(luma_blocks, &segment);
let dequantized_blocks =
self.get_dequantized_blocks_from_coeffs_luma_16x16(&mut coeffs, &segment);
for y in 0usize..4 {
for x in 0usize..4 {
let i = x + y * 4;
let rb: &[i32; 16] = dequantized_blocks[i * 16..][..16].try_into().unwrap();
let y0 = 1 + y * 4;
let x0 = 1 + x * 4;
add_residue(&mut y_with_border, rb, y0, x0, LUMA_STRIDE);
}
}
for (y, border_value) in self.left_border_y.iter_mut().enumerate() {
*border_value = y_with_border[y * LUMA_STRIDE + 16];
}
for (x, border_value) in self.top_border_y[mbx * 16..][..16].iter_mut().enumerate() {
*border_value = y_with_border[16 * LUMA_STRIDE + x + 1];
}
luma_blocks
}
fn transform_luma_blocks_4x4(
&mut self,
bpred_modes: [IntraMode; 16],
mbx: usize,
mby: usize,
segment: &Segment,
) -> [i32; 16 * 16] {
let mut luma_blocks = [0i32; 16 * 16];
let stride = 1usize + 16 + 4;
let mbw = self.macroblock_width;
let width = usize::from(mbw * 16);
let mut y_with_border = create_border_luma(
mbx,
mby,
mbw.into(),
&self.top_border_y,
&self.left_border_y,
);
for sby in 0usize..4 {
for sbx in 0usize..4 {
let i = sby * 4 + sbx;
let y0 = sby * 4 + 1;
let x0 = sbx * 4 + 1;
match bpred_modes[i] {
IntraMode::TM => predict_tmpred(&mut y_with_border, 4, x0, y0, stride),
IntraMode::VE => predict_bvepred(&mut y_with_border, x0, y0, stride),
IntraMode::HE => predict_bhepred(&mut y_with_border, x0, y0, stride),
IntraMode::DC => predict_bdcpred(&mut y_with_border, x0, y0, stride),
IntraMode::LD => predict_bldpred(&mut y_with_border, x0, y0, stride),
IntraMode::RD => predict_brdpred(&mut y_with_border, x0, y0, stride),
IntraMode::VR => predict_bvrpred(&mut y_with_border, x0, y0, stride),
IntraMode::VL => predict_bvlpred(&mut y_with_border, x0, y0, stride),
IntraMode::HD => predict_bhdpred(&mut y_with_border, x0, y0, stride),
IntraMode::HU => predict_bhupred(&mut y_with_border, x0, y0, stride),
}
let block_index = sby * 16 * 4 + sbx * 16;
let mut current_subblock = [0i32; 16];
let border_subblock_index = y0 * stride + x0;
let y_data_block_index = (mby * 16 + sby * 4) * width + mbx * 16 + sbx * 4;
for y in 0..4 {
for x in 0..4 {
let predicted_index = border_subblock_index + y * stride + x;
let predicted_value = y_with_border[predicted_index];
let actual_index = y_data_block_index + y * width + x;
let actual_value = self.frame.ybuf[actual_index];
current_subblock[y * 4 + x] =
i32::from(actual_value) - i32::from(predicted_value);
}
}
transform::dct4x4(&mut current_subblock);
luma_blocks[block_index..][..16].copy_from_slice(¤t_subblock);
for (index, y_value) in current_subblock.iter_mut().enumerate() {
let quant = if index > 0 { segment.yac } else { segment.ydc };
*y_value = (*y_value / i32::from(quant)) * i32::from(quant);
}
transform::idct4x4(&mut current_subblock);
add_residue(&mut y_with_border, ¤t_subblock, y0, x0, stride);
}
}
for (y, border_value) in self.left_border_y.iter_mut().enumerate() {
*border_value = y_with_border[y * stride + 16];
}
for (x, border_value) in self.top_border_y[mbx * 16..][..16].iter_mut().enumerate() {
*border_value = y_with_border[16 * stride + x + 1];
}
luma_blocks
}
fn get_predicted_chroma_block(
&self,
chroma_mode: ChromaMode,
mbx: usize,
mby: usize,
top_border: &[u8],
left_border: &[u8],
) -> [u8; CHROMA_BLOCK_SIZE] {
let mut chroma_with_border = create_border_chroma(mbx, mby, top_border, left_border);
match chroma_mode {
ChromaMode::DC => {
predict_dcpred(
&mut chroma_with_border,
8,
CHROMA_STRIDE,
mby != 0,
mbx != 0,
);
}
ChromaMode::V => {
predict_vpred(&mut chroma_with_border, 8, 1, 1, CHROMA_STRIDE);
}
ChromaMode::H => {
predict_hpred(&mut chroma_with_border, 8, 1, 1, CHROMA_STRIDE);
}
ChromaMode::TM => {
predict_tmpred(&mut chroma_with_border, 8, 1, 1, CHROMA_STRIDE);
}
}
chroma_with_border
}
fn get_chroma_blocks_from_predicted(
&self,
predicted_chroma: &[u8; CHROMA_BLOCK_SIZE],
chroma_data: &[u8],
mbx: usize,
mby: usize,
) -> [i32; 16 * 4] {
let mut chroma_blocks = [0i32; 16 * 4];
let stride = CHROMA_STRIDE;
let chroma_width = usize::from(self.macroblock_width * 8);
for block_y in 0..2 {
for block_x in 0..2 {
let block_index = block_y * 16 * 2 + block_x * 16;
let border_block_index = (block_y * 4 + 1) * stride + block_x * 4 + 1;
let chroma_data_block_index =
(mby * 8 + block_y * 4) * chroma_width + mbx * 8 + block_x * 4;
let mut chroma_block = [0i32; 16];
for y in 0..4 {
for x in 0..4 {
let predicted_index = border_block_index + y * stride + x;
let predicted_value = predicted_chroma[predicted_index];
let actual_index = chroma_data_block_index + y * chroma_width + x;
let actual_value = chroma_data[actual_index];
chroma_block[y * 4 + x] =
i32::from(actual_value) - i32::from(predicted_value);
}
}
transform::dct4x4(&mut chroma_block);
chroma_blocks[block_index..][..16].copy_from_slice(&chroma_block);
}
}
chroma_blocks
}
fn get_chroma_block_coeffs(
&self,
chroma_blocks: [i32; 16 * 4],
segment: &Segment,
) -> ChromaCoeffs {
let mut chroma_coeffs: ChromaCoeffs = [0i32; 16 * 4];
for (block, coeff_block) in chroma_blocks
.chunks_exact(16)
.zip(chroma_coeffs.chunks_exact_mut(16))
{
for ((index, &value), coeff) in block.iter().enumerate().zip(coeff_block.iter_mut()) {
let quant = if index > 0 {
segment.uvac
} else {
segment.uvdc
};
*coeff = value / i32::from(quant);
}
}
chroma_coeffs
}
fn get_dequantized_blocks_from_coeffs_chroma(
&self,
chroma_coeffs: &ChromaCoeffs,
segment: &Segment,
) -> [i32; 16 * 4] {
let mut dequantized_blocks = [0i32; 16 * 4];
for (coeffs_block, dequant_block) in chroma_coeffs
.chunks_exact(16)
.zip(dequantized_blocks.chunks_exact_mut(16))
{
for ((index, &coeff), dequant_value) in coeffs_block
.iter()
.enumerate()
.zip(dequant_block.iter_mut())
{
let quant = if index > 0 {
segment.uvac
} else {
segment.uvdc
};
*dequant_value = coeff * i32::from(quant);
}
transform::idct4x4(dequant_block);
}
dequantized_blocks
}
fn transform_chroma_blocks(
&mut self,
mbx: usize,
mby: usize,
macroblock_info: &MacroblockInfo,
) -> ([i32; 16 * 4], [i32; 16 * 4]) {
let stride = CHROMA_STRIDE;
let chroma_mode = macroblock_info.chroma_mode;
let segment = self.segments[macroblock_info.segment_id.unwrap_or(0)];
let mut predicted_u = self.get_predicted_chroma_block(
chroma_mode,
mbx,
mby,
&self.top_border_u,
&self.left_border_u,
);
let mut predicted_v = self.get_predicted_chroma_block(
chroma_mode,
mbx,
mby,
&self.top_border_v,
&self.left_border_v,
);
let u_blocks =
self.get_chroma_blocks_from_predicted(&predicted_u, &self.frame.ubuf, mbx, mby);
let v_blocks =
self.get_chroma_blocks_from_predicted(&predicted_v, &self.frame.vbuf, mbx, mby);
let u_coeffs = self.get_chroma_block_coeffs(u_blocks, &segment);
let v_coeffs = self.get_chroma_block_coeffs(v_blocks, &segment);
let quantized_u_residue =
self.get_dequantized_blocks_from_coeffs_chroma(&u_coeffs, &segment);
let quantized_v_residue =
self.get_dequantized_blocks_from_coeffs_chroma(&v_coeffs, &segment);
for y in 0usize..2 {
for x in 0usize..2 {
let i = x + y * 2;
let urb: &[i32; 16] = quantized_u_residue[i * 16..][..16].try_into().unwrap();
let y0 = 1 + y * 4;
let x0 = 1 + x * 4;
add_residue(&mut predicted_u, urb, y0, x0, stride);
let vrb: &[i32; 16] = quantized_v_residue[i * 16..][..16].try_into().unwrap();
add_residue(&mut predicted_v, vrb, y0, x0, stride);
}
}
for ((y, u_border_value), v_border_value) in self
.left_border_u
.iter_mut()
.enumerate()
.zip(self.left_border_v.iter_mut())
{
*u_border_value = predicted_u[y * stride + 8];
*v_border_value = predicted_v[y * stride + 8];
}
for ((x, u_border_value), v_border_value) in self.top_border_u[mbx * 8..][..8]
.iter_mut()
.enumerate()
.zip(self.top_border_v[mbx * 8..][..8].iter_mut())
{
*u_border_value = predicted_u[8 * stride + x + 1];
*v_border_value = predicted_v[8 * stride + x + 1];
}
(u_blocks, v_blocks)
}
}
fn get_coeffs0_from_block(blocks: &[i32; 16 * 16]) -> [i32; 16] {
let mut coeffs0 = [0i32; 16];
for (coeff, first_coeff_value) in coeffs0.iter_mut().zip(blocks.iter().step_by(16)) {
*coeff = *first_coeff_value;
}
coeffs0
}
pub(crate) fn encode_frame_lossy<W: Write>(
writer: W,
data: &[u8],
width: u32,
height: u32,
color: ColorType,
lossy_quality: u8,
) -> Result<(), EncodingError> {
let mut vp8_encoder = Vp8Encoder::new(writer);
let width = width
.try_into()
.map_err(|_| EncodingError::InvalidDimensions)?;
let height = height
.try_into()
.map_err(|_| EncodingError::InvalidDimensions)?;
vp8_encoder.encode_image(data, color, width, height, lossy_quality)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn encoder_with_top_context(mb_width: u16) -> Vp8Encoder<Vec<u8>> {
let mut encoder = Vp8Encoder::new(Vec::new());
encoder.macroblock_width = mb_width;
encoder.top_b_pred = vec![IntraMode::default(); 4 * usize::from(mb_width)];
encoder.left_b_pred = [IntraMode::default(); 4];
encoder
}
fn sample_macroblock_infos() -> Vec<MacroblockInfo> {
vec![
MacroblockInfo {
luma_mode: LumaMode::DC,
luma_bpred: None,
chroma_mode: ChromaMode::DC,
segment_id: Some(0),
coeffs_skipped: false,
},
MacroblockInfo {
luma_mode: LumaMode::TM,
luma_bpred: None,
chroma_mode: ChromaMode::V,
segment_id: Some(1),
coeffs_skipped: true,
},
MacroblockInfo {
luma_mode: LumaMode::B,
luma_bpred: Some([
IntraMode::DC,
IntraMode::TM,
IntraMode::VE,
IntraMode::HE,
IntraMode::LD,
IntraMode::RD,
IntraMode::VR,
IntraMode::VL,
IntraMode::HD,
IntraMode::HU,
IntraMode::DC,
IntraMode::TM,
IntraMode::VE,
IntraMode::HE,
IntraMode::LD,
IntraMode::RD,
]),
chroma_mode: ChromaMode::H,
segment_id: Some(2),
coeffs_skipped: false,
},
MacroblockInfo {
luma_mode: LumaMode::B,
luma_bpred: Some([IntraMode::HU; 16]),
chroma_mode: ChromaMode::TM,
segment_id: Some(3),
coeffs_skipped: false,
},
MacroblockInfo {
luma_mode: LumaMode::H,
luma_bpred: None,
chroma_mode: ChromaMode::DC,
segment_id: Some(0),
coeffs_skipped: false,
},
]
}
#[test]
fn advance_bpred_context_matches_write_macroblock_header() {
let mb_width = 6u16;
for mbx in 0..usize::from(mb_width) {
let mut via_write = encoder_with_top_context(mb_width);
let mut via_advance = encoder_with_top_context(mb_width);
for info in sample_macroblock_infos() {
via_write.write_macroblock_header(&info, mbx);
via_advance.advance_bpred_context(&info, mbx);
assert_eq!(
via_write.top_b_pred, via_advance.top_b_pred,
"top_b_pred diverged for mbx={mbx}"
);
assert_eq!(
via_write.left_b_pred, via_advance.left_b_pred,
"left_b_pred diverged for mbx={mbx}"
);
}
}
}
struct Xorshift32(u32);
impl Xorshift32 {
fn next_u8(&mut self) -> u8 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.0 = x;
(x & 0xff) as u8
}
}
fn flat_rgb(width: u16, height: u16) -> Vec<u8> {
let mut pixels = vec![0u8; usize::from(width) * usize::from(height) * 3];
for chunk in pixels.chunks_exact_mut(3) {
chunk.copy_from_slice(&[60, 120, 180]);
}
pixels
}
fn noisy_rgb(width: u16, height: u16) -> Vec<u8> {
let mut rng = Xorshift32(0x2222_1111 ^ (u32::from(width) << 16) ^ u32::from(height));
let mut pixels = vec![0u8; usize::from(width) * usize::from(height) * 3];
for b in pixels.iter_mut() {
*b = rng.next_u8();
}
pixels
}
fn mixed_rgb(width: u16, height: u16) -> Vec<u8> {
let mut pixels = vec![0u8; usize::from(width) * usize::from(height) * 3];
for y in 0..height {
for x in 0..width {
let i = (usize::from(y) * usize::from(width) + usize::from(x)) * 3;
let (r, g, b) = if x < width / 2 {
let v = (u32::from(x) * 255 / u32::from(width.max(1))) as u8;
(v, v, v)
} else if (x / 4 + y / 4) % 2 == 0 {
(20, 20, 20)
} else {
(235, 235, 235)
};
pixels[i] = r;
pixels[i + 1] = g;
pixels[i + 2] = b;
}
}
pixels
}
fn independent_real_pass_infos(encoder: &mut Vp8Encoder<Vec<u8>>) -> Vec<MacroblockInfo> {
let mut infos = Vec::new();
for mby in 0..encoder.macroblock_height {
encoder.left_complexity = Complexity::default();
encoder.left_b_pred = [IntraMode::default(); 4];
encoder.left_border_y = [129u8; 16 + 1];
encoder.left_border_u = [129u8; 8 + 1];
encoder.left_border_v = [129u8; 8 + 1];
for mbx in 0..encoder.macroblock_width {
let info = encoder.choose_macroblock_info(mbx.into(), mby.into());
encoder.write_macroblock_header(&info, mbx.into());
let y_block_data = encoder.transform_luma_block(mbx.into(), mby.into(), &info);
let (u_block_data, v_block_data) =
encoder.transform_chroma_blocks(mbx.into(), mby.into(), &info);
if !info.coeffs_skipped {
encoder.encode_residual_data(
&info,
0,
mbx.into(),
&y_block_data,
&u_block_data,
&v_block_data,
);
} else {
encoder.left_complexity.clear(info.luma_mode != LumaMode::B);
encoder.top_complexity[usize::from(mbx)].clear(info.luma_mode != LumaMode::B);
}
infos.push(info);
}
}
infos
}
type ModeAgreementFixture = (&'static str, u16, u16, u8, fn(u16, u16) -> Vec<u8>);
#[test]
fn all_three_passes_agree_on_mode_decisions() {
let fixtures: [ModeAgreementFixture; 3] = [
("flat", 32, 32, 70, flat_rgb),
("noisy", 48, 32, 60, noisy_rgb),
("mixed", 64, 48, 75, mixed_rgb),
];
for (name, width, height, quality, make_pixels) in fixtures {
let pixels = make_pixels(width, height);
let (y_bytes, u_bytes, v_bytes) = convert_image_yuv::<3>(&pixels, width, height);
let mut encoder = Vp8Encoder::new(Vec::new());
encoder.setup_encoding(quality, width, height, y_bytes, u_bytes, v_bytes);
encoder.count_skipped_macroblocks();
let pass1: Vec<MacroblockInfo> = encoder
.mb_info_cache
.iter()
.map(|info| info.expect("pass 1 fills every entry"))
.collect();
encoder.reset_frame_state();
encoder.collect_token_counts();
let pass2: Vec<MacroblockInfo> = encoder
.mb_info_cache
.iter()
.map(|info| info.expect("pass 1 fills every entry"))
.collect();
encoder.reset_frame_state();
let pass3 = independent_real_pass_infos(&mut encoder);
assert_eq!(pass1, pass2, "fixture '{name}': pass 1/2 disagreed");
assert_eq!(pass1, pass3, "fixture '{name}': pass 1/3 disagreed");
}
}
}