From 2eca5e8fa29fb692be17a2265da3317632253115 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Wed, 16 Oct 2024 18:35:25 +0200 Subject: [PATCH] add KMVC decoder --- nihav-game/Cargo.toml | 3 +- nihav-game/src/codecs/kmvc.rs | 445 +++++++++++++++++++++++++++++++++ nihav-game/src/codecs/mod.rs | 4 + nihav-registry/src/register.rs | 2 + 4 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 nihav-game/src/codecs/kmvc.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index 5b8151a..a32c211 100644 --- a/nihav-game/Cargo.toml +++ b/nihav-game/Cargo.toml @@ -37,7 +37,7 @@ demuxer_vx = ["demuxers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] -all_video_decoders = ["decoder_beam_fcp", "decoder_beam_vbv", "decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_ipma", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_rbt", "decoder_seq", "decoder_sga", "decoder_smush_video", "decoder_vmd", "decoder_vx"] +all_video_decoders = ["decoder_beam_fcp", "decoder_beam_vbv", "decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_ipma", "decoder_kmvc", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_rbt", "decoder_seq", "decoder_sga", "decoder_smush_video", "decoder_vmd", "decoder_vx"] decoder_beam_fcp = ["decoders"] decoder_beam_vbv = ["decoders"] decoder_bmv = ["decoders"] @@ -45,6 +45,7 @@ decoder_bmv3 = ["decoders"] decoder_fstvid = ["decoders"] decoder_gdvvid = ["decoders"] decoder_ipma = ["decoders"] +decoder_kmvc = ["decoders"] decoder_midivid = ["decoders"] decoder_midivid3 = ["decoders"] decoder_q = ["decoders"] diff --git a/nihav-game/src/codecs/kmvc.rs b/nihav-game/src/codecs/kmvc.rs new file mode 100644 index 0000000..88cb7f4 --- /dev/null +++ b/nihav-game/src/codecs/kmvc.rs @@ -0,0 +1,445 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +struct HybridReader<'a> { + src: &'a [u8], + pos: usize, + bitbuf: u8, + bits: u8, +} + +impl<'a> HybridReader<'a> { + fn new(src: &'a [u8]) -> Self { + Self { + src, + pos: 1, + bitbuf: if !src.is_empty() { src[0] } else { 0 }, + bits: 8, + } + } + fn read_byte(&mut self) -> DecoderResult { + if self.pos < self.src.len() { + let ret = self.src[self.pos]; + self.pos += 1; + Ok(ret) + } else { + Err(DecoderError::ShortData) + } + } + fn read_bit(&mut self) -> DecoderResult { + let ret = (self.bitbuf & 0x80) != 0; + self.bitbuf <<= 1; + self.bits -= 1; + if self.bits == 0 { + self.bitbuf = self.read_byte()?; + self.bits = 8; + } + Ok(ret) + } +} + +struct FrameDecoder<'a, 'b> { + width: usize, + height: usize, + stride: usize, + dst: &'a mut [u8], + prev_frm: &'a [u8], + hr: HybridReader<'b>, +} + +impl<'a, 'b> FrameDecoder<'a, 'b> { + fn new(dst: &'a mut [u8], prev_frm: &'a [u8], width: usize, height: usize, hr: HybridReader<'b>) -> Self { + Self { + width, height, + stride: width, + dst, prev_frm, + hr, + } + } + fn decode_intra1(&mut self, blk_size: usize) -> DecoderResult<()> { + for y in (0..self.height).step_by(blk_size) { + for x in (0..self.width).step_by(blk_size) { + self.decode_block1(x, y, blk_size)?; + } + } + Ok(()) + } + fn decode_block1(&mut self, x: usize, y: usize, blk_size: usize) -> DecoderResult<()> { + let cur_addr = x + y * self.stride; + if blk_size == 1 { + self.dst[cur_addr] = self.hr.read_byte()?; + return Ok(()); + } + if !self.hr.read_bit()? { + let fill = self.hr.read_byte()?; + for line in self.dst[cur_addr..].chunks_mut(self.stride).take(blk_size) { + for el in line[..blk_size].iter_mut() { + *el = fill; + } + } + } else { + let hsize = blk_size >> 1; + for n in 0..4 { + self.decode_block1(x + (n & 1) * hsize, y + (n >> 1) * hsize, hsize)?; + } + } + Ok(()) + } + fn decode_inter2(&mut self, blk_size: usize) -> DecoderResult<()> { + for y in (0..self.height).step_by(blk_size) { + for x in (0..self.width).step_by(blk_size) { + self.decode_block2(x, y, blk_size)?; + } + } + Ok(()) + } + fn decode_block2(&mut self, x: usize, y: usize, blk_size: usize) -> DecoderResult<()> { + let cur_addr = x + y * self.stride; + if blk_size == 1 { + self.dst[cur_addr] = self.hr.read_byte()?; + return Ok(()); + } + if !self.hr.read_bit()? { + if self.hr.read_bit()? { + let fill = self.hr.read_byte()?; + for line in self.dst[cur_addr..].chunks_mut(self.stride).take(blk_size) { + for el in line[..blk_size].iter_mut() { + *el = fill; + } + } + } else { + for (dline, sline) in self.dst[cur_addr..].chunks_mut(self.stride) + .zip(self.prev_frm[cur_addr..].chunks(self.stride)).take(blk_size) { + dline[..blk_size].copy_from_slice(&sline[..blk_size]); + } + } + } else { + let hsize = blk_size >> 1; + for n in 0..4 { + self.decode_block2(x + (n & 1) * hsize, y + (n >> 1) * hsize, hsize)?; + } + } + Ok(()) + } + fn decode_intra3(&mut self, blk_size: usize) -> DecoderResult<()> { + for y in (0..self.height).step_by(blk_size) { + for x in (0..self.width).step_by(blk_size) { + self.decode_block3(x, y, blk_size, true)?; + } + } + Ok(()) + } + fn decode_block3(&mut self, x: usize, y: usize, blk_size: usize, top: bool) -> DecoderResult<()> { + let mut cur_addr = x + y * self.stride; + if blk_size == 1 { + self.dst[cur_addr] = self.hr.read_byte()?; + return Ok(()); + } + if !self.hr.read_bit()? { + if top || !self.hr.read_bit()? { + let fill = self.hr.read_byte()?; + for line in self.dst[cur_addr..].chunks_mut(self.stride).take(blk_size) { + for el in line[..blk_size].iter_mut() { + *el = fill; + } + } + } else { + let mv = self.hr.read_byte()? as usize; + let offset = (mv & 0xF) + (mv >> 4) * self.stride; + validate!(offset <= cur_addr); + for _ in 0..blk_size { + for x in 0..blk_size { + self.dst[cur_addr + x] = self.dst[cur_addr + x - offset]; + } + cur_addr += self.stride; + } + } + } else { + let hsize = blk_size >> 1; + for n in 0..4 { + self.decode_block3(x + (n & 1) * hsize, y + (n >> 1) * hsize, hsize, false)?; + } + } + Ok(()) + } + fn decode_inter4(&mut self, blk_size: usize) -> DecoderResult<()> { + for y in (0..self.height).step_by(blk_size) { + for x in (0..self.width).step_by(blk_size) { + self.decode_block4(x, y, blk_size, true)?; + } + } + Ok(()) + } + fn decode_block4(&mut self, x: usize, y: usize, blk_size: usize, top: bool) -> DecoderResult<()> { + let cur_addr = x + y * self.stride; + if blk_size == 1 { + self.dst[cur_addr] = self.hr.read_byte()?; + return Ok(()); + } + if !self.hr.read_bit()? { + if !self.hr.read_bit()? { + let fill = self.hr.read_byte()?; + for line in self.dst[cur_addr..].chunks_mut(self.stride).take(blk_size) { + for el in line[..blk_size].iter_mut() { + *el = fill; + } + } + } else if top { + for (dline, sline) in self.dst[cur_addr..].chunks_mut(self.stride) + .zip(self.prev_frm[cur_addr..].chunks(self.stride)).take(blk_size) { + dline[..blk_size].copy_from_slice(&sline[..blk_size]); + } + } else { + let mv = self.hr.read_byte()? as usize; + let mv_x = mv & 0xF; + let mv_y = mv >> 4; + validate!(x + mv_x >= 8 && x + mv_x - 8 + blk_size <= self.width); + validate!(y + mv_y >= 8 && y + mv_y - 8 + blk_size <= self.height); + let ref_addr = cur_addr + mv_x - 8 + mv_y * self.stride - 8 * self.stride; + for (dline, sline) in self.dst[cur_addr..].chunks_mut(self.stride) + .zip(self.prev_frm[ref_addr..].chunks(self.stride)).take(blk_size) { + dline[..blk_size].copy_from_slice(&sline[..blk_size]); + } + } + } else { + let hsize = blk_size >> 1; + for n in 0..4 { + self.decode_block4(x + (n & 1) * hsize, y + (n >> 1) * hsize, hsize, false)?; + } + } + Ok(()) + } +} + +struct KMVCDecoder { + info: NACodecInfoRef, + pal: [u8; 768], + palsize: usize, + frame: Vec, + pframe: Vec, + width: usize, + height: usize, + firstpal: bool, +} + +impl KMVCDecoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + pal: [0; 768], + palsize: 127, + frame: Vec::new(), + pframe: Vec::new(), + width: 0, + height: 0, + firstpal: false, + } + } +} + +impl NADecoder for KMVCDecoder { + 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.pframe = vec![0; self.width * self.height]; + self.pal = [0; 768]; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + + if let Some(edata) = info.get_extradata() { + if edata.len() >= 12 { + let palsize = read_u16le(&edata[10..12])? as usize; + validate!(palsize > 0 && palsize < 256); + self.palsize = palsize; + } + if edata.len() == 1036 { + for (dst, src) in self.pal.chunks_exact_mut(3).zip(edata[12..].chunks_exact(4)) { + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + } + self.firstpal = true; + } + } + + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() >= 2); + + for sd in pkt.side_data.iter() { + if let NASideData::Palette(true, ref pal) = sd { + if !self.firstpal { + for (dst, src) in self.pal.chunks_exact_mut(3).zip(pal.chunks_exact(4)) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } else { + self.firstpal = false; + } + break; + } + } + + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + let header = br.read_byte()?; + let method = header & 0xF; + if (header & 0x80) != 0 && method < 3 { + let start_clr = br.read_byte()? as usize; + let nclrs = br.read_byte()? as usize; + validate!(start_clr + nclrs <= 256); + br.read_buf(&mut self.pal[start_clr * 3..][..nclrs * 3])?; + } + if (header & 0x40) != 0 { + br.read_buf(&mut self.pal[3..][..self.palsize * 3])?; + } + let blk_size = br.read_byte()? as usize; + validate!(blk_size > 0 && (blk_size & (blk_size - 1)) == 0); + validate!(self.width % blk_size == 0); + + let hr = HybridReader::new(&src[br.tell() as usize..]); + std::mem::swap(&mut self.frame, &mut self.pframe); + let mut dec = FrameDecoder::new(&mut self.frame, &self.pframe, self.width, self.height, hr); + match method { + 1 => dec.decode_intra1(blk_size)?, + 2 => dec.decode_inter2(blk_size)?, + 3 => dec.decode_intra3(blk_size)?, + 4 => dec.decode_inter4(blk_size)?, + _ => return Err(DecoderError::NotImplemented), + } + + 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 (header & 0x80) != 0 { FrameType::I } else { FrameType::P }; + frm.set_frame_type(ftype); + frm.set_keyframe(ftype == FrameType::I); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for KMVCDecoder { + 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(KMVCDecoder::new()) +} + +#[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_kmvc_0() { + 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); + + // sample from Alien Breed (PC version) + test_decoding("avi", "kmvc", "assets/Game/OUTRO.ANM", + Some(12), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x2b2cd2eb, 0x02c7d77e, 0xbf70ce2e, 0x8bad43b0], + [0x1a12d41f, 0x3309992e, 0xba018966, 0xa6349e19], + [0xe8793445, 0x0f3eb58a, 0x5d218de3, 0xe45f0a23], + [0xf42c5018, 0x98cd93c7, 0x01f2c546, 0xb9a825ce], + [0x7c01159b, 0x4688930a, 0x00d82ebb, 0x29db5b96], + [0xb3b659ac, 0xe16138a4, 0xc45369d4, 0xc139db89], + [0xdd024a89, 0x58063f17, 0xc50b8975, 0x3e0465d7], + [0x842f6bcf, 0xf0f6b4ac, 0xda0d2653, 0xa858d7ec], + [0xa6aec7ba, 0x2c234945, 0x8a04525f, 0x5f6a4ab7], + [0x7b6345fe, 0x0ab6f159, 0x438307f6, 0xf442ad08], + [0x89a7ed16, 0x4e2cfcea, 0xb75a002d, 0xbac95a16], + [0x6b18e35a, 0xe96bd87c, 0x071e5bd9, 0x77c19d1c], + [0xc7e3e93f, 0xaffeb4f8, 0x2908e8ed, 0x9f3e4ce9]])); + } + #[test] + fn test_kmvc_1() { + 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); + + // sample from https://samples.mplayerhq.hu/V-codecs/KMVC/FLAME.AVI + test_decoding("avi", "kmvc", "assets/Game/FLAME.AVI", + Some(12), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xf0ad790f, 0x7e3a57d0, 0xee9282c1, 0x15547d92], + [0x3a6b2a58, 0x6eaf7960, 0x9d4ff92e, 0x1833677a], + [0xfe29ef44, 0x53ad9d6b, 0x0f90644e, 0x2e063cc0], + [0x2888fc3e, 0x3931e3c5, 0xf152a8ac, 0xde4bfc6f], + [0xc1fb2980, 0x7921d476, 0xd9946a83, 0xed437fdb], + [0x8fcca880, 0x884260e9, 0x2f147ae1, 0x1ac3a77d], + [0xcb93573f, 0xb9f00b90, 0xac09cbd6, 0xb65e852b], + [0x293d955c, 0x15a0ec08, 0x2decbeeb, 0xb00a6845], + [0x7b75471a, 0x0e720f1a, 0x679b3b76, 0x6f0acfa2], + [0xcd4cef26, 0x9315612f, 0x6f7066a5, 0x9e931046], + [0x8eaced5f, 0xa1e04658, 0xbb4784eb, 0xe5e64c93], + [0x54a1e90d, 0xe9db5d38, 0x247ff005, 0x3f9c8e2b], + [0xa65668a6, 0x581883d1, 0xaca638cc, 0x8e459a9f]])); + } + #[test] + fn test_kmvc_2() { + 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); + + // sample from https://samples.mplayerhq.hu/V-codecs/KMVC/baseball1.avi + test_decoding("avi", "kmvc", "assets/Game/baseball1.avi", + Some(24), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0xf8778594, 0xd044befd, 0x054f3dcd, 0x72178a7b], + [0x5aefe81d, 0xa193c130, 0x960ffb88, 0x576405b2], + [0x95db7747, 0xdf4d34d7, 0x959c5c67, 0x8cf55708], + [0xcd000e61, 0xd5c852f0, 0xe97ea320, 0x7465ddc7], + [0xddcc347d, 0xb87eff08, 0xe6b5e9ab, 0xbc2e2f72], + [0x98f0f17f, 0x30b5ea8c, 0xb6a2eddc, 0x2f15fff6], + [0xe2fa257a, 0x2a24eda0, 0x0fe85644, 0x7b796130], + [0x96521427, 0x5a9f9e12, 0x7b1ffeff, 0x4b94f0c3], + [0x5d72afa7, 0x954e8404, 0x5598f536, 0x63e60d08], + [0xce3fc2df, 0x98b067a2, 0xf85c597f, 0x45797ff5], + [0x9f86d7bd, 0x3cac2a08, 0xdd00788e, 0x5d8a4731], + [0x6f0b9bda, 0x0613670c, 0xe9f3f75d, 0x0ef112c9], + [0x549ef3ee, 0xb7c582b7, 0xf995b7fe, 0x62140120], + [0xcd29a18d, 0x0c51e581, 0x3dc8e1eb, 0x3dea5759], + [0xb8bd405c, 0xd674557f, 0x9d783ccc, 0x75893613], + [0x922084d6, 0xc36fdcfc, 0x46bfe658, 0x7bb5922a], + [0xc237a20b, 0xbaacd94f, 0xc09a1665, 0xd988132d]])); + } +} diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index 7708f45..ba1ce15 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -22,6 +22,8 @@ pub mod futurevision; pub mod gremlinvideo; #[cfg(feature="decoder_ipma")] pub mod ipma; +#[cfg(feature="decoder_kmvc")] +pub mod kmvc; #[cfg(feature="decoder_lhst500f22")] pub mod lhst500f22; #[cfg(feature="decoder_midivid")] @@ -70,6 +72,8 @@ const GAME_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "ipma", get_decoder: ipma::get_decoder }, #[cfg(feature="decoder_ipma")] DecoderInfo { name: "ipma2", get_decoder: ipma::get_decoder_v2 }, +#[cfg(feature="decoder_kmvc")] + DecoderInfo { name: "kmvc", get_decoder: kmvc::get_decoder }, #[cfg(feature="decoder_q")] DecoderInfo { name: "legend-q-video", get_decoder: q::get_decoder }, #[cfg(feature="decoder_rbt")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 3df96a0..d39339a 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -266,6 +266,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "hl-fmv-video", "Highlander FMV video"), desc!(video-llp; "ipma", "Imagination Pilots Matte Animation"), desc!(video-llp; "ipma2", "Imagination Pilots Matte Animation v2"), + desc!(video; "kmvc", "Karl Morton's Video Codec"), desc!(video; "legend-q-video", "Legend Entertainment Q video"), desc!(video; "midivid", "MidiVid"), desc!(video; "midivid3", "MidiVid 3"), @@ -338,6 +339,7 @@ static AVI_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"Ipma", "ipma"), (b"Ip20", "ipma2"), + (b"KMVC", "kmvc"), (b"MVDV", "midivid"), (b"MV30", "midivid3"), -- 2.39.5