Fable IMAX video support
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 14 Feb 2021 11:53:02 +0000 (12:53 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 14 Feb 2021 11:53:02 +0000 (12:53 +0100)
nihav-game/Cargo.toml
nihav-game/src/codecs/imax.rs [new file with mode: 0644]
nihav-game/src/codecs/mod.rs
nihav-game/src/demuxers/imax.rs [new file with mode: 0644]
nihav-game/src/demuxers/mod.rs
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 6aab3639f989e64169ef736cf01f5ee9f8f4b64c..d1f9c244fed8ed1b9c3527f57bbb5fb5da6d8a61 100644 (file)
@@ -18,23 +18,25 @@ nihav_commonfmt = { path = "../nihav-commonfmt" }
 [features]
 default = ["all_decoders", "all_demuxers"]
 demuxers = []
-all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_vmd", "demuxer_vx"]
+all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_imax", "demuxer_vmd", "demuxer_vx"]
 demuxer_bmv = ["demuxers"]
 demuxer_bmv3 = ["demuxers"]
 demuxer_fcmp = ["demuxers"]
 demuxer_fst = ["demuxers"]
 demuxer_gdv = ["demuxers"]
+demuxer_imax = ["demuxers"]
 demuxer_vmd = ["demuxers"]
 demuxer_vx = ["demuxers"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
 
-all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_midivid", "decoder_midivid3", "decoder_vmd", "decoder_vx"]
+all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_midivid", "decoder_midivid3", "decoder_vmd", "decoder_vx"]
 decoder_bmv = ["decoders"]
 decoder_bmv3 = ["decoders"]
 decoder_fstvid = ["decoders"]
 decoder_gdvvid = ["decoders"]
+decoder_imax = ["decoders"]
 decoder_midivid = ["decoders"]
 decoder_midivid3 = ["decoders"]
 decoder_vmd = ["decoders"]
diff --git a/nihav-game/src/codecs/imax.rs b/nihav-game/src/codecs/imax.rs
new file mode 100644 (file)
index 0000000..5a4c814
--- /dev/null
@@ -0,0 +1,165 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+
+const FRAME_W: usize = 320;
+const FRAME_H: usize = 160;
+
+struct IMAXDecoder {
+    info:       NACodecInfoRef,
+    pal:        [u8; 768],
+    frame:      [u8; FRAME_W * FRAME_H],
+    hist:       [u8; 32768],
+    hist_pos:   usize,
+}
+
+impl IMAXDecoder {
+    fn new() -> Self {
+        Self {
+            info:       NACodecInfoRef::default(),
+            pal:        [0; 768],
+            frame:      [0; FRAME_W * FRAME_H],
+            hist:       [0; 32768],
+            hist_pos:   0,
+        }
+    }
+}
+
+impl NADecoder for IMAXDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(_vinfo) = info.get_properties() {
+            /*let fmt = NAPixelFormaton::new(ColorModel::RGB(RGBSubmodel::RGB),
+                                           Some(NAPixelChromaton::new(0, 0, true, 8, 0, 0, 3)),
+                                           Some(NAPixelChromaton::new(0, 0, true, 8, 0, 1, 3)),
+                                           Some(NAPixelChromaton::new(0, 0, true, 8, 0, 2, 3)),
+                                           None, None,
+                                           FORMATON_FLAG_PALETTE, 3);*/
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(FRAME_W, FRAME_H, false, PAL8_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() > 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 mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let mut is_intra = true;
+        let mut is_skip  = true;
+        let mut idx = 0;
+        while idx < self.frame.len() {
+            let v                       = br.read_byte()?;
+            let op  = v >> 6;
+            let len = (v & 0x3F) as usize;
+            match op {
+                0 => {
+                    validate!(idx + len <= self.frame.len());
+                    idx += len;
+                    is_intra = false;
+                },
+                1 => {
+                    if len == 0 {
+                        let off         = br.read_u16le()? as usize;
+                        let len         = br.read_byte()? as usize;
+                        validate!(idx + len <= self.frame.len());
+                        validate!(off + len <= self.hist.len());
+                        self.frame[idx..][..len].copy_from_slice(&self.hist[off..][..len]);
+                    } else {
+                        validate!(idx + len <= self.frame.len());
+                                          br.read_buf(&mut self.frame[idx..][..len])?;
+                        if self.hist_pos + len <= self.hist.len() {
+                            self.hist[self.hist_pos..][..len].copy_from_slice(&self.frame[idx..][..len]);
+                            self.hist_pos += len;
+                        }
+                        idx += len;
+                    }
+                    is_skip = false;
+                },
+                2 => {
+                    let pix             = br.read_byte()?;
+                    validate!(idx + len <= self.frame.len());
+                    for _ in 0..len {
+                        self.frame[idx] = pix;
+                        idx += 1;
+                    }
+                    is_skip = false;
+                },
+                _ => {
+                    let len2            = br.read_byte()? as usize;
+                    let skip_len = len * 64 + len2;
+                    validate!(idx + skip_len <= self.frame.len());
+                    idx += skip_len;
+                    is_intra = false;
+                },
+            };
+        }
+
+        let bufinfo = if !is_skip {
+                let binfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+                let mut vbuf = binfo.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(FRAME_W)) {
+                    drow[..FRAME_W].copy_from_slice(srow);
+                }
+                data[paloff..][..768].copy_from_slice(&self.pal);
+                binfo
+            } else {
+                NABufferType::None
+            };
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+        frm.set_keyframe(is_intra);
+        let ftype = if is_skip { FrameType::Skip } else if is_intra { FrameType::I } else { FrameType::P };
+        frm.set_frame_type(ftype);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+    }
+}
+
+impl NAOptionHandler for IMAXDecoder {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    Box::new(IMAXDecoder::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 crate::game_register_all_demuxers;
+    #[test]
+    fn test_imax_video() {
+        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("fable-imax", "fable-imax", "assets/Game/present.imx",
+                      Some(64), &dmx_reg, &dec_reg,
+                      ExpectedTestResult::MD5([0x775e1326, 0x7aa63674, 0x9b8aec54, 0x5caee2e3]));
+    }
+}
index d98233bbfde02c0a8597f4e39f17ac9182cdc054..fb30a05fd8eadb5b5cf1d5e1b65fcf0aac114fbe 100644 (file)
@@ -12,6 +12,8 @@ pub mod bmv3;
 pub mod futurevision;
 #[cfg(feature="decoder_gdvvid")]
 pub mod gremlinvideo;
+#[cfg(feature="decoder_imax")]
+pub mod imax;
 #[cfg(feature="decoder_lhst500f22")]
 pub mod lhst500f22;
 #[cfg(feature="decoder_midivid")]
@@ -42,6 +44,8 @@ const GAME_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "fst-audio", get_decoder: futurevision::get_decoder_audio },
 #[cfg(feature="decoder_fstvid")]
     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_vmd")]
     DecoderInfo { name: "vmd-audio", get_decoder: vmd::get_decoder_audio },
 #[cfg(feature="decoder_vmd")]
diff --git a/nihav-game/src/demuxers/imax.rs b/nihav-game/src/demuxers/imax.rs
new file mode 100644 (file)
index 0000000..ad427dd
--- /dev/null
@@ -0,0 +1,144 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+use std::sync::Arc;
+
+#[allow(dead_code)]
+struct IMAXDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    cur_frame:  u64,
+    apos:       u64,
+    a_id:       usize,
+    v_id:       usize,
+    pal:        Arc<[u8; 1024]>,
+    pal_change: bool,
+}
+
+impl<'a> DemuxCore<'a> for IMAXDemuxer<'a> {
+    #[allow(unused_variables)]
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let src = &mut self.src;
+
+        let magic                       = src.read_tag()?;
+        validate!(&magic == b"IMAX");
+        let nframes                     = u64::from(src.read_u32le()?);
+        let fps                         = u32::from(src.read_u16le()?);
+        let magic2                      = src.read_u16le()?;
+        validate!(magic2 == 0x102);
+        let _zero                       = src.read_u16le()?;
+        let _max_vframe_size            = src.read_u32le()?;
+        let _buffering_size             = src.read_u32le()?;
+
+        let vhdr = NAVideoInfo::new(320, 160, false, PAL8_FORMAT);
+        let vci = NACodecTypeInfo::Video(vhdr);
+        let vinfo = NACodecInfo::new("fable-imax", vci, None);
+        self.v_id = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, fps, nframes)).unwrap();
+        let ahdr = NAAudioInfo::new(22050, 1, SND_U8_FORMAT, 2);
+        let ainfo = NACodecInfo::new("pcm", NACodecTypeInfo::Audio(ahdr), None);
+        self.a_id = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, 22050, 2)).unwrap();
+        self.cur_frame = 0;
+        self.apos = 0;
+        Ok(())
+    }
+
+    #[allow(unused_variables)]
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        loop {
+            let fsize                   = self.src.read_u32le()? as usize;
+            let ftype                   = self.src.read_u32le()?;
+
+            match ftype {
+                0xAA97 => {
+                    let str = strmgr.get_stream(self.v_id).unwrap();
+                    let (tb_num, tb_den) = str.get_timebase();
+                    let ts = NATimeInfo::new(Some(self.cur_frame), None, None, tb_num, tb_den);
+                    self.cur_frame += 1;
+                    let mut pkt = self.src.read_packet(str, ts, true, fsize)?;
+                    pkt.add_side_data(NASideData::Palette(self.pal_change, self.pal.clone()));
+                    self.pal_change = false;
+                    return Ok(pkt);
+                },
+                0xAA98 => {
+                    validate!(fsize == 768);
+                    let mut pal = [0u8; 1024];
+                    for chunk in pal.chunks_mut(4) {
+                        let r           = self.src.read_byte()?;
+                        let g           = self.src.read_byte()?;
+                        let b           = self.src.read_byte()?;
+                        chunk[0] = (r << 2) | (r >> 4);
+                        chunk[1] = (g << 2) | (g >> 4);
+                        chunk[2] = (b << 2) | (b >> 4);
+                    }
+                    self.pal = Arc::new(pal);
+                    self.pal_change = true;
+                },
+                0xAA99 => {
+                    let str = strmgr.get_stream(self.a_id).unwrap();
+                    let (tb_num, tb_den) = str.get_timebase();
+                    let ts = NATimeInfo::new(Some(self.apos), None, None, tb_num, tb_den);
+                    self.apos += fsize as u64;
+                    return self.src.read_packet(str, ts, true, fsize);
+                },
+                0xAAFF => return Err(DemuxerError::EOF),
+                _ => return Err(DemuxerError::InvalidData),
+            }
+        }
+    }
+
+    fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        Err(DemuxerError::NotPossible)
+    }
+    fn get_duration(&self) -> u64 { 0 }
+}
+impl<'a> NAOptionHandler for IMAXDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+impl<'a> IMAXDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        IMAXDemuxer {
+            src:        io,
+            cur_frame:  0,
+            apos:       0,
+            a_id:       0,
+            v_id:       0,
+            pal:        Arc::new([0; 1024]),
+            pal_change: false,
+        }
+    }
+}
+
+pub struct IMAXDemuxerCreator { }
+
+impl DemuxerCreator for IMAXDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(IMAXDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "fable-imax" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_imax_demux() {
+        let mut file = File::open("assets/Game/present.imx").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = IMAXDemuxer::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);
+        }
+    }
+}
index fcd3dcdeaedec5187baf0cc176b6a6a3a91ad97e..9aec01f286b1ab6fd72051c5334dcce3fbb16765 100644 (file)
@@ -11,6 +11,8 @@ mod bmv;
 mod fst;
 #[cfg(feature="demuxer_gdv")]
 mod gdv;
+#[cfg(feature="demuxer_imax")]
+mod imax;
 #[cfg(feature="demuxer_vmd")]
 mod vmd;
 #[cfg(feature="demuxer_vx")]
@@ -27,6 +29,8 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[
     &fst::FSTDemuxerCreator {},
 #[cfg(feature="demuxer_gdv")]
     &gdv::GDVDemuxerCreator {},
+#[cfg(feature="demuxer_imax")]
+    &imax::IMAXDemuxerCreator {},
 #[cfg(feature="demuxer_vmd")]
     &vmd::VMDDemuxerCreator {},
 #[cfg(feature="demuxer_vx")]
index a34670ef049bfc98a4f872a8a7b1ba61ff13bbc2..f10b76e931bb43e18f38d7689094fb9021b98d62 100644 (file)
@@ -235,6 +235,12 @@ const DETECTORS: &[DetectConditions] = &[
         extensions: ".gdv",
         conditions: &[CheckItem{offs: 0, cond: &CC::Eq(Arg::U32LE(0x29111994))}],
     },
+    DetectConditions {
+        demux_name: "fable-imax",
+        extensions: ".imx",
+        conditions: &[CheckItem{offs:  0, cond: &CC::Str(b"IMAX") },
+                      CheckItem{offs: 10, cond: &CC::Eq(Arg::U16LE(0x102)) }],
+    },
     DetectConditions {
         demux_name: "realaudio",
         extensions: ".ra,.ram",
index fac4e04f500b431ee47b0b78f22454f0dd1c03ae..75a13d91e9424896e21fa590f417a302c14b6b7f 100644 (file)
@@ -227,6 +227,7 @@ static CODEC_REGISTER: &'static [CodecDescription] = &[
     desc!(audio;    "bmv-audio",     "BMV audio"),
     desc!(video;    "bmv3-video",    "DW Noir BMV video"),
     desc!(audio;    "bmv3-audio",    "DW Noir BMV audio"),
+    desc!(video;    "fable-imax",    "Fable IMAX video"),
     desc!(video;    "fst-video",     "FutureVision video"),
     desc!(audio;    "fst-audio",     "FutureVision audio"),
     desc!(video;    "midivid",       "MidiVid"),