Arxel CI2 video support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 9 Nov 2023 17:44:17 +0000 (18:44 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 9 Nov 2023 17:44:17 +0000 (18:44 +0100)
nihav-game/src/codecs/arxel_vid.rs
nihav-game/src/demuxers/cnm.rs

index d705c822c9281be4ec6681abea841e6ce5e43656..269b2c07d2a320589b99cde6a47b8a1c706240fb 100644 (file)
@@ -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]));
+    }
 }
index 1c25ec41627ab9dfa52843087c5457cf2bcc66d1..e9e6126e8549a8c444b1e590a31c246b29f43f76 100644 (file)
@@ -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);
+        }
+    }
 }