From 7cb65894212b51b13b8ac2e30f9a520627938a3e Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sat, 12 Mar 2022 17:21:22 +0100 Subject: [PATCH] vp6enc: add fast(er) encoding mode --- nihav-duck/src/codecs/vp6enc/mb.rs | 146 ++++++++++++++++++++++++++++ nihav-duck/src/codecs/vp6enc/mod.rs | 56 ++++++++++- 2 files changed, 200 insertions(+), 2 deletions(-) diff --git a/nihav-duck/src/codecs/vp6enc/mb.rs b/nihav-duck/src/codecs/vp6enc/mb.rs index c7e5003..2d460f1 100644 --- a/nihav-duck/src/codecs/vp6enc/mb.rs +++ b/nihav-duck/src/codecs/vp6enc/mb.rs @@ -505,6 +505,152 @@ impl FrameEncoder { } } } + fn motion_est_mb(src_mb: &ResidueMB, cur_blk: &mut [[u8; 64]; 6], mb: &mut InterMB, mv_search: &mut Box, mv_est: &mut MVEstimator, mb_x: usize, mb_y: usize) { + src_mb.fill(cur_blk); + let (best_mv, _best_dist) = mv_search.search_mb(mv_est, &cur_blk, mb_x, mb_y); + mb.mv[3] = best_mv; + + for i in 0..4 { + mv_est.mc_block(i, 0, mb_x * 16 + (i & 1) * 8, mb_y * 16 + (i >> 1) * 8, best_mv); + sub_blk(&mut mb.residue.coeffs[i], &cur_blk[i], &mv_est.ref_blk[i]); + } + for plane in 1..3 { + mv_est.mc_block(plane + 3, plane, mb_x * 8, mb_y * 8, best_mv); + sub_blk(&mut mb.residue.coeffs[plane + 3], &cur_blk[plane + 3], &mv_est.ref_blk[plane + 3]); + } + + for (dblk, sblk) in mb.reference.iter_mut().zip(mv_est.ref_blk.iter()) { + for (dst, &src) in dblk.iter_mut().zip(sblk.iter()) { + *dst = i16::from(src); + } + } + } + pub fn select_inter_blocks_fast(&mut self, ref_frame: NAVideoBufferRef, gold_frame: Option>, mc_buf: NAVideoBufferRef, lambda: f32) { + let loop_thr = i16::from(VP56_FILTER_LIMITS[self.quant as usize]); + + if self.inter_mbs.is_empty() { + for _ in 0..self.mb_w * self.mb_h { + self.inter_mbs.push(InterMB::new()); + } + } + if self.golden_mbs.is_empty() { + for _ in 0..self.mb_w * self.mb_h { + self.golden_mbs.push(InterMB::new()); + } + } + + let mut cur_blk = [[0u8; 64]; 6]; + + let mut mv_est = MVEstimator::new(ref_frame.clone(), mc_buf.clone(), loop_thr, self.me_range); + let mut mv_est_g = if let Some(gold_frm) = gold_frame { + Some(MVEstimator::new(gold_frm, mc_buf.clone(), loop_thr, self.me_range)) + } else { + None + }; + + let mut mv_search = self.me_mode.create_search(); + + let mut tmp_mb = ResidueMB::new(); + + let mut mb_idx = 0; + for mb_y in 0..self.mb_h { + for mb_x in 0..self.mb_w { + let smb = &self.src_mbs[mb_idx]; + + let inter_mb = &mut self.inter_mbs[mb_idx]; + Self::motion_est_mb(smb, &mut cur_blk, inter_mb, &mut mv_search, &mut mv_est, mb_x, mb_y); + inter_mb.residue.fdct(); + inter_mb.residue.quant(self.quant); + self.mb_types[mb_idx] = VPMBType::InterMV; + + tmp_mb.dequant_from(&inter_mb.residue, self.quant); + tmp_mb.idct(); + for (blk, res) in tmp_mb.coeffs.iter_mut().zip(inter_mb.reference.iter()) { + for (coef, add) in blk.iter_mut().zip(res.iter()) { + *coef = (*coef + add).max(0).min(255); + } + } + let mut best_dist = calc_mb_dist(smb, &tmp_mb); + let mut inter_nits = estimate_inter_mb_nits(inter_mb, self.quant, false); + if inter_mb.mv[3] != ZERO_MV { + inter_nits += estimate_mv_nits(inter_mb.mv[3]); + } + let mut best_cost = (best_dist as f32) + lambda * (inter_nits as f32); + if best_dist > 512 { + self.estimate_fourmv(ref_frame.clone(), mc_buf.clone(), mb_idx % self.mb_w, mb_idx / self.mb_w); + self.fourmv_mbs[mb_idx].residue.fdct(); + self.fourmv_mbs[mb_idx].residue.quant(self.quant); + + let smb = &self.src_mbs[mb_idx]; + tmp_mb.dequant_from(&self.fourmv_mbs[mb_idx].residue, self.quant); + tmp_mb.idct(); + for (blk, res) in tmp_mb.coeffs.iter_mut().zip(self.fourmv_mbs[mb_idx].reference.iter()) { + for (coef, add) in blk.iter_mut().zip(res.iter()) { + *coef = (*coef + add).max(0).min(255); + } + } + let fourmv_dist = calc_mb_dist(smb, &tmp_mb); + let fourmv_nits = estimate_inter_mb_nits(&self.fourmv_mbs[mb_idx], self.quant, true); + let fourmv_cost = (fourmv_dist as f32) + lambda * (fourmv_nits as f32); + if fourmv_cost < best_cost { + self.mb_types[mb_idx] = VPMBType::InterFourMV; + best_cost = fourmv_cost; + best_dist = fourmv_dist; + } + } + let smb = &self.src_mbs[mb_idx]; + if best_dist > 512 { + if let Some(ref mut mve_gold) = mv_est_g { + let gold_mb = &mut self.golden_mbs[mb_idx]; + Self::motion_est_mb(smb, &mut cur_blk, gold_mb, &mut mv_search, mve_gold, mb_x, mb_y); + gold_mb.residue.fdct(); + gold_mb.residue.quant(self.quant); + + tmp_mb.dequant_from(&gold_mb.residue, self.quant); + tmp_mb.idct(); + for (blk, res) in tmp_mb.coeffs.iter_mut().zip(gold_mb.reference.iter()) { + for (coef, add) in blk.iter_mut().zip(res.iter()) { + *coef = (*coef + add).max(0).min(255); + } + } + let golden_dist = calc_mb_dist(smb, &tmp_mb); + let golden_nits = estimate_inter_mb_nits(gold_mb, self.quant, false); + let golden_cost = (golden_dist as f32) + lambda * (golden_nits as f32); + if golden_cost < best_cost { + self.mb_types[mb_idx] = VPMBType::GoldenMV; + best_cost = golden_cost; + best_dist = golden_dist; + } + } + } + if best_dist > 512 { + let intra_mb = &mut self.intra_mbs[mb_idx]; + *intra_mb = smb.clone(); + intra_mb.fdct(); + for blk in intra_mb.coeffs.iter_mut() { + blk[0] -= 4096; + } + intra_mb.quant(self.quant); + + tmp_mb.dequant_from(intra_mb, self.quant); + tmp_mb.idct(); + for blk in tmp_mb.coeffs.iter_mut() { + for coef in blk.iter_mut() { + *coef = (*coef + 128).max(0).min(255); + } + } + let intra_dist = calc_mb_dist(smb, &tmp_mb); + let intra_nits = estimate_intra_mb_nits(&intra_mb.coeffs, self.quant); + let intra_cost = (intra_dist as f32) + lambda * (intra_nits as f32); + if intra_cost < best_cost { + self.mb_types[mb_idx] = VPMBType::Intra; + } + } + + mb_idx += 1; + } + } + } pub fn decide_frame_type(&self) -> (bool, bool) { let mut intra_count = 0usize; let mut non_intra = 0usize; diff --git a/nihav-duck/src/codecs/vp6enc/mod.rs b/nihav-duck/src/codecs/vp6enc/mod.rs index 9f87054..b6ce632 100644 --- a/nihav-duck/src/codecs/vp6enc/mod.rs +++ b/nihav-duck/src/codecs/vp6enc/mod.rs @@ -189,6 +189,7 @@ struct VP6Encoder { me_range: i16, force_q: Option, + fast: bool, } impl VP6Encoder { @@ -225,6 +226,7 @@ impl VP6Encoder { me_range: 16, force_q: None, + fast: false, } } fn decide_encoding(&mut self) -> bool { @@ -482,6 +484,12 @@ impl VP6Encoder { let (_force_intra, golden_frame) = self.fenc.decide_frame_type(); self.fenc.apply_dc_prediction(&mut self.dc_pred); self.fenc.predict_mvs(); + + self.write_inter_frame(bw, quant, multistream, loop_filter, golden_frame)?; + + Ok(golden_frame) + } + fn write_inter_frame(&mut self, bw: &mut ByteWriter, quant: usize, multistream: bool, loop_filter: bool, golden_frame: bool) -> EncoderResult<()> { self.estimate_blocks(false); self.stats.generate(&mut self.models, false); @@ -560,7 +568,31 @@ impl VP6Encoder { VP6Writer::Huffman(HuffEncoder::new(bw)) }; self.encode_coeffs(writer)?; - Ok(golden_frame) + + Ok(()) + } + fn encode_inter_fast(&mut self, bw: &mut ByteWriter, quant: usize) -> EncoderResult { + self.stats.reset(); + + let multistream = self.huffman || self.version != VERSION_VP60; + let loop_filter = false; + + let last_frm = self.last_frame.get_vbuf().unwrap(); + let gold_frm = if !self.last_gold { + Some(self.gold_frame.get_vbuf().unwrap()) + } else { + None + }; + let lambda = if self.force_q.is_some() { 1.0 } else { self.ratectl.lambda }; + self.fenc.select_inter_blocks_fast(last_frm, gold_frm, self.mc_buf.clone(), lambda); + let golden_frame = false; + self.fenc.apply_dc_prediction(&mut self.dc_pred); + self.fenc.predict_mvs(); + self.estimate_blocks(false); + + self.write_inter_frame(bw, quant, multistream, loop_filter, golden_frame)?; + + Ok(false) } fn encode_coeffs(&mut self, mut writer: VP6Writer) -> EncoderResult<()> { if self.huffman { @@ -709,12 +741,14 @@ impl NAEncoder for VP6Encoder { self.fenc.me_range = self.me_range; let golden_frame = if is_intra { self.encode_intra(&mut bw, quant)? - } else { + } else if !self.fast { self.fenc.estimate_mvs(self.last_frame.get_vbuf().unwrap(), self.mc_buf.clone(), false); if !self.last_gold { self.fenc.estimate_mvs(self.gold_frame.get_vbuf().unwrap(), self.mc_buf.clone(), true); } self.encode_inter(&mut bw, quant)? + } else { + self.encode_inter_fast(&mut bw, quant)? }; self.fenc.reconstruct_frame(&mut self.dc_pred, self.last_frame.get_vbuf().unwrap()); self.last_gold = golden_frame; @@ -758,6 +792,7 @@ const QUANT_OPTION: &str = "quant"; const VERSION_OPTION: &str = "version"; const MV_SEARCH_OPTION: &str = "mv_mode"; const MV_RANGE_OPTION: &str = "mv_range"; +const FAST_OPTION: &str = "fast"; const ENCODER_OPTS: &[NAOptionDefinition] = &[ NAOptionDefinition { @@ -778,6 +813,9 @@ const ENCODER_OPTS: &[NAOptionDefinition] = &[ NAOptionDefinition { name: MV_RANGE_OPTION, description: "motion search range (in pixels)", opt_type: NAOptionDefinitionType::Int(Some(0), Some(30)) }, + NAOptionDefinition { + name: FAST_OPTION, description: "faster (but worse) encoding", + opt_type: NAOptionDefinitionType::Bool }, ]; impl NAOptionHandler for VP6Encoder { @@ -824,6 +862,11 @@ impl NAOptionHandler for VP6Encoder { self.me_range = intval as i16; } }, + FAST_OPTION => { + if let NAValue::Bool(bval) = option.value { + self.fast = bval; + } + }, _ => {}, }; } @@ -850,6 +893,7 @@ impl NAOptionHandler for VP6Encoder { }, MV_SEARCH_OPTION => Some(NAValue::String(self.me_mode.to_string())), MV_RANGE_OPTION => Some(NAValue::Int(i64::from(self.me_range))), + FAST_OPTION => Some(NAValue::Bool(self.fast)), _ => None, } } @@ -923,6 +967,14 @@ mod test { encode_test("vp6-bool.avi", enc_options, &[0xb57f49e5, 0x6b48accd, 0xc28fadb3, 0xc89a30d2]); } #[test] + fn test_vp6_encoder_fast() { + let enc_options = &[ + NAOption { name: super::QUANT_OPTION, value: NAValue::Int(42) }, + NAOption { name: super::FAST_OPTION, value: NAValue::Bool(true) }, + ]; + encode_test("vp6-fast.avi", enc_options, &[0xb8037ce1, 0xc00ade72, 0x3c0b73c2, 0xbfc4113d]); + } + #[test] fn test_vp6_encoder_rc() { let enc_options = &[ ]; -- 2.39.5