]> git.nihav.org Git - nihav.git/blobdiff - nihav-commonfmt/src/codecs/cinepakenc.rs
add YUV4MPEG muxer
[nihav.git] / nihav-commonfmt / src / codecs / cinepakenc.rs
index ff86a524e4044781da66bf45543c161ac6792133..6db1c74e869a20407f3ddcf71f82c2827d1c07a7 100644 (file)
@@ -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(),
         }
     }
@@ -253,12 +253,21 @@ struct CinepakEncoder {
     rng:        RNG,
     masks:      MaskWriter,
     skip_dist:  Vec<u32>,
+    fst_bins:   [Vec<YUVCode>; 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)))?;
@@ -267,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::<YUVCode, YUVCodeSum>(entries, codebook);
+    if cb_len < codebook.len() {
+        cb_len
+    } else {
+        let mut elbg: ELBG<YUVCode, YUVCodeSum> = ELBG::new(codebook);
+        elbg.quantise(entries, codebook)
+    }
+}
+
+fn quant_fast(bins: &mut [Vec<YUVCode>; 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 {
@@ -274,7 +328,7 @@ impl CinepakEncoder {
             pkt:        None,
             lastfrm:    None,
             frmcount:   0,
-            qmode:      QuantMode::MedianCut,
+            qmode:      QuantMode::Fast,
             key_int:    25,
             quality:    0,
             nstrips:    2,
@@ -294,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<u8>, start: usize, end: usize) {
@@ -606,37 +661,22 @@ impl CinepakEncoder {
     fn quant_vectors(&mut self) {
         match self.qmode {
             QuantMode::ELBG => {
-                let mut elbg_v1: ELBG<YUVCode, YUVCodeSum> = ELBG::new(&self.v1_cb[self.cur_strip]);
-                let mut elbg_v4: ELBG<YUVCode, YUVCodeSum> = 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 = if !self.force_v1 { elbg_v4.quantise(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip]) } else { 0 };
-            },
-            QuantMode::Hybrid => {
-                let v1_len = quantise_median_cut::<YUVCode, YUVCodeSum>(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip]);
-                let v4_len = if !self.force_v1 {
-                        quantise_median_cut::<YUVCode, YUVCodeSum>(&self.v4_entries, &mut self.v4_cur_cb[self.cur_strip])
+                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 {
                         0
                     };
-                self.v1_len = if v1_len < 256 {
-                        v1_len
-                    } else {
-                        let mut elbg_v1: ELBG<YUVCode, YUVCodeSum> = ELBG::new(&self.v1_cur_cb[self.cur_strip]);
-                        elbg_v1.quantise(&self.v1_entries, &mut self.v1_cur_cb[self.cur_strip])
-                    };
-                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<YUVCode, YUVCodeSum> = 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 => {
@@ -958,6 +998,7 @@ impl NAEncoder for CinepakEncoder {
             }
         }
     }
+    fn get_capabilities(&self) -> u64 { 0 }
     fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> {
         match encinfo.format {
             NACodecTypeInfo::None => Err(EncoderError::FormatError),
@@ -999,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;
@@ -1050,7 +1097,7 @@ 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 },
@@ -1077,7 +1124,7 @@ 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,
                                     _ => {},
                                 };
@@ -1157,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]);
     }
 }