From e31eabc9d7a774e2234835f4deffaa8e5991cb97 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Thu, 9 Nov 2023 18:44:17 +0100 Subject: [PATCH] Arxel CI2 video support --- nihav-game/src/codecs/arxel_vid.rs | 330 ++++++++++++++++++++++++++--- nihav-game/src/demuxers/cnm.rs | 105 +++++++-- 2 files changed, 388 insertions(+), 47 deletions(-) diff --git a/nihav-game/src/codecs/arxel_vid.rs b/nihav-game/src/codecs/arxel_vid.rs index d705c82..269b2c0 100644 --- a/nihav-game/src/codecs/arxel_vid.rs +++ b/nihav-game/src/codecs/arxel_vid.rs @@ -1,6 +1,7 @@ use nihav_core::codecs::*; use nihav_core::io::byteio::*; use nihav_core::io::bitreader::*; +use nihav_codec_support::codecs::HAMShuffler; const HEADER_SIZE: usize = 0x2F; @@ -17,6 +18,11 @@ const BPP: usize = 4; struct ArxelVideoDecoder { info: NACodecInfoRef, tiles: [u8; 65536], + version: u8, + idx_buf: Vec, + contexts: [[usize; 16]; 4096], + ctx_pos: [usize; 4096], + hams: HAMShuffler, } impl ArxelVideoDecoder { @@ -24,36 +30,15 @@ impl ArxelVideoDecoder { Self { info: NACodecInfoRef::default(), tiles: [0; 65536], + version: 0, + idx_buf: Vec::new(), + contexts: [[0; 16]; 4096], + ctx_pos: [0; 4096], + hams: HAMShuffler::new(), } } -} - -const RGBA_FORMAT: NAPixelFormaton = NAPixelFormaton { - model: ColorModel::RGB(RGBSubmodel::RGB), components: 4, - comp_info: [ - Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }), - Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }), - Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }), - Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }), - None ], - elem_size: 4, be: false, alpha: true, palette: false }; - -impl NADecoder for ArxelVideoDecoder { - fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { - if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { - let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), true, RGBA_FORMAT)); - self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); - - Ok(()) - } else { - Err(DecoderError::InvalidData) - } - } - fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { - let src = pkt.get_buffer(); - validate!(src.len() > HEADER_SIZE); - - let mut mr = MemoryReader::new_read(&src); + fn decode_v1(&mut self, src: &[u8]) -> DecoderResult<(NABufferType, bool)> { + let mut mr = MemoryReader::new_read(src); let mut br = ByteReader::new(&mut mr); let size = br.read_u32le()? as usize; @@ -138,13 +123,287 @@ impl NADecoder for ArxelVideoDecoder { let (src, dst) = lines.split_at_mut(stride); dst[..stride].copy_from_slice(src); } + Ok((bufinfo, true)) + } + fn add_to_context(&mut self, prev: usize, cur: usize) { + self.contexts[prev][self.ctx_pos[prev]] = cur; + self.ctx_pos[prev] += 1; + if self.ctx_pos[prev] == 16 { + self.ctx_pos[prev] = 0; + } + } + fn decode_v2(&mut self, src: &[u8]) -> DecoderResult<(NABufferType, bool)> { + let mut mr = MemoryReader::new_read(src); + let mut br = ByteReader::new(&mut mr); + + let mut has_tiles = false; + let mut is_55 = false; + loop { + let ftype = br.read_byte()?; + match ftype { + 0x54 => { + let size = br.read_u32le()? as usize; + let num_tiles = br.read_u16le()? as usize; + let tile_size = br.read_u16le()? as usize; + + let tile_w = if tile_size == 2 { 2 } else { 4 }; + let tsize = tile_w * BPP; + + match tile_size { + 2 | 4 => { + validate!(size >= tsize); + br.read_buf(&mut self.tiles[..tsize])?; + let off = br.tell() as usize; + let mut bir = BitReader::new(&src[off..][..size - tsize], BitReaderMode::LE); + for tile in 1..num_tiles { + let (prev_tiles, cur_tile) = self.tiles.split_at_mut(tile * tsize); + cur_tile[..16].copy_from_slice(&prev_tiles[prev_tiles.len() - 16..]); + for comp in 0..BPP { + let bits = bir.read(3)? as u8; + if bits == 0 { + continue; + } + for i in 0..tile_size { + let el = &mut cur_tile[i * BPP + comp]; + *el = match bits { + 7 => { + bir.read(8)? as u8 + }, + _ => { + let mut delta = bir.read(bits)? as i16; + if delta != 0 && bir.read_bool()? { + delta = -delta; + } + (i16::from(*el) + delta) as u8 + }, + }; + } + } + } + br.read_skip(size - tsize)?; + has_tiles = true; + }, + _ => { + unimplemented!(); + }, + }; + }, + 0x53 => break, + 0x55 => { + is_55 = true; + break; + }, + _ => return Err(DecoderError::InvalidData), + }; + } + + let size = br.read_u32le()? as usize; + validate!(size + HEADER_SIZE <= (br.left() as usize) + 4); + let part2_off = br.read_u32le()?; + validate!(part2_off as usize == size); + let num_tiles = br.read_u16le()? as usize; + validate!((0..4096).contains(&num_tiles)); + let tile_size = br.read_u16le()? as usize; + let width = br.read_u32le()? as usize; + let height = br.read_u32le()? as usize; + br.read_skip(0x1B)?; + + let vinfo = self.info.get_properties().get_video_info().unwrap(); + validate!(width == vinfo.get_width()); + validate!(height == vinfo.get_height()); + let is_intra = is_55 && has_tiles; + + let mut vbuf = if is_intra { + let binfo = alloc_video_buffer(vinfo, 0)?; + let vbuf = binfo.get_vbuf().unwrap(); + self.hams.add_frame(vbuf); + self.hams.get_output_frame().unwrap() + } else { + if let Some(buf) = self.hams.clone_ref() { + buf + } else { + return Err(DecoderError::MissingReference); + } + }; + let stride = vbuf.get_stride(0); + let data = vbuf.get_data_mut().unwrap(); + let dst = data.as_mut_slice(); + + let tile_w = if tile_size == 2 { 2 } else { 4 }; + let tsize = tile_w * BPP; + let mut idx_bits = 0; + let mut v = num_tiles; + while v > 0 { + idx_bits += 1; + v >>= 1; + } + let start = br.tell() as usize; + let mut br = BitReader::new(&src[start..], BitReaderMode::LE); + let mut ypos = 0; + let mut last_seen = [0usize.wrapping_sub(1); 4096]; + let mut cand_list = Vec::with_capacity(4); + let istride = width / tile_w; + self.idx_buf.resize(istride * height, 0); + self.contexts = [[0; 16]; 4096]; + self.ctx_pos = [0; 4096]; + + for y in 0..height { + for x8 in (0..istride).step_by(8) { + let pos = ypos + x8; + if br.read_bool()? { + validate!(y > 0); + for x in 0..8 { + self.idx_buf[pos + x] = self.idx_buf[pos + x - istride]; + } + } else { + for x in 0..8 { + if br.read_bool()? { + validate!(y > 0); + self.idx_buf[pos + x] = self.idx_buf[pos + x - istride]; + } else { + let mode = br.read(2)?; + match mode { + 0 => { + let idx = br.read(idx_bits)? as usize; + self.idx_buf[pos + x] = idx; + if y > 0 { + self.add_to_context(self.idx_buf[pos + x - istride], idx); + } + }, + 1 => { + cand_list.clear(); + let cur_pos = pos + x; + if y > 0 { + last_seen[self.idx_buf[cur_pos - istride]] = cur_pos; + } + if x8 + x > 0 { + let src_idx = cur_pos - 1; + if last_seen[self.idx_buf[src_idx]] != cur_pos { + cand_list.push(self.idx_buf[src_idx]); + last_seen[self.idx_buf[src_idx]] = cur_pos; + } + } + if (y > 0) && (x8 + x > 0) { + let src_idx = cur_pos - 1 - istride; + if last_seen[self.idx_buf[src_idx]] != cur_pos { + cand_list.push(self.idx_buf[src_idx]); + last_seen[self.idx_buf[src_idx]] = cur_pos; + } + } + if (y > 0) && (x8 + x + 1 < istride) { + let src_idx = cur_pos + 1 - istride; + if last_seen[self.idx_buf[src_idx]] != cur_pos { + cand_list.push(self.idx_buf[src_idx]); + last_seen[self.idx_buf[src_idx]] = cur_pos; + } + } + if y > 1 { + let src_idx = cur_pos - 2 * istride; + if last_seen[self.idx_buf[src_idx]] != cur_pos { + cand_list.push(self.idx_buf[src_idx]); + last_seen[self.idx_buf[src_idx]] = cur_pos; + } + } + + validate!(!cand_list.is_empty()); + self.idx_buf[cur_pos] = match cand_list.len() { + 1 => cand_list[0], + 2 => cand_list[br.read(1)? as usize], + _ => { + let idx = br.read(2)? as usize; + validate!(idx < cand_list.len()); + cand_list[idx] + }, + }; + if y > 0 { + self.add_to_context(self.idx_buf[cur_pos - istride], self.idx_buf[cur_pos]); + } + }, + 2 => { + validate!(y > 0); + let top_idx = self.idx_buf[pos + x - istride]; + let delta = br.read(4)? as usize + 1; + self.idx_buf[pos + x] = if !br.read_bool()? { + validate!(top_idx + delta < num_tiles); + top_idx + delta + } else { + validate!(top_idx >= delta); + top_idx - delta + }; + if y > 0 { + self.add_to_context(self.idx_buf[pos + x - istride], self.idx_buf[pos + x]); + } + }, + _ => { + validate!(y > 0); + let idx = br.read(4)? as usize; + self.idx_buf[pos + x] = self.contexts[self.idx_buf[pos + x - istride]][idx]; + }, + } + } + } + } + } + ypos += istride; + } + + for (dline, sline) in dst.chunks_mut(stride).take(height).zip(self.idx_buf.chunks_exact(istride)) { + for (dst, &idx) in dline.chunks_exact_mut(tsize).zip(sline.iter()) { + if idx != 0 || is_intra { + dst.copy_from_slice(&self.tiles[idx * tsize..][..tsize]); + } + } + } + + Ok((NABufferType::Video(vbuf), is_intra)) + } +} + +const RGBA_FORMAT: NAPixelFormaton = NAPixelFormaton { + model: ColorModel::RGB(RGBSubmodel::RGB), components: 4, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }), + None ], + elem_size: 4, be: false, alpha: true, palette: false }; + +impl NADecoder for ArxelVideoDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), true, RGBA_FORMAT)); + if let Some(edata) = info.get_extradata() { + validate!(!edata.is_empty()); + if edata[0] > 1 { + return Err(DecoderError::NotImplemented); + } + self.version = edata[0]; + } + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > HEADER_SIZE); + + let (bufinfo, is_intra) = match self.version { + 0 => self.decode_v1(&src)?, + 1 => self.decode_v2(&src)?, + _ => return Err(DecoderError::NotImplemented), + }; let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); - frm.set_keyframe(true); - frm.set_frame_type(FrameType::I); + frm.set_keyframe(is_intra); + frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P }); Ok(frm.into_ref()) } fn flush(&mut self) { + self.hams.clear(); } } @@ -177,4 +436,15 @@ mod test { test_decoding("arxel-cnm", "arxel-video", "assets/Game/logo.cnm", Some(10), &dmx_reg, &dec_reg, ExpectedTestResult::MD5([0x9b1fc970, 0x1fe86e2c, 0x44dd9255, 0x3920c49b])); } + // sample from Faust: The Seven Games of the Soul game + #[test] + fn test_arxel_video_v2() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("arxel-cnm", "arxel-video", "assets/Game/logo.CI2", Some(10), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x3bf66a39, 0x6627f529, 0x4ed19e8e, 0xc0693aae])); + } } diff --git a/nihav-game/src/demuxers/cnm.rs b/nihav-game/src/demuxers/cnm.rs index 1c25ec4..e9e6126 100644 --- a/nihav-game/src/demuxers/cnm.rs +++ b/nihav-game/src/demuxers/cnm.rs @@ -9,6 +9,8 @@ struct ArxelCinemaDemuxer<'a> { vpts: u64, tb_num: u32, tb_den: u32, + is_ci2: bool, + tdata: Vec, } impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { @@ -35,12 +37,12 @@ impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { let nframes = src.read_u32le()? as usize; src.read_u32le()?; //nframes again? let tab_size = src.read_u32le()? as usize; - validate!(tab_size == nframes * 8); src.read_skip(0x98)?; let vhdr = NAVideoInfo::new(width, height, true, RGB24_FORMAT); let vci = NACodecTypeInfo::Video(vhdr); - let vinfo = NACodecInfo::new("arxel-video", vci, None); + // use tab_size mismatch as the version marker + let vinfo = NACodecInfo::new("arxel-video", vci, Some(vec![(tab_size != nframes * 8) as u8])); if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 100, tb_den, nframes as u64)).is_none() { return Err(DemuxerError::MemoryError); } @@ -66,11 +68,23 @@ impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { } } - let tab_size = tab_size / 4; - self.offsets = Vec::with_capacity(tab_size); - for _ in 0..tab_size { - let offset = src.read_u32le()?; - self.offsets.push(offset); + if tab_size == nframes * 8 { + let tab_size = tab_size / 4; + self.offsets = Vec::with_capacity(tab_size); + for _ in 0..tab_size { + let offset = src.read_u32le()?; + self.offsets.push(offset); + } + } else { + validate!(nframes > 0); + let off0 = src.read_u32le()?; + let off1 = src.read_u32le()?; + if off0 == 0 && off1 == 0 { + self.is_ci2 = true; + src.read_skip((nframes - 1) * 8)?; + } else { + return Err(DemuxerError::InvalidData); + } } self.cur_frame = 0; self.vpts = 0; @@ -80,16 +94,24 @@ impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { #[allow(unused_variables)] fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { loop { - if self.cur_frame >= self.offsets.len() { return Err(DemuxerError::EOF); } - let pos = u64::from(self.offsets[self.cur_frame]); - let stream_id = (self.cur_frame % (self.astreams.max(1) + 1)) as u32; - self.cur_frame += 1; - if pos == 0 { - continue; - } - + let stream_id; + if !self.is_ci2 { + if self.cur_frame >= self.offsets.len() { return Err(DemuxerError::EOF); } + let pos = u64::from(self.offsets[self.cur_frame]); + stream_id = (self.cur_frame % (self.astreams.max(1) + 1)) as u32; + self.cur_frame += 1; + if pos == 0 { + continue; + } self.src.seek(SeekFrom::Start(pos))?; - let ftype = self.src.read_byte()?; + } else { + stream_id = 1; + } + let ftype = match self.src.read_byte() { + Ok(b) => b, + Err(ByteIOError::EOF) if self.is_ci2 => return Err(DemuxerError::EOF), + _ => return Err(DemuxerError::IOError), + }; match ftype { 0x41 | 0x42 | 0x5A => { let size = self.src.read_u32le()? as usize; @@ -103,7 +125,7 @@ impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { self.src.read_skip(size)?; } }, - 0x53 => { + 0x53 if !self.is_ci2 => { let size = self.src.peek_u32le()? as usize; if let Some(stream) = strmgr.get_stream_by_id(0) { let ts = stream.make_ts(Some(self.vpts), None, None); @@ -113,12 +135,39 @@ impl<'a> DemuxCore<'a> for ArxelCinemaDemuxer<'a> { return Err(DemuxerError::MemoryError); } }, + 0x53 | 0x55 => { + let size = self.src.peek_u32le()? as usize; + let mut data = Vec::new(); + std::mem::swap(&mut self.tdata, &mut data); + data.push(ftype); + let head_size = data.len(); + data.resize(head_size + size + 0x2F, 0); + self.src.read_buf(&mut data[head_size..])?; + if let Some(stream) = strmgr.get_stream_by_id(0) { + let ts = stream.make_ts(Some(self.vpts), None, None); + self.vpts += 1; + return Ok(NAPacket::new(stream, ts, ftype == 0x55, data)); + } else { + return Err(DemuxerError::MemoryError); + } + }, + 0x54 => { + validate!(self.is_ci2); + let size = self.src.peek_u32le()? as usize; + validate!(self.tdata.is_empty()); + self.tdata.resize(size + 9, 0); + self.tdata[0] = 0x54; + self.src.read_buf(&mut self.tdata[1..])?; + }, _ => continue, }; } } fn seek(&mut self, time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { + if self.is_ci2 { + return Err(DemuxerError::NotPossible); + } match time { NATimePoint::PTS(pts) => self.seek_to_frame(pts), NATimePoint::Milliseconds(ms) => { @@ -148,6 +197,8 @@ impl<'a> ArxelCinemaDemuxer<'a> { vpts: 0, tb_num: 0, tb_den: 0, + is_ci2: false, + tdata: Vec::new(), } } fn seek_to_frame(&mut self, pts: u64) -> DemuxerResult<()> { @@ -214,4 +265,24 @@ mod test { println!("Got {}", pkt); } } + // sample from Faust: The Seven Games of the Soul game + #[test] + fn test_ci2_demux() { + let mut file = File::open("assets/Game/logo.CI2").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = ArxelCinemaDemuxer::new(&mut br); + let mut sm = StreamManager::new(); + let mut si = SeekIndex::new(); + dmx.open(&mut sm, &mut si).unwrap(); + loop { + let pktres = dmx.get_frame(&mut sm); + if let Err(e) = pktres { + if (e as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } } -- 2.30.2