From: Kostya Shishkov Date: Sat, 21 Mar 2026 17:13:28 +0000 (+0100) Subject: mpeg4asp: recognise and work around some bugs X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=4efa555160dd0c00aacfdfdf20656c388e02c579;p=nihav.git mpeg4asp: recognise and work around some bugs --- diff --git a/nihav-mpeg/src/codecs/mpeg4asp/bitstream.rs b/nihav-mpeg/src/codecs/mpeg4asp/bitstream.rs index 534d6eb..35e5972 100644 --- a/nihav-mpeg/src/codecs/mpeg4asp/bitstream.rs +++ b/nihav-mpeg/src/codecs/mpeg4asp/bitstream.rs @@ -50,20 +50,44 @@ pub fn scan_start_codes(src: &[u8]) -> DecoderResult> { 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 { @@ -78,7 +102,7 @@ fn read_var_len(br: &mut BitReader) -> DecoderResult { 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 { diff --git a/nihav-mpeg/src/codecs/mpeg4asp/decoder.rs b/nihav-mpeg/src/codecs/mpeg4asp/decoder.rs index a71da94..c497563 100644 --- a/nihav-mpeg/src/codecs/mpeg4asp/decoder.rs +++ b/nihav-mpeg/src/codecs/mpeg4asp/decoder.rs @@ -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)?; diff --git a/nihav-mpeg/src/codecs/mpeg4asp/dsp.rs b/nihav-mpeg/src/codecs/mpeg4asp/dsp.rs index 7db0b74..7d6d493 100644 --- a/nihav-mpeg/src/codecs/mpeg4asp/dsp.rs +++ b/nihav-mpeg/src/codecs/mpeg4asp/dsp.rs @@ -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, src: NAVideoBufferRef, 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 = 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, 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, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef, 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, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef, 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, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef, 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, mb_x: usize, mb_y: usize, pframe: NAVideoBufferRef, 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); } } diff --git a/nihav-mpeg/src/codecs/mpeg4asp/mod.rs b/nihav-mpeg/src/codecs/mpeg4asp/mod.rs index d9d3e56..d614293 100644 --- a/nihav-mpeg/src/codecs/mpeg4asp/mod.rs +++ b/nihav-mpeg/src/codecs/mpeg4asp/mod.rs @@ -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(); + }, _ => {}, } } diff --git a/nihav-mpeg/src/codecs/mpeg4asp/types.rs b/nihav-mpeg/src/codecs/mpeg4asp/types.rs index 28ff0c3..f79f29b 100644 --- a/nihav-mpeg/src/codecs/mpeg4asp/types.rs +++ b/nihav-mpeg/src/codecs/mpeg4asp/types.rs @@ -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;