From d66e60559ac53f86a8e7a1c31b89a6cf320177d0 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Tue, 5 Oct 2021 12:56:52 +0200 Subject: [PATCH] Imagination Pilots Matte Animation decoder --- nihav-game/Cargo.toml | 3 +- nihav-game/src/codecs/ipma.rs | 252 +++++++++++++++++++++++++++++++++ nihav-game/src/codecs/mod.rs | 6 + nihav-registry/src/register.rs | 5 + 4 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 nihav-game/src/codecs/ipma.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index cfbfca8..8079a21 100644 --- a/nihav-game/Cargo.toml +++ b/nihav-game/Cargo.toml @@ -32,12 +32,13 @@ demuxer_vx = ["demuxers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] -all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"] +all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"] decoder_bmv = ["decoders"] decoder_bmv3 = ["decoders"] decoder_fstvid = ["decoders"] decoder_gdvvid = ["decoders"] decoder_imax = ["decoders"] +decoder_ipma = ["decoders"] decoder_midivid = ["decoders"] decoder_midivid3 = ["decoders"] decoder_q = ["decoders"] diff --git a/nihav-game/src/codecs/ipma.rs b/nihav-game/src/codecs/ipma.rs new file mode 100644 index 0000000..21f657d --- /dev/null +++ b/nihav-game/src/codecs/ipma.rs @@ -0,0 +1,252 @@ +use nihav_core::codecs::*; +use nihav_core::io::bitreader::*; + +const DICT_SIZE: usize = 4096; +const START_BITS: u8 = 9; +const MAX_BITS: u8 = 12; +const START_POS: usize = 258; +const INVALID_POS: usize = 65536; + +struct LZWState { + dict_sym: [u8; DICT_SIZE], + dict_prev: [u16; DICT_SIZE], + dict_pos: usize, + dict_lim: usize, + idx_bits: u8, +} + +impl LZWState { + fn new() -> Self { + Self { + dict_sym: [0; DICT_SIZE], + dict_prev: [0; DICT_SIZE], + dict_pos: START_POS, + dict_lim: 1 << START_BITS, + idx_bits: START_BITS, + } + } + fn reset(&mut self) { + self.dict_pos = START_POS; + self.dict_lim = 1 << START_BITS; + self.idx_bits = START_BITS; + } + fn add(&mut self, prev: usize, sym: u8) { + if self.dict_pos < self.dict_lim { + self.dict_sym [self.dict_pos] = sym; + self.dict_prev[self.dict_pos] = prev as u16; + self.dict_pos += 1; + } + } + fn decode_idx(&self, dst: &mut [u8], pos: usize, idx: usize) -> DecoderResult { + let mut tot_len = 1; + let mut tidx = idx; + while tidx > 256 { + tidx = self.dict_prev[tidx] as usize; + tot_len += 1; + } + validate!(pos + tot_len <= dst.len()); + + let mut end = pos + tot_len - 1; + let mut tidx = idx; + while tidx > 256 { + dst[end] = self.dict_sym[tidx]; + end -= 1; + tidx = self.dict_prev[tidx] as usize; + } + dst[end] = tidx as u8; + + Ok(tot_len) + } + #[allow(clippy::comparison_chain)] + fn decode(&mut self, br: &mut BitReader, dst: &mut [u8]) -> DecoderResult<()> { + self.reset(); + + let mut pos = 0; + let mut lastidx = INVALID_POS; + loop { + let idx = br.read(self.idx_bits)? as usize; + if idx == 256 { + self.reset(); + lastidx = INVALID_POS; + continue; + } + if idx == 257 { + break; + } + if idx < self.dict_pos { + let len = self.decode_idx(dst, pos, idx)?; + if lastidx != INVALID_POS { + self.add(lastidx, dst[pos]); + } + pos += len; + } else if idx == self.dict_pos { + validate!(lastidx != INVALID_POS); + let len = self.decode_idx(dst, pos, lastidx)?; + let lastsym = dst[pos]; + pos += len; + validate!(pos < dst.len()); + dst[pos] = lastsym; + pos += 1; + self.add(lastidx, lastsym); + } else { + return Err(DecoderError::InvalidData); + } + lastidx = idx; + if self.dict_pos == self.dict_lim && self.idx_bits < MAX_BITS { + self.dict_lim <<= 1; + self.idx_bits += 1; + } + } + validate!(pos == dst.len()); + Ok(()) + } +} + +struct IPMADecoder { + info: NACodecInfoRef, + v1: bool, + pal: [u8; 768], + frame: Vec, + fframe: Vec, + dbuf: Vec, + width: usize, + height: usize, + first: bool, + lzw: LZWState, +} + +impl IPMADecoder { + fn new(v1: bool) -> Self { + Self { + info: NACodecInfoRef::default(), + v1, + pal: [0; 768], + frame: Vec::new(), + fframe: Vec::new(), + dbuf: Vec::new(), + width: 0, + height: 0, + first: false, + lzw: LZWState::new(), + } + } +} + +impl NADecoder for IPMADecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = vinfo.width; + self.height = vinfo.height; + self.frame = vec![0; self.width * self.height]; + self.fframe = vec![0; self.width * self.height]; + self.dbuf = vec![0; self.width * self.height]; + self.pal = [0; 768]; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, true, PAL8_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + + self.first = self.v1; + + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 0); + + for sd in pkt.side_data.iter() { + if let NASideData::Palette(true, ref pal) = sd { + for (dst, src) in self.pal.chunks_mut(3).zip(pal.chunks(4)) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + break; + } + } + + let mut br = BitReader::new(&src, BitReaderMode::LE); + self.lzw.decode(&mut br, &mut self.dbuf)?; + if self.first { + self.fframe.copy_from_slice(&self.dbuf); + self.first = false; + } + for (dp, (&sp, &rp)) in self.frame.iter_mut().zip(self.dbuf.iter().zip(self.fframe.iter())) { + match sp { + 0 => {}, + 1 => *dp = rp, + _ => *dp = sp, + }; + } + + let buf = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + let mut vbuf = buf.get_vbuf().unwrap(); + let paloff = vbuf.get_offset(1); + let stride = vbuf.get_stride(0); + let data = vbuf.get_data_mut().unwrap(); + + for (drow, srow) in data.chunks_mut(stride).zip(self.frame.chunks(self.width)) { + drow[..self.width].copy_from_slice(srow); + } + data[paloff..][..768].copy_from_slice(&self.pal); + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buf); + let ftype = if pkt.keyframe { FrameType::I } else { FrameType::P }; + frm.set_frame_type(ftype); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for IPMADecoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub fn get_decoder() -> Box { + Box::new(IPMADecoder::new(true)) +} + +pub fn get_decoder_v2() -> Box { + Box::new(IPMADecoder::new(false)) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use nihav_commonfmt::generic_register_all_demuxers; + #[test] + fn test_ipma_video() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("avi", "ipma", "assets/Game/lripa07.avi", + Some(2), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x13a4e2e5, 0xeac494b1, 0xef107b9f, 0x1213bbc6], + [0x09e2684a, 0xe453ec76, 0xff1c3cee, 0xf38701c9], + [0xd39258b1, 0x2491b8d9, 0x37dd4415, 0x8ddb4669]])); + } + #[test] + fn test_ip20_video() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("avi", "ipma2", "assets/Game/27mwza00.avi", + Some(2), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x8b000e62, 0x2e4e0344, 0x471e40ca, 0x7b9f83d4], + [0xc1429aa9, 0x3f2e856b, 0x80b27114, 0x25b5d453], + [0xfa24d5fa, 0x29bf2e17, 0x3cf5f550, 0x296e29b0]])); + } +} diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index 799ecc8..8e735f2 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -14,6 +14,8 @@ pub mod futurevision; pub mod gremlinvideo; #[cfg(feature="decoder_imax")] pub mod imax; +#[cfg(feature="decoder_ipma")] +pub mod ipma; #[cfg(feature="decoder_lhst500f22")] pub mod lhst500f22; #[cfg(feature="decoder_midivid")] @@ -48,6 +50,10 @@ const GAME_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "fst-video", get_decoder: futurevision::get_decoder_video }, #[cfg(feature="decoder_imax")] DecoderInfo { name: "fable-imax", get_decoder: imax::get_decoder }, +#[cfg(feature="decoder_ipma")] + DecoderInfo { name: "ipma", get_decoder: ipma::get_decoder }, +#[cfg(feature="decoder_ipma")] + DecoderInfo { name: "ipma2", get_decoder: ipma::get_decoder_v2 }, #[cfg(feature="decoder_q")] DecoderInfo { name: "legend-q-video", get_decoder: q::get_decoder }, #[cfg(feature="decoder_vmd")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index a89e2a7..1a8e1eb 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -236,6 +236,8 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "fable-imax", "Fable IMAX video"), desc!(video; "fst-video", "FutureVision video"), desc!(audio; "fst-audio", "FutureVision audio"), + desc!(video-llp; "ipma", "Imagination Pilots Matte Animation"), + desc!(video-llp; "ipma2", "Imagination Pilots Matte Animation v2"), desc!(video; "legend-q-video", "Legend Entertainment Q video"), desc!(video; "midivid", "MidiVid"), desc!(video; "midivid3", "MidiVid 3"), @@ -290,6 +292,9 @@ static AVI_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"cvid", "cinepak"), (b"ZMBV", "zmbv"), + (b"Ipma", "ipma"), + (b"Ip20", "ipma2"), + (b"MVDV", "midivid"), (b"MV30", "midivid3"), (b"MVLZ", "midivid-ll"), -- 2.30.2