vp6enc: add fast(er) encoding mode
[nihav.git] / nihav-duck / src / codecs / vp6enc / mod.rs
index 7959ae006cadc4da520814011200ee68816e72ed..b6ce632cd550aa9a9ae71efdb5b820f12b95bba3 100644 (file)
@@ -172,6 +172,7 @@ struct VP6Encoder {
     fenc:       FrameEncoder,
     ratectl:    RateControl,
 
+    flipped:    bool,
     huffman:    bool,
 
     version:    u8,
@@ -188,10 +189,11 @@ struct VP6Encoder {
     me_range:   i16,
 
     force_q:    Option<usize>,
+    fast:       bool,
 }
 
 impl VP6Encoder {
-    fn new() -> Self {
+    fn new(flipped: bool) -> Self {
         let vt = alloc_video_buffer(NAVideoInfo::new(24, 24, false, VP_YUVA420_FORMAT), 4).unwrap();
         let mc_buf = vt.get_vbuf().unwrap();
         Self {
@@ -207,6 +209,7 @@ impl VP6Encoder {
             ratectl:    RateControl::new(),
             mc_buf,
 
+            flipped,
             huffman:    false,
 
             version:    VERSION_VP60,
@@ -223,6 +226,7 @@ impl VP6Encoder {
             me_range:   16,
 
             force_q:    None,
+            fast:       false,
         }
     }
     fn decide_encoding(&mut self) -> bool {
@@ -480,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);
@@ -558,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<bool> {
+        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 {
@@ -633,12 +667,12 @@ impl NAEncoder for VP6Encoder {
         match encinfo.format {
             NACodecTypeInfo::None => {
                 let mut ofmt = EncodeParameters::default();
-                ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV420_FORMAT));
+                ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, self.flipped, YUV420_FORMAT));
                 Ok(ofmt)
             },
             NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
             NACodecTypeInfo::Video(vinfo) => {
-                let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, true, YUV420_FORMAT);
+                let outinfo = NAVideoInfo::new((vinfo.width + 3) & !3, (vinfo.height + 3) & !3, self.flipped, YUV420_FORMAT);
                 let mut ofmt = *encinfo;
                 ofmt.format = NACodecTypeInfo::Video(outinfo);
                 Ok(ofmt)
@@ -660,8 +694,8 @@ impl NAEncoder for VP6Encoder {
                     return Err(EncoderError::FormatError);
                 }
 
-                let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, true, vinfo.format);
-                let info = NACodecInfo::new("vp6", NACodecTypeInfo::Video(out_info), None);
+                let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, self.flipped, vinfo.format);
+                let info = NACodecInfo::new(if self.flipped { "vp6" } else { "vp6f" }, NACodecTypeInfo::Video(out_info), None);
                 let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0);
                 stream.set_num(stream_id as usize);
                 let stream = stream.into_ref();
@@ -707,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;
@@ -756,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 {
@@ -776,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 {
@@ -822,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;
+                            }
+                        },
                         _ => {},
                     };
                 }
@@ -848,13 +893,18 @@ 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,
         }
     }
 }
 
 pub fn get_encoder() -> Box<dyn NAEncoder + Send> {
-    Box::new(VP6Encoder::new())
+    Box::new(VP6Encoder::new(true))
+}
+
+pub fn get_encoder_flv() -> Box<dyn NAEncoder + Send> {
+    Box::new(VP6Encoder::new(false))
 }
 
 #[cfg(test)]
@@ -876,6 +926,7 @@ mod test {
         let mut enc_reg = RegisteredEncoders::new();
         duck_register_all_encoders(&mut enc_reg);
 
+        // sample: https://samples.mplayerhq.hu/V-codecs/VP4/ot171_vp40.avi
         let dec_config = DecoderTestParams {
                 demuxer:        "avi",
                 in_name:        "assets/Duck/ot171_vp40.avi",
@@ -916,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 = &[
             ];