VMD demuxer and decoder
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 16 Feb 2019 11:23:57 +0000 (12:23 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 16 Feb 2019 11:23:57 +0000 (12:23 +0100)
nihav-core/src/detect.rs
nihav-core/src/register.rs
nihav-game/Cargo.toml
nihav-game/src/codecs/mod.rs
nihav-game/src/codecs/vmd.rs [new file with mode: 0644]
nihav-game/src/demuxers/mod.rs
nihav-game/src/demuxers/vmd.rs [new file with mode: 0644]

index 0285e64a84ac6034a5c54326d8665d5c77d53557..17b1411df7810fb8be38a9e4a37346206e178d32 100644 (file)
@@ -211,6 +211,11 @@ const DETECTORS: &[DetectConditions] = &[
         extensions: ".bmv",
         conditions: &[],
     },
+    DetectConditions {
+        demux_name: "vmd",
+        extensions: ".vmd",
+        conditions: &[],
+    },
 ];
 
 pub fn detect_format(name: &str, src: &mut ByteReader) -> Option<(&'static str, DetectionScore)> {
index 8d6bab4c693e4985be22ab693eb05bc1e2409e54..56d2cfc99a2fa67bfd019887ada07161b6375e11 100644 (file)
@@ -165,6 +165,8 @@ static CODEC_REGISTER: &'static [CodecDescription] = &[
     desc!(audio;    "gdv-audio",     "Gremlin Digital Video - audio"),
     desc!(video;    "bmv-video",     "BMV video"),
     desc!(audio;    "bmv-audio",     "BMV audio"),
+    desc!(video;    "vmd-video",     "VMD video"),
+    desc!(audio;    "vmd-audio",     "VMD audio"),
 
     desc!(video;    "smacker-video", "Smacker video"),
     desc!(audio;    "smacker-audio", "Smacker audio"),
index 2c1e800f5d1ed75a87da60083258fae8ba5bca6a..1da57042adadfcdae39b536a674ed376559063b3 100644 (file)
@@ -11,15 +11,17 @@ features = []
 [features]
 default = ["all_decoders", "all_demuxers"]
 demuxers = []
-all_demuxers = ["demuxer_bmv", "demuxer_gdv"]
+all_demuxers = ["demuxer_bmv", "demuxer_gdv", "demuxer_vmd"]
 demuxer_bmv = ["demuxers"]
 demuxer_gdv = ["demuxers"]
+demuxer_vmd = ["demuxers"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
 
-all_video_decoders = ["decoder_bmv", "decoder_gdvvid"]
+all_video_decoders = ["decoder_bmv", "decoder_gdvvid", "decoder_vmd"]
 decoder_bmv = ["decoders"]
 decoder_gdvvid = ["decoders"]
+decoder_vmd = ["decoders"]
 
 all_audio_decoders = []
index 2bf29cca90705e7aa56fc10f481788b5cfdbf7da..fde660144eca543b3bbad99eadee77b57828d8f3 100644 (file)
@@ -8,6 +8,8 @@ macro_rules! validate {
 pub mod bmv;
 #[cfg(feature="decoder_gdvvid")]
 pub mod gremlinvideo;
+#[cfg(feature="decoder_vmd")]
+pub mod vmd;
 
 const GAME_CODECS: &[DecoderInfo] = &[
 #[cfg(feature="decoder_gdvvid")]
@@ -18,6 +20,10 @@ const GAME_CODECS: &[DecoderInfo] = &[
     DecoderInfo { name: "bmv-audio", get_decoder: bmv::get_decoder_audio },
 #[cfg(feature="decoder_bmv")]
     DecoderInfo { name: "bmv-video", get_decoder: bmv::get_decoder_video },
+#[cfg(feature="decoder_vmd")]
+    DecoderInfo { name: "vmd-audio", get_decoder: vmd::get_decoder_audio },
+#[cfg(feature="decoder_vmd")]
+    DecoderInfo { name: "vmd-video", get_decoder: vmd::get_decoder_video },
 ];
 
 pub fn game_register_all_codecs(rd: &mut RegisteredDecoders) {
diff --git a/nihav-game/src/codecs/vmd.rs b/nihav-game/src/codecs/vmd.rs
new file mode 100644 (file)
index 0000000..c6d98aa
--- /dev/null
@@ -0,0 +1,466 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use std::str::FromStr;
+
+macro_rules! lz_op {
+    (read; $dst:ident, $dpos:expr, $window:ident, $wpos:expr, $br:expr, $dst_size:expr) => {
+        validate!($dpos < $dst_size);
+        let b = $br.read_byte()?;
+        $dst[$dpos] = b;
+        $dpos += 1;
+        $window[$wpos] = b;
+        $wpos = ($wpos + 1) & 0xFFF;
+    };
+    (copy; $dst:ident, $dpos:expr, $window:ident, $wpos:expr, $off:expr, $dst_size:expr) => {
+        let b = $window[$off];
+        validate!($dpos < $dst_size);
+        $dst[$dpos] = b;
+        $dpos += 1;
+        $window[$wpos] = b;
+        $wpos = ($wpos + 1) & 0xFFF;
+        $off = ($off + 1) & 0xFFF;
+    };
+}
+fn lz_unpack(br: &mut ByteReader, dst: &mut [u8]) -> DecoderResult<()> {
+    let mut window: [u8; 0x1000] = [0x20; 0x1000];
+
+    let dst_size = br.read_u32le()? as usize;
+    validate!(dst_size <= dst.len());
+    let mut pos;
+    let esc_len;
+    if br.peek_u32le()? == 0x56781234 {
+        br.read_skip(4)?;
+        pos = 0x111;
+        esc_len = 15;
+    } else {
+        pos = 0xFEE;
+        esc_len = 255;
+    }
+
+    let mut opos = 0;
+    while br.left() > 0 && opos < dst_size {
+        let op = br.read_byte()?;
+        if (op == 0xFF) && (br.left() > 8) {
+            for _ in 0..8 {
+                lz_op!(read; dst, opos, window, pos, br, dst_size);
+            }
+        } else {
+            for i in 0..8 {
+                if opos == dst_size { break; }
+                let is_literal = ((op >> i) & 1) != 0;
+                if is_literal {
+                    lz_op!(read; dst, opos, window, pos, br, dst_size);
+                } else {
+                    let b0 = br.read_byte()? as usize;
+                    let b1 = br.read_byte()? as usize;
+                    let mut off = b0 | ((b1 & 0xF0) << 4);
+                    let mut len = b1 & 0xF;
+                    if len == esc_len {
+                        len = (br.read_byte()? as usize) + esc_len;
+                    }
+                    for _ in 0..len+3 {
+                        lz_op!(copy; dst, opos, window, pos, off, dst_size);
+                    }
+                }
+            }
+        }
+    }
+    Ok(())
+}
+
+fn rle_unpack(br: &mut ByteReader, len: usize, dst: &mut [u8]) -> DecoderResult<()> {
+    let end = br.tell() + (len as u64);
+    let mut dpos = 0;
+    if (len & 1) != 0 {
+        dst[dpos] = br.read_byte()?;
+        dpos += 1;
+    }
+    while dpos < dst.len() && br.tell() < end {
+        let val = br.read_byte()?;
+        let len = ((val & 0x7F) as usize) * 2;
+        validate!(dpos + len <= dst.len());
+        if (val & 0x80) != 0 {
+            let dst = &mut dst[dpos..][..len];
+            br.read_buf(dst)?;
+        } else {
+            let val = br.read_byte()?;
+            for i in 0..len {
+                dst[dpos + i] = val;
+            }
+        }
+        dpos += len;
+    }
+    Ok(())
+}
+
+fn decode_frame_data(br: &mut ByteReader, dst: &mut [u8], mut dpos: usize, stride: usize, w: usize, h: usize, method: u8) -> DecoderResult<bool> {
+    match method {
+        1 => {
+            for _ in 0..h {
+                let mut x = 0;
+                while x < w {
+                    let val                     = br.read_byte()?;
+                    let len = ((val & 0x7F) as usize) + 1;
+                    validate!(x + len <= w);
+                    if (val & 0x80) != 0 {
+                        let pix = &mut dst[dpos + x..][..len];
+                                                  br.read_buf(pix)?;
+                    } // otherwise skip already existing data
+                    x += len;
+                }
+                dpos += stride;
+            }
+            Ok(false)
+        },
+        2 => {
+            for _ in 0..h {
+                let pix = &mut dst[dpos..][..w];
+                                                  br.read_buf(pix)?;
+                dpos += stride;
+            }
+            Ok(true)
+        },
+        3 => {
+            for _ in 0..h {
+                let mut x = 0;
+                while x < w {
+                    let val                     = br.read_byte()?;
+                    let len = ((val & 0x7F) as usize) + 1;
+                    validate!(x + len <= w);
+                    if (val & 0x80) != 0 {
+                        let pix = &mut dst[dpos + x..][..len];
+                        if br.peek_byte()? == 0xFF {
+                                                  br.read_skip(1)?;
+                            rle_unpack(br, len, pix)?;
+                        } else {
+                                                  br.read_buf(pix)?;
+                        }
+                    } // otherwise data is already there
+                    x += len;
+                }
+                dpos += stride;
+            }
+            Ok(false)
+        },
+        _ => return Err(DecoderError::InvalidData),
+    }
+}
+
+struct VMDVideoDecoder {
+    info:       Rc<NACodecInfo>,
+    pal:        [u8; 768],
+    buf:        Vec<u8>,
+    width:      usize,
+    height:     usize,
+    hams:       HAMShuffler,
+}
+
+impl VMDVideoDecoder {
+    fn new() -> Self {
+        Self {
+            info:       Rc::new(NACodecInfo::default()),
+            pal:        [0; 768],
+            buf:        Vec::new(),
+            width:      0,
+            height:     0,
+            hams:       HAMShuffler::default(),
+        }
+    }
+    fn decode_frame(&mut self, br: &mut ByteReader, buf: &mut NAVideoBuffer<u8>) -> DecoderResult<bool> {
+        let paloff = buf.get_offset(1);
+        let stride = buf.get_stride(0);
+        let mut data = buf.get_data_mut();
+        let dst = data.as_mut_slice();
+
+        let frame_x                             = br.read_u16le()? as usize;
+        let frame_y                             = br.read_u16le()? as usize;
+        let frame_l                             = br.read_u16le()? as usize;
+        let frame_d                             = br.read_u16le()? as usize;
+                                                  br.read_skip(1)?;
+        let flags                               = br.read_byte()?;
+        let has_pal = (flags & 0x02) != 0;
+        validate!(frame_l >= frame_x && frame_d >= frame_y);
+        validate!(frame_l < self.width && frame_d < self.height);
+
+        if has_pal {
+                                                  br.read_skip(2)?;
+            for e in self.pal.iter_mut() {
+                let val                         = br.read_byte()?;
+                *e = (val << 2) | (val >> 4);
+            }
+        }
+
+        let dpal = &mut dst[paloff..][..768];
+        dpal.copy_from_slice(&self.pal[0..]);
+
+        if br.left() == 0 { return Ok(false); }
+
+        let w = frame_l + 1 - frame_x;
+        let h = frame_d + 1 - frame_y;
+        let dpos = frame_x + frame_y * stride;
+
+        let method                              = br.read_byte()?;
+        let is_intra;
+        if (method & 0x80) != 0 {
+            validate!(self.buf.len() > 0);
+            lz_unpack(br, &mut self.buf)?;
+            let mut mr = MemoryReader::new_read(&self.buf);
+            let mut buf_br = ByteReader::new(&mut mr);
+            is_intra = decode_frame_data(&mut buf_br, dst, dpos, stride, w, h, method & 0x7F)?;
+        } else {
+            is_intra = decode_frame_data(br, dst, dpos, stride, w, h, method & 0x7F)?;
+        }
+        Ok(is_intra && frame_x == 0 && frame_y == 0 && w == self.width && h == self.height)
+    }
+}
+
+impl NADecoder for VMDVideoDecoder {
+    fn init(&mut self, info: Rc<NACodecInfo>) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            self.width  = vinfo.get_width();
+            self.height = vinfo.get_height();
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT));
+            self.info = Rc::new(NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()));
+            validate!(info.get_extradata().is_some());
+
+            if let Some(ref edata) = info.get_extradata() {
+                validate!(edata.len() == 0x330);
+                let unp_size = read_u32le(&edata[800..])? as usize;
+                validate!(unp_size < self.width * self.height * 3 + 64); // just for sanity
+                self.buf.resize(unp_size, 0);
+                for i in 0..768 {
+                    let el = edata[28 + i];
+                    self.pal[i] = (el << 2) | (el >> 4);
+                }
+            }
+
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() >= 10);
+
+        let mut mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let mut buf;
+        let bufret = self.hams.clone_ref();
+        if let Some(bbuf) = bufret {
+            buf = bbuf;
+        } else {
+            let bufret = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 4);
+            if let Err(_) = bufret { return Err(DecoderError::InvalidData); }
+            let bufinfo = bufret.unwrap();
+            buf = bufinfo.get_vbuf().unwrap();
+            self.hams.add_frame(buf);
+            buf = self.hams.get_output_frame().unwrap();
+        }
+
+        let is_intra = self.decode_frame(&mut br, &mut buf)?;
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), NABufferType::Video(buf));
+        frm.set_keyframe(is_intra);
+        frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P });
+        Ok(Rc::new(RefCell::new(frm)))
+    }
+}
+
+
+pub fn get_decoder_video() -> Box<NADecoder> {
+    Box::new(VMDVideoDecoder::new())
+}
+
+struct VMDAudioDecoder {
+    ainfo:      NAAudioInfo,
+    chmap:      NAChannelMap,
+    is16bit:    bool,
+    blk_align:  usize,
+    blk_size:   usize,
+}
+
+const SOL_AUD_STEPS16: [i16; 128] = [
+     0x00,   0x08,   0x10,   0x20,   0x30,   0x40,   0x50,   0x60,
+     0x70,   0x80,   0x90,   0xA0,   0xB0,   0xC0,   0xD0,   0xE0,
+     0xF0,  0x100,  0x110,  0x120,  0x130,  0x140,  0x150,  0x160,
+    0x170,  0x180,  0x190,  0x1A0,  0x1B0,  0x1C0,  0x1D0,  0x1E0,
+    0x1F0,  0x200,  0x208,  0x210,  0x218,  0x220,  0x228,  0x230,
+    0x238,  0x240,  0x248,  0x250,  0x258,  0x260,  0x268,  0x270,
+    0x278,  0x280,  0x288,  0x290,  0x298,  0x2A0,  0x2A8,  0x2B0,
+    0x2B8,  0x2C0,  0x2C8,  0x2D0,  0x2D8,  0x2E0,  0x2E8,  0x2F0,
+    0x2F8,  0x300,  0x308,  0x310,  0x318,  0x320,  0x328,  0x330,
+    0x338,  0x340,  0x348,  0x350,  0x358,  0x360,  0x368,  0x370,
+    0x378,  0x380,  0x388,  0x390,  0x398,  0x3A0,  0x3A8,  0x3B0,
+    0x3B8,  0x3C0,  0x3C8,  0x3D0,  0x3D8,  0x3E0,  0x3E8,  0x3F0,
+    0x3F8,  0x400,  0x440,  0x480,  0x4C0,  0x500,  0x540,  0x580,
+    0x5C0,  0x600,  0x640,  0x680,  0x6C0,  0x700,  0x740,  0x780,
+    0x7C0,  0x800,  0x900,  0xA00,  0xB00,  0xC00,  0xD00,  0xE00,
+    0xF00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+];
+
+impl VMDAudioDecoder {
+    fn new() -> Self {
+        Self {
+            ainfo:  NAAudioInfo::new(0, 1, SND_S16P_FORMAT, 0),
+            chmap:  NAChannelMap::new(),
+            is16bit: false,
+            blk_align: 0,
+            blk_size: 0,
+        }
+    }
+    fn decode_16bit(&self, dst: &mut [i16], off1: usize, br: &mut ByteReader, nblocks: usize, mut mask: u32) -> DecoderResult<()> {
+        let channels = self.chmap.num_channels();
+        let mut off = [0, off1];
+        for _ in 0..nblocks {
+            if (mask & 1) != 0 {
+                for ch in 0..channels {
+                    for i in 0..self.blk_align {
+                        dst[off[ch] + i] = 0;
+                    }
+                    off[ch] += self.blk_align;
+                }
+            } else {
+                let mut pred: [i32; 2] = [0; 2];
+                for ch in 0..channels {
+                    pred[ch]                        = br.read_u16le()? as i32;
+                    dst[off[ch]] = pred[ch] as i16;
+                    off[ch] += 1;
+                }
+                let mut ch = 0;
+                let flip_ch = if channels == 2 { 1 } else { 0 };
+                for _ in 1..self.blk_align {
+                    let b                           = br.read_byte()? as usize;
+                    if (b & 0x80) != 0 {
+                        pred[ch] -= SOL_AUD_STEPS16[b & 0x7F] as i32;
+                    } else {
+                        pred[ch] += SOL_AUD_STEPS16[b & 0x7F] as i32;
+                    }
+                    //pred[ch] = pred[ch].max(-32768).min(32767);
+                    dst[off[ch]] = pred[ch] as i16;
+                    off[ch] += 1;
+                    ch ^= flip_ch;
+                }
+            }
+            mask >>= 1;
+        }
+        validate!(br.left() == 0);
+        Ok(())
+    }
+}
+
+impl NADecoder for VMDAudioDecoder {
+    fn init(&mut self, info: Rc<NACodecInfo>) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
+            let fmt;
+            if ainfo.get_format().get_bits() == 8 {
+                fmt = SND_U8_FORMAT;
+                self.is16bit = false;
+                self.blk_size = ainfo.get_block_len();
+            } else {
+                fmt = SND_S16P_FORMAT;
+                self.is16bit = true;
+                self.blk_size = (ainfo.get_block_len() + 1) * (ainfo.get_channels() as usize);
+            };
+            self.blk_align = ainfo.get_block_len() / (ainfo.get_channels() as usize);
+            self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), ainfo.get_channels(), fmt, ainfo.get_block_len());
+            self.chmap = NAChannelMap::from_str(if ainfo.get_channels() == 1 { "C" } else { "L,R" }).unwrap();
+            Ok(())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn decode(&mut self, pkt: &NAPacket) -> DecoderResult<NAFrameRef> {
+        let info = pkt.get_stream().get_info();
+        if let NACodecTypeInfo::Audio(_) = info.get_properties() {
+            let pktbuf = pkt.get_buffer();
+            validate!(pktbuf.len() >= 6);
+            let mut mr = MemoryReader::new_read(&pktbuf);
+            let mut br = ByteReader::new(&mut mr);
+            let blk_type                        = br.read_byte()?;
+                                                  br.read_skip(9)?;
+            let mask;
+            let nblocks;
+            if blk_type == 2 { // initial
+                mask                            = br.read_u32le()?;
+                nblocks = (mask.count_ones() as usize) + (pktbuf.len() - 14) / self.blk_size;
+            } else if blk_type == 3 { // silence
+                mask    = 1;
+                nblocks = 1;
+            } else {
+                mask    = 0;
+                nblocks = 1;
+            }
+            let samples = nblocks * self.blk_align;
+            let abuf = alloc_audio_buffer(self.ainfo, samples, self.chmap.clone())?;
+            if self.is16bit {
+                let mut adata = abuf.get_abuf_i16().unwrap();
+                let off1 = adata.get_offset(1);
+                let mut dst = adata.get_data_mut();
+                self.decode_16bit(&mut dst, off1, &mut br, nblocks, mask)?;
+            } else {
+                let mut adata = abuf.get_abuf_u8().unwrap();
+                let mut dst = adata.get_data_mut();
+                let mut doff = 0;
+                let mut mask = mask;
+                let channels = self.chmap.num_channels();
+                for _ in 0..nblocks {
+                    if (mask & 1) != 0 {
+                        for i in 0..self.blk_align * channels {
+                            dst[doff + i] = 0;
+                        }
+                    } else {
+                        for i in 0..self.blk_align * channels {
+                            dst[doff + i]       = br.read_byte()?;
+                        }
+                    }
+                    doff += self.blk_align;
+                    mask >>= 1;
+                }
+            }
+
+            let mut frm = NAFrame::new_from_pkt(pkt, info, abuf);
+            frm.set_duration(Some(samples as u64));
+            frm.set_keyframe(true);
+            Ok(Rc::new(RefCell::new(frm)))
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+}
+
+pub fn get_decoder_audio() -> Box<NADecoder> {
+    Box::new(VMDAudioDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_core::test::dec_video::*;
+    use crate::codecs::game_register_all_codecs;
+    use crate::demuxers::game_register_all_demuxers;
+    #[test]
+    fn test_vmd_video() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        game_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        game_register_all_codecs(&mut dec_reg);
+
+//        let file = "assets/1491.VMD";
+        let file = "assets/128.vmd";
+        test_file_decoding("vmd", file, Some(10), true, false, None/*Some("vmd")*/, &dmx_reg, &dec_reg);
+    }
+    #[test]
+    fn test_vmd_audio() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        game_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        game_register_all_codecs(&mut dec_reg);
+
+//        let file = "assets/1491.VMD";
+        let file = "assets/128.vmd";
+        test_decode_audio("vmd", file, None, "vmd", &dmx_reg, &dec_reg);
+    }
+}
index f361ea36a2e206a79962225061db66c9818fb3a9..9d7d18728ceb0c85ac55c5d45eb2844dd040ea5e 100644 (file)
@@ -9,12 +9,16 @@ macro_rules! validate {
 mod bmv;
 #[cfg(feature="demuxer_gdv")]
 mod gdv;
+#[cfg(feature="demuxer_vmd")]
+mod vmd;
 
 const GAME_DEMUXERS: &[&'static DemuxerCreator] = &[
 #[cfg(feature="demuxer_bmv")]
     &bmv::BMVDemuxerCreator {},
 #[cfg(feature="demuxer_gdv")]
     &gdv::GDVDemuxerCreator {},
+#[cfg(feature="demuxer_vmd")]
+    &vmd::VMDDemuxerCreator {},
 ];
 
 pub fn game_register_all_demuxers(rd: &mut RegisteredDemuxers) {
diff --git a/nihav-game/src/demuxers/vmd.rs b/nihav-game/src/demuxers/vmd.rs
new file mode 100644 (file)
index 0000000..74fe3a7
--- /dev/null
@@ -0,0 +1,199 @@
+use nihav_core::frame::*;
+use nihav_core::demuxers::*;
+use std::io::SeekFrom;
+
+const HEADER_SIZE: usize = 0x330;
+const FRAME_HDR_SIZE: usize = 10;
+
+const CHTYPE_VIDEO: u8 = 0x02;
+const CHTYPE_AUDIO: u8 = 0x01;
+
+#[derive(Clone,Copy)]
+struct FrameRec {
+    chtype:     u8,
+    size:       u32,
+    off:        u32,
+    hdr:        [u8; FRAME_HDR_SIZE],
+    ts:         u32,
+}
+
+struct VMDDemuxer<'a> {
+    src:        &'a mut ByteReader<'a>,
+    vid_id:     usize,
+    aud_id:     usize,
+    fno:        usize,
+    is_indeo:   bool,
+    frames:     Vec<FrameRec>,
+}
+
+impl<'a> DemuxCore<'a> for VMDDemuxer<'a> {
+    #[allow(unused_variables)]
+    fn open(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> {
+        let src = &mut self.src;
+
+        let mut header: [u8; HEADER_SIZE] = [0; HEADER_SIZE];
+                                                src.read_buf(&mut header)?;
+
+        let mut width  = read_u16le(&header[12..])? as usize;
+        let mut height = read_u16le(&header[14..])? as usize;
+        self.is_indeo = &header[24..27] == b"iv3";
+        if self.is_indeo && width > 320 {
+            width  >>= 1;
+            height >>= 1;
+        }
+
+        let nframes = read_u16le(&header[6..])? as usize;
+        let fpb     = read_u16le(&header[18..])? as usize;
+        validate!(nframes > 0 && fpb > 0);
+        
+        let mut edata: Vec<u8> = Vec::with_capacity(HEADER_SIZE);
+        edata.extend_from_slice(&header);
+        let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT);
+        let vci = NACodecTypeInfo::Video(vhdr);
+        let vinfo = NACodecInfo::new(if !self.is_indeo { "vmd-video" } else { "indeo3" }, vci, Some(edata));
+        self.vid_id = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 12)).unwrap();
+
+        let srate = read_u16le(&header[804..])? as u32;
+        if srate > 0 {
+            let bsize = read_u16le(&header[806..])? as usize;
+            let channels = if (header[811] & 0x80) != 0 { 2 } else { 1 };
+            let is16bit;
+            let block_size;
+            if (bsize & 0x8000) != 0 {
+                is16bit = true;
+                block_size = 0x10000 - bsize;
+            } else {
+                is16bit = false;
+                block_size = bsize;
+            }
+
+            let ahdr = NAAudioInfo::new(srate, channels, if is16bit { SND_S16P_FORMAT } else { SND_U8_FORMAT }, block_size);
+            let ainfo = NACodecInfo::new("vmd-audio", NACodecTypeInfo::Audio(ahdr), None);
+            self.aud_id = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, srate)).unwrap();
+        }
+
+        let adelay  = read_u16le(&header[808..])? as u32;
+        let idx_off = read_u32le(&header[812..])? as u64;
+                                                src.seek(SeekFrom::Start(idx_off))?;
+        let mut offs: Vec<u32> = Vec::with_capacity(nframes);
+        for i in 0..nframes {
+            let _flags                          = src.read_u16le()?;
+            let off                             = src.read_u32le()?;
+            offs.push(off);
+        }
+        self.frames.reserve(nframes * fpb);
+        let mut ats = adelay;
+        for i in 0..nframes {
+            let mut off = offs[i];
+            for _ in 0..fpb {
+                let chtype                      = src.read_byte()?;
+                                                  src.read_skip(1)?;
+                let size                        = src.read_u32le()?;
+                let mut hdr: [u8; FRAME_HDR_SIZE] = [0; FRAME_HDR_SIZE];
+                                                  src.read_buf(&mut hdr)?;
+                if (chtype == CHTYPE_VIDEO || chtype == CHTYPE_AUDIO) && (size > 0) {
+                    let ts = if (i == 0) || (chtype != CHTYPE_AUDIO) {
+                            i as u32
+                        } else {
+                            ats
+                        };
+                    self.frames.push(FrameRec { chtype, size, hdr, off, ts });
+                }
+                if i > 0 && chtype == CHTYPE_AUDIO {
+                    ats += 1;
+                }
+                if chtype != 0 {
+                    validate!(off.checked_add(size).is_some());
+                    off += size;
+                }
+            }
+        }
+
+        self.fno = 0;
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        if self.fno >= self.frames.len() { return Err(DemuxerError::EOF); }
+        let cur_frame = &self.frames[self.fno];
+//println!("fno {} -> type {} size {} @ {:X} ts {}", self.fno, cur_frame.chtype, cur_frame.size, cur_frame.off, cur_frame.ts);
+        let next_pos = cur_frame.off as u64;
+        if self.src.tell() != next_pos {
+            self.src.seek(SeekFrom::Start(next_pos))?;
+        }
+
+        let is_video = cur_frame.chtype == CHTYPE_VIDEO;
+        let mut buf: Vec<u8> = Vec::with_capacity(FRAME_HDR_SIZE + (cur_frame.size as usize));
+        if !self.is_indeo || !is_video {
+            buf.extend_from_slice(&cur_frame.hdr);
+            buf.resize(FRAME_HDR_SIZE + (cur_frame.size as usize), 0);
+            self.src.read_buf(&mut buf[FRAME_HDR_SIZE..])?;
+        } else {
+            buf.resize(cur_frame.size as usize, 0);
+            self.src.read_buf(&mut buf)?;
+        }
+
+        self.fno += 1;
+
+        let str_id = if is_video { self.vid_id } else { self.aud_id };
+        let str = strmgr.get_stream(str_id).unwrap();
+        let (tb_num, tb_den) = str.get_timebase();
+        let ts = NATimeInfo::new(Some(cur_frame.ts as u64), None, None, tb_num, tb_den);
+        let pkt = NAPacket::new(str, ts, false, buf);
+
+        Ok(pkt)
+    }
+
+    #[allow(unused_variables)]
+    fn seek(&mut self, time: u64) -> DemuxerResult<()> {
+        Err(DemuxerError::NotImplemented)
+    }
+}
+
+impl<'a> VMDDemuxer<'a> {
+    fn new(io: &'a mut ByteReader<'a>) -> Self {
+        Self {
+            src:        io,
+            vid_id:     0,
+            aud_id:     0,
+            fno:        0,
+            is_indeo:   false,
+            frames:     Vec::new(),
+        }
+    }
+}
+
+pub struct VMDDemuxerCreator { }
+
+impl DemuxerCreator for VMDDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<DemuxCore<'a> + 'a> {
+        Box::new(VMDDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "vmd" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_vmd_demux() {
+        let mut file = File::open("assets/128.vmd").unwrap();
+        //let mut file = File::open("assets/1491.VMD").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = VMDDemuxer::new(&mut br);
+        let mut sm = StreamManager::new();
+        dmx.open(&mut sm).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);
+        }
+    }
+}