From 3813fe8a3408aa3b6dcb296ca7203c89f2a6894d Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sun, 14 Feb 2021 12:53:02 +0100 Subject: [PATCH] Fable IMAX video support --- nihav-game/Cargo.toml | 6 +- nihav-game/src/codecs/imax.rs | 165 ++++++++++++++++++++++++++++++++ nihav-game/src/codecs/mod.rs | 4 + nihav-game/src/demuxers/imax.rs | 144 ++++++++++++++++++++++++++++ nihav-game/src/demuxers/mod.rs | 4 + nihav-registry/src/detect.rs | 6 ++ nihav-registry/src/register.rs | 1 + 7 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 nihav-game/src/codecs/imax.rs create mode 100644 nihav-game/src/demuxers/imax.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index 6aab363..d1f9c24 100644 --- a/nihav-game/Cargo.toml +++ b/nihav-game/Cargo.toml @@ -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 index 0000000..5a4c814 --- /dev/null +++ b/nihav-game/src/codecs/imax.rs @@ -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 { + 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 { None } +} + +pub fn get_decoder() -> Box { + 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])); + } +} diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index d98233b..fb30a05 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -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 index 0000000..ad427dd --- /dev/null +++ b/nihav-game/src/demuxers/imax.rs @@ -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 { + 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 { 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 + '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); + } + } +} diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs index fcd3dcd..9aec01f 100644 --- a/nihav-game/src/demuxers/mod.rs +++ b/nihav-game/src/demuxers/mod.rs @@ -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")] diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs index a34670e..f10b76e 100644 --- a/nihav-registry/src/detect.rs +++ b/nihav-registry/src/detect.rs @@ -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", diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index fac4e04..75a13d9 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -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"), -- 2.30.2