]> git.nihav.org Git - nihav.git/commitdiff
mpeg4asp: recognise and work around some bugs
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 21 Mar 2026 17:13:28 +0000 (18:13 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 21 Mar 2026 17:13:28 +0000 (18:13 +0100)
nihav-mpeg/src/codecs/mpeg4asp/bitstream.rs
nihav-mpeg/src/codecs/mpeg4asp/decoder.rs
nihav-mpeg/src/codecs/mpeg4asp/dsp.rs
nihav-mpeg/src/codecs/mpeg4asp/mod.rs
nihav-mpeg/src/codecs/mpeg4asp/types.rs

index 534d6eb900c8b1e095b4eae7edc0fbf564039e7f..35e5972a28dd178021d1f4ca4441062351b848b0 100644 (file)
@@ -50,20 +50,44 @@ pub fn scan_start_codes(src: &[u8]) -> DecoderResult<Vec<usize>> {
     Ok(offs)
 }
 
-pub fn check_xvid_user_data(src: &[u8]) -> bool {
-    let is_xvid = src.len() > 7 && &src[1..5] == b"XviD";
-    if is_xvid { // check for version
+pub fn check_user_data(src: &[u8]) -> Bugs {
+    let mut bugs = Bugs::new();
+    if src.len() > 7 && &src[1..5] == b"XviD" {
         let mut ver = 0;
         for &c in src[6..].iter() {
             if !c.is_ascii_digit() { break; }
             ver = ver * 10 + u32::from(c - b'0');
-            if ver > 1048576 { return false; }
+            if ver > 1048576 { break; }
         }
-        if ver < 50 { // these ones apparently use non-special DCT
-            return false;
+        if ver >= 50 { // these ones apparently use non-special DCT
+            bugs.set_xvid_dct();
         }
     }
-    is_xvid
+    if src.len() > 7 && &src[1..5] == b"DivX" {
+        let mut ver = 0;
+        let mut build = 0;
+        let mut pos = 6;
+        for &c in src[6..].iter() {
+            if !c.is_ascii_digit() { break; }
+            ver = ver * 10 + u32::from(c - b'0');
+            pos += 1;
+            if ver > 1048576 { break; }
+        }
+        if (pos + 5 < src.len()) && &src[pos..][..5] == b"Build" {
+            for &c in src[6..].iter() {
+                if !c.is_ascii_digit() { break; }
+                build = build * 10 + u32::from(c - b'0');
+                if build > 1048576 { break; }
+            }
+        }
+        if ver < 500 {
+            bugs.set_divx_edge_emu();
+        }
+        if ver > 0 {
+            bugs.set_direct_mb();
+        }
+    }
+    bugs
 }
 
 fn read_var_len(br: &mut BitReader) -> DecoderResult<u32> {
@@ -78,7 +102,7 @@ fn read_var_len(br: &mut BitReader) -> DecoderResult<u32> {
     Ok(len)
 }
 
-pub fn parse_es_descriptor(src: &[u8]) -> DecoderResult<(VideoObjectLayer, bool)> {
+pub fn parse_es_descriptor(src: &[u8]) -> DecoderResult<(VideoObjectLayer, Bugs)> {
     let mut br = BitReader::new(src, BitReaderMode::BE);
 
     // ES_Descriptor
@@ -131,7 +155,7 @@ pub fn parse_es_descriptor(src: &[u8]) -> DecoderResult<(VideoObjectLayer, bool)
     let dsi = &src[br.tell() / 8..][..len as usize];
     let offs = scan_start_codes(dsi)?;
     let mut vol = None;
-    let mut is_xvid = false;
+    let mut bugs = Bugs::new();
     for range in offs.windows(2) {
         let obj_src = &dsi[range[0] + 3..range[1]];
         match obj_src[0] {
@@ -146,7 +170,7 @@ pub fn parse_es_descriptor(src: &[u8]) -> DecoderResult<(VideoObjectLayer, bool)
             },
             0xB1 => {}, // visual_object_sequence_end_code
             0xB2 => { // user_data_start_code
-                is_xvid |= check_xvid_user_data(obj_src);
+                bugs = check_user_data(obj_src);
             },
             0xB3 => return Err(DecoderError::InvalidData), // group_of_vop_start_code
             0xB4 => return Err(DecoderError::NotImplemented), // video_session_error_code
@@ -159,7 +183,7 @@ pub fn parse_es_descriptor(src: &[u8]) -> DecoderResult<(VideoObjectLayer, bool)
     }
     // the rest - who cares?
     validate!(vol.is_some());
-    Ok((vol.unwrap(), is_xvid))
+    Ok((vol.unwrap(), bugs))
 }
 
 pub fn parse_video_object_layer(src: &[u8]) -> DecoderResult<VideoObjectLayer> {
index a71da949f113809e20236b0aa5c0f5e3bc6142c3..c497563ed01fcabc8868053c819367d1df39bbaf 100644 (file)
@@ -29,6 +29,7 @@ pub struct FrameContext {
     fwd_ts:         u64,
     pred:           PredState,
     dsp:            DSP,
+    solid_dmb:      bool,
 }
 
 impl FrameContext {
@@ -42,11 +43,18 @@ impl FrameContext {
             fwd_ts:         0,
             pred:           PredState::new(),
             dsp:            DSP::new(),
+            solid_dmb:      false,
         }
     }
     pub fn use_xvid_idct(&mut self) {
         self.dsp.use_xvid_idct();
     }
+    pub fn set_edge_emu_bug(&mut self) {
+        self.dsp.set_no_align();
+    }
+    pub fn set_solid_dmb(&mut self) {
+        self.solid_dmb = true;
+    }
     pub fn resize(&mut self, mb_w: usize) {
         self.pred.resize(mb_w);
     }
@@ -150,6 +158,14 @@ impl FrameContext {
                             *f_mv = ref_mv + diff_mv;
                             *b_mv = MV::b_sub(src_mv, *f_mv, diff_mv, trb, trd);
                         }
+                        if self.solid_dmb { //xxx: but not for quarter sample apparently
+                            f_mvs[1] = f_mvs[0];
+                            f_mvs[2] = f_mvs[0];
+                            f_mvs[3] = f_mvs[0];
+                            b_mvs[1] = b_mvs[0];
+                            b_mvs[2] = b_mvs[0];
+                            b_mvs[3] = b_mvs[0];
+                        }
                         if let (Some(f_frame), Some(b_frame)) = (self.fwd_ref.clone(), self.bwd_ref.clone()) {
                             let (xpos, ypos) = Self::calc_mvpos_4mv(&f_mvs, mb_x, mb_y, mb_w, mb_h);
                             helper.wait_for_mb(xpos, ypos, self.fwd_ts as u32)?;
index 7db0b74e480b236e3667f5d547b43b75afc82026..7d6d493952b8c1c0ff0ef7f8eff93387da684c9d 100644 (file)
@@ -1,6 +1,6 @@
 use nihav_core::frame::*;
 use nihav_codec_support::codecs::MV;
-use nihav_codec_support::codecs::blockdsp::*;
+use nihav_codec_support::codecs::blockdsp::{BlkInterpFunc, edge_emu};
 use nihav_codec_support::codecs::h263::code::*;
 use nihav_codec_support::codecs::h263::data::H263_CHROMA_ROUND;
 use super::types::*;
@@ -130,10 +130,48 @@ impl MVOps for MV {
     }
 }
 
+#[allow(clippy::too_many_arguments)]
+fn copy_block(dst: &mut NASimpleVideoFrame<u8>, src: NAVideoBufferRef<u8>, comp: usize,
+              dx: usize, dy: usize, mv_x: i16, mv_y: i16, bw: usize, bh: usize,
+              preborder: usize, postborder: usize,
+              mode: usize, interp: &[BlkInterpFunc], align: u8)
+{
+    let pre  = if mode != 0 { preborder  as isize } else { 0 };
+    let post = if mode != 0 { postborder as isize } else { 0 };
+    let (w, h) = src.get_dimensions(comp);
+    let sx = (dx as isize) + (mv_x as isize);
+    let sy = (dy as isize) + (mv_y as isize);
+
+    if (sx - pre < 0) || (sx + (bw as isize) + post > (w as isize)) ||
+       (sy - pre < 0) || (sy + (bh as isize) + post > (h as isize)) {
+        let ebuf_stride: usize = 32;
+        let mut ebuf: Vec<u8> = vec![0; ebuf_stride * (bh + ((pre + post) as usize))];
+
+        let dstride = dst.stride[comp];
+        let doff    = dst.offset[comp];
+        let edge = (pre + post) as usize;
+        edge_emu(&src, sx - pre, sy - pre, bw + edge, bh + edge,
+                 ebuf.as_mut_slice(), ebuf_stride, comp, align);
+        (interp[mode])(&mut dst.data[doff + dx + dy * dstride..], dstride,
+                       ebuf.as_slice(), ebuf_stride, bw, bh);
+    } else {
+        let sstride = src.get_stride(comp);
+        let soff    = src.get_offset(comp);
+        let sdta    = src.get_data();
+        let sbuf: &[u8] = sdta.as_slice();
+        let dstride = dst.stride[comp];
+        let doff    = dst.offset[comp];
+        let saddr = soff + ((sx - pre) as usize) + ((sy - pre) as usize) * sstride;
+        (interp[mode])(&mut dst.data[doff + dx + dy * dstride..], dstride,
+                       &sbuf[saddr..], sstride, bw, bh);
+    }
+}
+
 pub struct DSP {
     idct:           fn(block: &mut [i16; 64]),
     mc_funcs:       &'static [BlkInterpFunc],
     avg_mc_funcs:   &'static [BlkInterpFunc],
+    align:          u8,
 }
 
 impl DSP {
@@ -142,6 +180,7 @@ impl DSP {
             idct:           h263_idct,
             mc_funcs:       ASP_INTERP_FUNCS_NR,
             avg_mc_funcs:   ASP_INTERP_AVG_FUNCS_NR,
+            align:          4,
         }
     }
     pub fn use_xvid_idct(&mut self) {
@@ -156,6 +195,9 @@ impl DSP {
             self.avg_mc_funcs   = H263_INTERP_AVG_FUNCS;
         }
     }
+    pub fn set_no_align(&mut self) {
+        self.align = 0;
+    }
     pub fn put_mb(&self, frm: &mut NASimpleVideoFrame<u8>, mb: &mut Macroblock, mb_x: usize, mb_y: usize) {
         let mut cbp = mb.cbp;
         let y_offset = frm.offset[0] + mb_x * 16 + mb_y * 16 * frm.stride[0];
@@ -229,44 +271,44 @@ impl DSP {
 
     pub fn mb_mv(&self, frm: &mut NASimpleVideoFrame<u8>, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef<u8>, mv: MV) {
         let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize;
-        copy_block(frm, pframe.clone(), 0, mb_x * 16, mb_y * 16, mv.x >> 1, mv.y >> 1, 16, 16, 0, 1, mode, self.mc_funcs);
+        copy_block(frm, pframe.clone(), 0, mb_x * 16, mb_y * 16, mv.x >> 1, mv.y >> 1, 16, 16, 0, 1, mode, self.mc_funcs, self.align);
         let cmv = mv.get_chroma_mv();
         let cmode = (if (mv.x & 3) != 0 { 1 } else { 0 }) + (if (mv.y & 3) != 0 { 2 } else { 0 });
-        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs);
-        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs);
+        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs, self.align);
+        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs, self.align);
     }
 
     pub fn avg_mb_mv(&self, frm: &mut NASimpleVideoFrame<u8>, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef<u8>, mv: MV) {
         let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize;
-        copy_block(frm, pframe.clone(), 0, mb_x * 16, mb_y * 16, mv.x >> 1, mv.y >> 1, 16, 16, 0, 1, mode, self.avg_mc_funcs);
+        copy_block(frm, pframe.clone(), 0, mb_x * 16, mb_y * 16, mv.x >> 1, mv.y >> 1, 16, 16, 0, 1, mode, self.avg_mc_funcs, self.align);
         let cmv = mv.get_chroma_mv();
         let cmode = (if (mv.x & 3) != 0 { 1 } else { 0 }) + (if (mv.y & 3) != 0 { 2 } else { 0 });
-        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, mv.y >> 1, 8, 8, 0, 1, cmode, self.avg_mc_funcs);
-        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, mv.y >> 1, 8, 8, 0, 1, cmode, self.avg_mc_funcs);
+        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, mv.y >> 1, 8, 8, 0, 1, cmode, self.avg_mc_funcs, self.align);
+        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, mv.y >> 1, 8, 8, 0, 1, cmode, self.avg_mc_funcs, self.align);
     }
 
     pub fn mb_4mv(&self, frm: &mut NASimpleVideoFrame<u8>, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef<u8>, mvs: &[MV; 4]) {
         for (n, &mv) in mvs.iter().enumerate() {
             let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize;
-            copy_block(frm, pframe.clone(), 0, mb_x * 16 + (n & 1) * 8, mb_y * 16 + (n / 2) * 8, mv.x >> 1, mv.y >> 1, 8, 8, 0, 1, mode, self.mc_funcs);
+            copy_block(frm, pframe.clone(), 0, mb_x * 16 + (n & 1) * 8, mb_y * 16 + (n / 2) * 8, mv.x >> 1, mv.y >> 1, 8, 8, 0, 1, mode, self.mc_funcs, self.align);
         }
         let sum_mv = mvs[0] + mvs[1] + mvs[2] + mvs[3];
         let cmv = sum_mv.get_chroma_4mv();
         let cmode = ((cmv.x & 1) + (cmv.y & 1) * 2) as usize;
-        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs);
-        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs);
+        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs, self.align);
+        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x >> 1, cmv.y >> 1, 8, 8, 0, 1, cmode, self.mc_funcs, self.align);
     }
 
     pub fn avg_mb_4mv(&self, frm: &mut NASimpleVideoFrame<u8>, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef<u8>, mvs: &[MV; 4]) {
         for (n, &mv) in mvs.iter().enumerate() {
             let mode = ((mv.x & 1) + (mv.y & 1) * 2) as usize;
-            copy_block(frm, pframe.clone(), 0, mb_x * 16 + (n & 1) * 8, mb_y * 16 + (n / 2) * 8, mv.x >> 1, mv.y >> 1, 8, 8, 0, 1, mode, self.avg_mc_funcs);
+            copy_block(frm, pframe.clone(), 0, mb_x * 16 + (n & 1) * 8, mb_y * 16 + (n / 2) * 8, mv.x >> 1, mv.y >> 1, 8, 8, 0, 1, mode, self.avg_mc_funcs, self.align);
         }
         let sum_mv = mvs[0] + mvs[1] + mvs[2] + mvs[3];
         let cmv = sum_mv.get_chroma_4mv();
         let cmode = ((cmv.x & 1) + (cmv.y & 1) * 2) as usize;
-        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x, cmv.y, 8, 8, 0, 1, cmode, self.avg_mc_funcs);
-        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x, cmv.y, 8, 8, 0, 1, cmode, self.avg_mc_funcs);
+        copy_block(frm, pframe.clone(), 1, mb_x * 8, mb_y * 8, cmv.x, cmv.y, 8, 8, 0, 1, cmode, self.avg_mc_funcs, self.align);
+        copy_block(frm, pframe,         2, mb_x * 8, mb_y * 8, cmv.x, cmv.y, 8, 8, 0, 1, cmode, self.avg_mc_funcs, self.align);
     }
 }
 
index d9d3e56f1392839750bc91f94ca0f351fa188f4d..d614293784fa877e3cee083c3998e06e70639de0 100644 (file)
@@ -72,14 +72,20 @@ impl NADecoder for ASPDecoder {
             if let Some(edata) = info.get_extradata() {
                 // ES descriptor, usually happens in MOV
                 if edata.len() > 16 && &edata[..4] == b"esds" {
-                    let (vol, is_xvid) = parse_es_descriptor(&edata[8..])?;
+                    let (vol, bugs) = parse_es_descriptor(&edata[8..])?;
                     if vol.quarter_sample {
                         println!("quarterpel is not supported!");
                         return Err(DecoderError::NotImplemented);
                     }
-                    if is_xvid {
+                    if bugs.is_xvid_dct() {
                         self.fctx.use_xvid_idct();
                     }
+                    if bugs.is_divx_edge_emu() {
+                        self.fctx.set_edge_emu_bug();
+                    }
+                    if bugs.is_direct_mb() {
+                        self.fctx.set_solid_dmb();
+                    }
                     self.seq.set_vol(vol);
                     parsed_vol = true;
                 }
@@ -146,9 +152,16 @@ impl NADecoder for ASPDecoder {
                 },
                 0xB1 => {}, // visual_object_sequence_end_code
                 0xB2 => { // user_data_start_code
-                    if check_xvid_user_data(obj_src) {
+                    let bugs = check_user_data(obj_src);
+                    if bugs.is_xvid_dct() {
                         self.fctx.use_xvid_idct();
                     }
+                    if bugs.is_divx_edge_emu() {
+                        self.fctx.set_edge_emu_bug();
+                    }
+                    if bugs.is_direct_mb() {
+                        self.fctx.set_solid_dmb();
+                    }
                 },
                 0xB3 => { // group_of_vop_start_code
                     validate!(obj_src.len() == 4);
@@ -290,6 +303,7 @@ impl NADecoder for ASPDecoder {
 }
 
 const NO_REORDER_OPTION: &str = "noreorder";
+const EDGE_EMU_OPTION: &str = "edge_emu_bug";
 
 const DECODER_OPTIONS: &[NAOptionDefinition] = &[
     NAOptionDefinition {
@@ -302,6 +316,9 @@ const DECODER_OPTIONS: &[NAOptionDefinition] = &[
     NAOptionDefinition {
         name: NO_REORDER_OPTION, description: "Do not attempt to reorder frames",
         opt_type: NAOptionDefinitionType::None },
+    NAOptionDefinition {
+        name: EDGE_EMU_OPTION, description: "Use dimensions-aligned edge emulation (some old files may need it)",
+        opt_type: NAOptionDefinitionType::None },
 ];
 
 impl NAOptionHandler for ASPDecoder {
@@ -319,6 +336,9 @@ impl NAOptionHandler for ASPDecoder {
                         (NO_REORDER_OPTION, _) => {
                             self.assign_mode = AssignMode::No;
                         },
+                        (EDGE_EMU_OPTION, _) => {
+                            self.fctx.set_edge_emu_bug();
+                        },
                         _ => {},
                     }
                 }
index 28ff0c31d9be60f80347b339c7e9fe485b833209..f79f29bcbc5dd9a09482d41c05926b5660604938 100644 (file)
@@ -2,6 +2,19 @@ use nihav_core::frame::*;
 use nihav_codec_support::codecs::{MV, ZERO_MV};
 use nihav_codec_support::data::GenericCache;
 
+#[derive(Clone,Copy,Default,Debug)]
+pub struct Bugs(u32);
+
+impl Bugs {
+    pub fn new() -> Self { Self::default() }
+    pub fn set_xvid_dct(&mut self) { self.0 |= 0x0001; }
+    pub fn is_xvid_dct(&self) -> bool { (self.0 & 0x0001) != 0 }
+    pub fn set_divx_edge_emu(&mut self) { self.0 |= 0x0002; }
+    pub fn is_divx_edge_emu(&self) -> bool { (self.0 & 0x0002) != 0 }
+    pub fn set_direct_mb(&mut self) { self.0 |= 0x0004; }
+    pub fn is_direct_mb(&self) -> bool { (self.0 & 0x0004) != 0 }
+}
+
 pub trait FrameMV {
     fn add_pred(pred: MV, diff: MV, fcode: u8) -> MV;
     fn scale(&self, trb: i32, trd: i32) -> MV;