]> git.nihav.org Git - nihav.git/blobdiff - nihav-commonfmt/src/codecs/zmbvenc.rs
Acorn Super Moving Blocks Decoder
[nihav.git] / nihav-commonfmt / src / codecs / zmbvenc.rs
index 23e8aadd893bfd45eb3255b313229d6ba8ea186e..1ea74d01585e3377058ff81b8fa2acff4cd87e9c 100644 (file)
@@ -35,6 +35,8 @@ struct ZMBVEncoder {
     width:      usize,
     height:     usize,
     range:      usize,
+    full_me:    bool,
+    sent_pal:   bool,
 }
 
 fn buf_type_to_bpp(buf: &NABufferType) -> u8 {
@@ -134,16 +136,19 @@ impl ZMBVEncoder {
             width:      0,
             height:     0,
             range:      128,
+            full_me:    false,
+            sent_pal:   false,
         }
     }
     fn encode_intra(&mut self, bw: &mut ByteWriter, buf: NABufferType) -> EncoderResult<()> {
-        let bpp = buf_type_to_bpp(&buf);
+        let mut bpp = buf_type_to_bpp(&buf);
 
         if let NABufferType::None = buf {
             if self.bpp == 0 {
                 return Err(EncoderError::FormatError);
             }
             self.frm1.copy_from_slice(&self.frm2);
+            bpp = self.bpp;
         } else {
             if bpp == 0 {
                 return Err(EncoderError::FormatError);
@@ -182,7 +187,7 @@ impl ZMBVEncoder {
             }
             bw.write_buf(&self.frm1[..self.width * self.height * bm])?;
         } else {
-            self.tmp_buf.truncate(0);
+            self.tmp_buf.clear();
             if bpp == 8 {
                 self.tmp_buf.extend_from_slice(&self.pal);
             }
@@ -191,7 +196,7 @@ impl ZMBVEncoder {
 
             let mut db = Vec::new();
             std::mem::swap(&mut db, &mut self.zbuf);
-            db.truncate(0);
+            db.clear();
             let mut wr = DeflateWriter::new(db);
             self.compr.write_zlib_header(&mut wr);
             self.compr.compress(&self.tmp_buf, &mut wr);
@@ -209,7 +214,7 @@ impl ZMBVEncoder {
             self.frm1.copy_from_slice(&self.frm2);
 
             bw.write_byte(0)?;
-            self.tmp_buf.truncate(0);
+            self.tmp_buf.clear();
             let tile_w = (self.width  + self.tile_w - 1) / self.tile_w;
             let tile_h = (self.height + self.tile_h - 1) / self.tile_h;
             let mv_size = (tile_w * tile_h * 2 + 3) & !3;
@@ -222,7 +227,7 @@ impl ZMBVEncoder {
                 let mut db = Vec::new();
 
                 std::mem::swap(&mut db, &mut self.zbuf);
-                db.truncate(0);
+                db.clear();
                 let mut wr = DeflateWriter::new(db);
                 self.compr.compress(&self.tmp_buf, &mut wr);
                 self.compr.compress_flush(&mut wr);
@@ -238,7 +243,7 @@ impl ZMBVEncoder {
             return Err(EncoderError::FormatError);
         }
 
-        self.tmp_buf.truncate(0);
+        self.tmp_buf.clear();
         if let (NABufferType::Video(ref vbuf), true) = (&buf, bpp == 8) {
             let mut npal = [0; 768];
             let off = vbuf.get_offset(1);
@@ -258,6 +263,8 @@ impl ZMBVEncoder {
                 self.pal = npal;
 
                 bw.write_byte(2)?;
+
+                self.sent_pal = false;
             } else {
                 bw.write_byte(0)?;
             }
@@ -282,48 +289,7 @@ impl ZMBVEncoder {
             for x in (0..self.width).step_by(self.tile_w) {
                 let cur_w = (self.width - x).min(self.tile_w);
 
-                let mut best_dist = std::u32::MAX;
-                let mut best_x = x;
-                let mut best_y = y;
-
-                'search: for yoff in 0..self.range {
-                    let ypos = (y as isize) + to_signed(yoff);
-                    if ypos < 0 {
-                        continue;
-                    }
-                    let ypos = ypos as usize;
-                    if ypos + cur_h > self.height {
-                        break;
-                    }
-                    for xoff in 0..self.range {
-                        let xpos = (x as isize) + to_signed(xoff);
-                        if xpos < 0 {
-                            continue;
-                        }
-                        let xpos = xpos as usize;
-                        if xpos + cur_w > self.width {
-                            break;
-                        }
-
-                        let mut diff = 0;
-                        let roff = xpos * bpp + ypos * stride;
-                        for (line0, line1) in self.frm1[off..].chunks(stride).take(cur_h).zip(self.frm2[roff..].chunks(stride)) {
-                            for (&a, &b) in line0[..cur_w * bpp].iter().zip(line1[..cur_w * bpp].iter()) {
-                                diff += u32::from(a ^ b);
-                            }
-                        }
-
-                        if best_dist > diff {
-                            best_dist = diff;
-                            best_x = xpos;
-                            best_y = ypos;
-                            if diff == 0 {
-                                break 'search;
-                            }
-                        }
-                    }
-                }
-
+                let (best_x, best_y, best_dist) = self.motion_search(&self.frm1[off..], x, y, cur_w, cur_h, bpp);
                 let has_delta = best_dist != 0;
                 self.tmp_buf[mv_start] = (best_x.wrapping_sub(x) << 1) as u8;
                 if has_delta {
@@ -352,7 +318,7 @@ impl ZMBVEncoder {
             let mut db = Vec::new();
 
             std::mem::swap(&mut db, &mut self.zbuf);
-            db.truncate(0);
+            db.clear();
             let mut wr = DeflateWriter::new(db);
             self.compr.compress(&self.tmp_buf, &mut wr);
             self.compr.compress_flush(&mut wr);
@@ -361,18 +327,106 @@ impl ZMBVEncoder {
 
             bw.write_buf(&self.zbuf)?;
         }
-        
+
         Ok(())
     }
+    fn calc_dist(&self, cur_frm: &[u8], xpos: usize, ypos: usize, cur_w: usize, cur_h: usize, bpp: usize) -> u32 {
+        let stride = self.width * bpp;
+        let mut diff = 0;
+        let roff = xpos * bpp + ypos * stride;
+        for (line0, line1) in cur_frm.chunks(stride).take(cur_h).zip(self.frm2[roff..].chunks(stride)) {
+            for (&a, &b) in line0[..cur_w * bpp].iter().zip(line1[..cur_w * bpp].iter()) {
+                diff += u32::from(a ^ b);
+            }
+        }
+        diff
+    }
+    fn motion_search(&self, cur_frm: &[u8], x: usize, y: usize, cur_w: usize, cur_h: usize, bpp: usize) -> (usize, usize, u32) {
+        let mut best_dist = self.calc_dist(cur_frm, x, y, cur_w, cur_h, bpp);
+        if best_dist == 0 {
+            return (x, y, 0);
+        }
+        let mut best_x = x;
+        let mut best_y = y;
+
+        if !self.full_me {
+            let mut cur_range = self.range.min(64);
+
+            while cur_range > 1 {
+                let x1 = best_x.saturating_sub(cur_range);
+                let x2 = (best_x + cur_range).min(self.width - cur_w);
+                let y1 = best_y.saturating_sub(cur_range);
+                let y2 = (best_y + cur_range).min(self.height - cur_h);
+                let points = [(best_x,  y1),
+                              (x2,      y1),
+                              (x2,      best_y),
+                              (x2,      y2),
+                              (best_x,  y2),
+                              (x1,      y2),
+                              (x1,      best_y),
+                              (x1,      y1)];
+
+                for &(pt_x, pt_y) in points.iter() {
+                    if ((x as isize) - (pt_x as isize)).abs() >= 64 {
+                        continue;
+                    }
+                    if ((y as isize) - (pt_y as isize)).abs() >= 64 {
+                        continue;
+                    }
+                    let dist = self.calc_dist(cur_frm, pt_x, pt_y, cur_w, cur_h, bpp);
+                    if dist < best_dist {
+                        best_dist = dist;
+                        best_x = pt_x;
+                        best_y = pt_y;
+                    }
+                }
+                cur_range = (cur_range + 1) >> 1;
+            }
+        } else {
+            for yoff in 0..self.range {
+                let ypos = (y as isize) + to_signed(yoff);
+                if ypos < 0 {
+                    continue;
+                }
+                let ypos = ypos as usize;
+                if ypos + cur_h > self.height {
+                    break;
+                }
+                for xoff in 0..self.range {
+                    let xpos = (x as isize) + to_signed(xoff);
+                    if xpos < 0 {
+                        continue;
+                    }
+                    let xpos = xpos as usize;
+                    if xpos + cur_w > self.width {
+                        break;
+                    }
+
+                    let diff = self.calc_dist(cur_frm, xpos, ypos, cur_w, cur_h, bpp);
+
+                    if best_dist > diff {
+                        best_dist = diff;
+                        best_x = xpos;
+                        best_y = ypos;
+                        if diff == 0 {
+                            return (best_x, best_y, 0);
+                        }
+                    }
+                }
+            }
+        }
+        (best_x, best_y, best_dist)
+    }
 }
 
 impl NAEncoder for ZMBVEncoder {
     fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult<EncodeParameters> {
         match encinfo.format {
             NACodecTypeInfo::None => {
-                let mut ofmt = EncodeParameters::default();
-                ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV420_FORMAT));
-                Ok(ofmt)
+                Ok(EncodeParameters {
+                    format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, true, YUV420_FORMAT)),
+                    ..Default::default()
+                })
             },
             NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError),
             NACodecTypeInfo::Video(vinfo) => {
@@ -393,6 +447,7 @@ impl NAEncoder for ZMBVEncoder {
             }
         }
     }
+    fn get_capabilities(&self) -> u64 { ENC_CAPS_SKIPFRAME }
     fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult<NAStreamRef> {
         match encinfo.format {
             NACodecTypeInfo::None => Err(EncoderError::FormatError),
@@ -400,7 +455,7 @@ impl NAEncoder for ZMBVEncoder {
             NACodecTypeInfo::Video(vinfo) => {
                 self.width  = vinfo.width;
                 self.height = vinfo.height;
-                        
+
                 let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, vinfo.format);
                 let info = NACodecInfo::new("zmbv", NACodecTypeInfo::Video(out_info), None);
                 let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0);
@@ -429,6 +484,21 @@ impl NAEncoder for ZMBVEncoder {
                 false
             };
         self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf));
+        if self.bpp == 8 && !self.sent_pal {
+            if let NABufferType::Video(ref buf) = frm.get_buffer() {
+                let paloff = buf.get_offset(1);
+                let data = buf.get_data();
+                let mut pal = [0; 1024];
+                let srcpal = &data[paloff..][..768];
+                for (dclr, sclr) in pal.chunks_exact_mut(4).zip(srcpal.chunks_exact(3)) {
+                    dclr[..3].copy_from_slice(sclr);
+                }
+                if let Some(ref mut pkt) = &mut self.pkt {
+                    pkt.side_data.push(NASideData::Palette(true, Arc::new(pal)));
+                }
+            }
+            self.sent_pal = true;
+        }
         self.frmcount += 1;
         if self.frmcount == self.key_int {
             self.frmcount = 0;
@@ -454,6 +524,9 @@ const ENCODER_OPTS: &[NAOptionDefinition] = &[
     NAOptionDefinition {
         name: "range", description: "Block search range (0-128)",
         opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) },
+    NAOptionDefinition {
+        name: "full_me", description: "Brute force search",
+        opt_type: NAOptionDefinitionType::Bool },
     NAOptionDefinition {
         name: "tile_width", description: "Block width (1-255)",
         opt_type: NAOptionDefinitionType::Int(Some(1), Some(255)) },
@@ -489,6 +562,11 @@ impl NAOptionHandler for ZMBVEncoder {
                                 self.range = intval as usize;
                             }
                         },
+                        "full_me" => {
+                            if let NAValue::Bool(bval) = option.value {
+                                self.full_me = bval;
+                            }
+                        },
                         "tile_width" => {
                             if let NAValue::Int(intval) = option.value {
                                 self.tile_w = intval as usize;
@@ -510,6 +588,7 @@ impl NAOptionHandler for ZMBVEncoder {
             "compr_level" => Some(NAValue::String(self.cmode.to_string())),
             KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.key_int))),
             "range"       => Some(NAValue::Int(self.range as i64)),
+            "full_me"     => Some(NAValue::Bool(self.full_me)),
             "tile_width"  => Some(NAValue::Int(self.tile_w as i64)),
             "tile_height" => Some(NAValue::Int(self.tile_h as i64)),
             _ => None,
@@ -530,6 +609,7 @@ mod test {
     use nihav_codec_support::test::enc_video::*;
     use super::{RGB555_FORMAT, RGB24_0_FORMAT};
 
+    // samples are from https://samples.mplayerhq.hu/V-codecs/ZMBV/
     #[test]
     fn test_zmbv_encoder_8bit() {
         let mut dmx_reg = RegisteredDemuxers::new();
@@ -569,9 +649,13 @@ 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,
-                          &[0x50df10e2, 0x606f3268, 0xdd4bc2ff, 0x844e7d87]);
+        let enc_options = &[
+                NAOption { name: "range", value: NAValue::Int(16) },
+                NAOption { name: "full_me", value: NAValue::Bool(true) },
+            ];
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options,
+                          &[0x18bd3754, 0x97007f81, 0xff2bcd07, 0x739c48dc]);
     }
 
     #[test]
@@ -613,9 +697,13 @@ 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,
-                          &[0x0b4cb528, 0x66c91f6c, 0x1c2187a5, 0x2723a08d]);
+        let enc_options = &[
+                NAOption { name: "range", value: NAValue::Int(16) },
+                NAOption { name: "full_me", value: NAValue::Bool(true) },
+            ];
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options,
+                          &[0x00311257, 0xd26a0e9e, 0xfd4b003f, 0x7c962d7b]);
     }
 
     #[test]
@@ -657,9 +745,13 @@ 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,
-                          &[0x1a522743, 0x6c320a6e, 0xd08539e1, 0x03fc17ea]);
+        let enc_options = &[
+                NAOption { name: "range", value: NAValue::Int(16) },
+                NAOption { name: "full_me", value: NAValue::Bool(true) },
+            ];
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options,
+                          &[0x4eea104f, 0x2ebe544b, 0x54deb0f9, 0xe5ca88f4]);
     }
 
     #[test]
@@ -701,8 +793,12 @@ 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,
-                          &[0x3880e045, 0xe6c88dc7, 0x21066058, 0xc789f1e9]);
+        let enc_options = &[
+                NAOption { name: "range", value: NAValue::Int(16) },
+                NAOption { name: "full_me", value: NAValue::Bool(true) },
+            ];
+        //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options);
+        test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options,
+                          &[0xffceb4bd, 0xb1beccd9, 0x4983e7f6, 0xf46e33ba]);
     }
 }