X-Git-Url: https://git.nihav.org/?a=blobdiff_plain;f=nihav-commonfmt%2Fsrc%2Fcodecs%2Fcinepakenc.rs;h=6db1c74e869a20407f3ddcf71f82c2827d1c07a7;hb=1ee737a48ec7a767c64bd72e67313ef67cfcc367;hp=3f999e703b9d734343e82a7a7bb1a15faa1c3ef1;hpb=b790a725496c9d4385fa8a869af2b5cb6f3ffdd5;p=nihav.git diff --git a/nihav-commonfmt/src/codecs/cinepakenc.rs b/nihav-commonfmt/src/codecs/cinepakenc.rs index 3f999e7..6db1c74 100644 --- a/nihav-commonfmt/src/codecs/cinepakenc.rs +++ b/nihav-commonfmt/src/codecs/cinepakenc.rs @@ -214,7 +214,7 @@ impl MaskWriter { #[derive(Clone,Copy,PartialEq)] enum QuantMode { ELBG, - Hybrid, + Fast, MedianCut, } @@ -222,7 +222,7 @@ impl std::string::ToString for QuantMode { fn to_string(&self) -> String { match *self { QuantMode::ELBG => "elbg".to_string(), - QuantMode::Hybrid => "hybrid".to_string(), + QuantMode::Fast => "fast".to_string(), QuantMode::MedianCut => "mediancut".to_string(), } } @@ -237,6 +237,7 @@ struct CinepakEncoder { qmode: QuantMode, quality: u8, nstrips: usize, + force_v1: bool, cur_strip: usize, v1_entries: Vec, v4_entries: Vec, @@ -252,12 +253,21 @@ struct CinepakEncoder { rng: RNG, masks: MaskWriter, skip_dist: Vec, + fst_bins: [Vec; 4], } fn avg4(a: u8, b: u8, c: u8, d: u8) -> u8 { ((u16::from(a) + u16::from(b) + u16::from(c) + u16::from(d) + 3) >> 2) as u8 } +fn variance(a: u8, mean: u8) -> u32 { + if a >= mean { + u32::from(a - mean) * u32::from(a - mean) + } else { + u32::from(mean - a) * u32::from(mean - a) + } +} + fn patch_size(bw: &mut ByteWriter, pos: u64) -> EncoderResult<()> { let size = bw.tell() - pos; bw.seek(SeekFrom::Current(-((size + 3) as i64)))?; @@ -266,6 +276,51 @@ fn patch_size(bw: &mut ByteWriter, pos: u64) -> EncoderResult<()> { Ok(()) } +fn elbg_quant(entries: &[YUVCode], codebook: &mut [YUVCode]) -> usize { + let cb_len = quantise_median_cut::(entries, codebook); + if cb_len < codebook.len() { + cb_len + } else { + let mut elbg: ELBG = ELBG::new(codebook); + elbg.quantise(entries, codebook) + } +} + +fn quant_fast(bins: &mut [Vec; 4], entries: &[YUVCode], codebook: &mut [YUVCode]) -> usize { + for bin in bins.iter_mut() { + bin.clear(); + } + for &entry in entries.iter() { + let y_avg = avg4(entry.y[0], entry.y[1], entry.y[2], entry.y[3]); + let dist = entry.y.iter().fold(0u32, |acc, &x| acc + variance(x, y_avg)); + let ilog = if dist == 0 { 0 } else { 32 - dist.leading_zeros() }; + let bin = match ilog { + 0..=3 => &mut bins[0], + 4..=7 => &mut bins[1], + 8..=11 => &mut bins[2], + _ => &mut bins[3], + }; + bin.push(entry); + } + let mut free_cw = codebook.len(); + let mut entries_left = entries.len(); + let mut offset = 0; + for bin in bins.iter() { + if bin.is_empty() { + continue; + } + if free_cw == 0 || entries_left == 0 { + break; + } + let target = (free_cw * bin.len() + entries_left - 1) / entries_left; + let cur_len = elbg_quant(bin, &mut codebook[offset..][..target]); + offset += cur_len; + free_cw -= cur_len; + entries_left -= bin.len(); + } + offset +} + impl CinepakEncoder { fn new() -> Self { Self { @@ -273,10 +328,11 @@ impl CinepakEncoder { pkt: None, lastfrm: None, frmcount: 0, - qmode: QuantMode::MedianCut, + qmode: QuantMode::Fast, key_int: 25, quality: 0, nstrips: 2, + force_v1: false, cur_strip: 0, v1_entries: Vec::new(), v4_entries: Vec::new(), @@ -292,6 +348,7 @@ impl CinepakEncoder { v4_idx: Vec::new(), masks: MaskWriter::new(), skip_dist: Vec::new(), + fst_bins: [Vec::new(), Vec::new(), Vec::new(), Vec::new()], } } fn read_strip(&mut self, in_frm: &NAVideoBuffer, start: usize, end: usize) { @@ -604,38 +661,31 @@ impl CinepakEncoder { fn quant_vectors(&mut self) { match self.qmode { QuantMode::ELBG => { - let mut elbg_v1: ELBG = ELBG::new(&self.v1_cb[self.cur_strip]); - let mut elbg_v4: ELBG = ELBG::new(&self.v4_cb[self.cur_strip]); - - for entry in self.v1_cb[self.cur_strip].iter_mut().skip(self.v1_len) { - self.rng.fill_entry(entry); - } - for entry in self.v4_cb[self.cur_strip].iter_mut().skip(self.v4_len) { - self.rng.fill_entry(entry); - } - - self.v1_len = elbg_v1.quantise(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]); - self.v4_len = elbg_v4.quantise(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]); - }, - QuantMode::Hybrid => { - let v1_len = quantise_median_cut::(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]); - let v4_len = quantise_median_cut::(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]); - self.v1_len = if v1_len < 256 { - v1_len + self.v1_len = elbg_quant(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]); + self.v4_len = if !self.force_v1 { + elbg_quant(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]) } else { - let mut elbg_v1: ELBG = ELBG::new(&self.v1_cur_cb[self.cur_strip]); - elbg_v1.quantise(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]) + 0 }; - self.v4_len = if v4_len < 256 { - v4_len + }, + QuantMode::Fast => { + for bin in self.fst_bins.iter_mut() { + bin.clear(); + } + self.v1_len = quant_fast(&mut self.fst_bins, &self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]); + self.v4_len = if !self.force_v1 { + quant_fast(&mut self.fst_bins, &self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]) } else { - let mut elbg_v4: ELBG = ELBG::new(&self.v4_cur_cb[self.cur_strip]); - elbg_v4.quantise(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]) + 0 }; }, QuantMode::MedianCut => { self.v1_len = quantise_median_cut::(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]); - self.v4_len = quantise_median_cut::(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]); + if !self.force_v1 { + self.v4_len = quantise_median_cut::(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]); + } else { + self.v4_len = 0; + } }, }; @@ -691,7 +741,7 @@ impl CinepakEncoder { for (v1_entry, v4_entries) in self.v1_entries.iter().zip(self.v4_entries.chunks(4)) { let (v1_idx, v1_dist) = Self::find_nearest(&self.v1_cur_cb[self.cur_strip][..self.v1_len], *v1_entry); - if v1_dist == 0 { + if v1_dist == 0 || self.force_v1 { self.masks.put_v1(); self.v1_idx.push(v1_idx); continue; @@ -836,7 +886,7 @@ impl CinepakEncoder { } else { self.masks.put_inter(false); } - if v1_dist == 0 { + if v1_dist == 0 || self.force_v1 { self.masks.put_v1(); self.v1_idx.push(v1_idx); continue; @@ -948,6 +998,7 @@ impl NAEncoder for CinepakEncoder { } } } + fn get_capabilities(&self) -> u64 { 0 } fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult { match encinfo.format { NACodecTypeInfo::None => Err(EncoderError::FormatError), @@ -989,6 +1040,12 @@ impl NAEncoder for CinepakEncoder { fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { let buf = frm.get_buffer(); if let Some(ref vbuf) = buf.get_vbuf() { + if self.nstrips == 0 { + let (w, h) = vbuf.get_dimensions(0); + self.nstrips = ((((w * h) >> 4) + 1200) / 2400).max(1).min(3); + let strip_h = ((h + self.nstrips - 1) / self.nstrips + 3) & !3; + self.nstrips = (h + strip_h - 1) / strip_h; + } let cur_strips = self.v1_cb.len(); if cur_strips != self.nstrips { self.frmcount = 0; @@ -1040,7 +1097,10 @@ const ENCODER_OPTS: &[NAOptionDefinition] = &[ opt_type: NAOptionDefinitionType::Int(Some(0), Some(16)) }, NAOptionDefinition { name: "quant_mode", description: "Quantisation mode", - opt_type: NAOptionDefinitionType::String(Some(&["elbg", "hybrid", "mediancut"])) }, + opt_type: NAOptionDefinitionType::String(Some(&["elbg", "fast", "mediancut"])) }, + NAOptionDefinition { + name: "force_v1", description: "Force coarse (V1-only) mode", + opt_type: NAOptionDefinitionType::Bool }, ]; impl NAOptionHandler for CinepakEncoder { @@ -1064,12 +1124,17 @@ impl NAOptionHandler for CinepakEncoder { if let NAValue::String(ref strval) = option.value { match strval.as_str() { "elbg" => self.qmode = QuantMode::ELBG, - "hybrid" => self.qmode = QuantMode::Hybrid, + "fast" => self.qmode = QuantMode::Fast, "mediancut" => self.qmode = QuantMode::MedianCut, _ => {}, }; } }, + "force_v1" => { + if let NAValue::Bool(val) = option.value { + self.force_v1 = val; + } + }, _ => {}, }; } @@ -1081,6 +1146,7 @@ impl NAOptionHandler for CinepakEncoder { KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.key_int))), "nstrips" => Some(NAValue::Int(self.nstrips as i64)), "quant_mode" => Some(NAValue::String(self.qmode.to_string())), + "force_v1" => Some(NAValue::Bool(self.force_v1)), _ => None, } } @@ -1138,8 +1204,11 @@ mod test { tb_den: 0, flags: 0, }; - //test_encoding_to_file(&dec_config, &enc_config, enc_params, &[]); - test_encoding_md5(&dec_config, &enc_config, enc_params, &[], + let enc_options = &[ + NAOption { name: "quant_mode", value: NAValue::String("mediancut".to_string()) }, + ]; + //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options); + test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, &[0x1d4690c8, 0x3b15b4b3, 0xc2df3c7b, 0x1a25b159]); } }