aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKostya Shishkov <kostya.shishkov@gmail.com>2023-11-09 18:44:17 +0100
committerKostya Shishkov <kostya.shishkov@gmail.com>2023-11-09 18:44:17 +0100
commite31eabc9d7a774e2234835f4deffaa8e5991cb97 (patch)
treea1130b34915a7e354969381ed7c6bde55f41b3eb
parent86b51082f5aa5b94e40aec40c6963ec34071f9b5 (diff)
downloadnihav-e31eabc9d7a774e2234835f4deffaa8e5991cb97.tar.gz
Arxel CI2 video support
-rw-r--r--nihav-game/src/codecs/arxel_vid.rs330
-rw-r--r--nihav-game/src/demuxers/cnm.rs105
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<usize>,
+ contexts: [[usize; 16]; 4096],
+ ctx_pos: [usize; 4096],
+ hams: HAMShuffler<u8>,
}
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<NAFrameRef> {
- 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<NAFrameRef> {
+ 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<u8>,
}
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<NAPacket> {
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);
+ }
+ }
}