add Indeo IVF demuxer
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 13 Oct 2022 16:21:33 +0000 (18:21 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 13 Oct 2022 16:22:10 +0000 (18:22 +0200)
nihav-allstuff/src/lib.rs
nihav-indeo/Cargo.toml
nihav-indeo/src/demuxers/ivf.rs [new file with mode: 0644]
nihav-indeo/src/demuxers/mod.rs [new file with mode: 0644]
nihav-indeo/src/lib.rs
nihav-registry/src/detect.rs

index 1ef01c482d054135416b52288dff9977bf2b1be8..90ee6c3af2fe380cee88cb93b670a6db2a1cc64a 100644 (file)
@@ -10,7 +10,7 @@ use nihav_commonfmt::*;
 use nihav_duck::*;
 use nihav_flash::*;
 use nihav_game::*;
-use nihav_indeo::indeo_register_all_decoders;
+use nihav_indeo::*;
 use nihav_itu::itu_register_all_decoders;
 use nihav_llaudio::*;
 use nihav_misc::*;
@@ -51,6 +51,7 @@ pub fn nihav_register_all_demuxers(rd: &mut RegisteredDemuxers) {
     generic_register_all_demuxers(rd);
     flash_register_all_demuxers(rd);
     game_register_all_demuxers(rd);
+    indeo_register_all_demuxers(rd);
     llaudio_register_all_demuxers(rd);
     rad_register_all_demuxers(rd);
     realmedia_register_all_demuxers(rd);
index a5591bb93c4368be54a882612c5050cb2308b1b0..4243e434b2b557169e4e93329f9621d49d2bfcbb 100644 (file)
@@ -15,7 +15,7 @@ features = ["h263", "fft", "dsp_window"]
 nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers"] }
 
 [features]
-default = ["all_decoders"]
+default = ["all_decoders", "all_demuxers"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 all_video_decoders = ["decoder_indeo2", "decoder_indeo3", "decoder_indeo4", "decoder_indeo5", "decoder_intel263"]
@@ -28,3 +28,8 @@ decoder_indeo3 = ["decoders"]
 decoder_indeo4 = ["decoders"]
 decoder_indeo5 = ["decoders"]
 decoder_intel263 = ["decoders"]
+
+all_demuxers = ["demuxer_ivf"]
+demuxers = []
+
+demuxer_ivf = ["demuxers"]
\ No newline at end of file
diff --git a/nihav-indeo/src/demuxers/ivf.rs b/nihav-indeo/src/demuxers/ivf.rs
new file mode 100644 (file)
index 0000000..952e526
--- /dev/null
@@ -0,0 +1,306 @@
+use nihav_core::demuxers::*;
+
+struct IVFDemuxer<'a> {
+    src:            &'a mut ByteReader<'a>,
+    nframes:        u32,
+    vframe:         u32,
+    aframe:         u32,
+    size:           u64,
+    vframes:        Vec<Vec<u8>>,
+    vsizes:         Vec<u32>,
+    aframes:        Vec<Vec<u8>>,
+    do_v:           bool,
+
+    passes:         u8,
+}
+
+impl<'a> IVFDemuxer<'a> {
+    fn new(src: &'a mut ByteReader<'a>) -> Self {
+        IVFDemuxer {
+            src,
+            nframes:    0,
+            vframe:     0,
+            aframe:     0,
+            size:       0,
+            vframes:    Vec::new(),
+            aframes:    Vec::new(),
+            vsizes:     Vec::new(),
+            do_v:       false,
+
+            passes:     0,
+        }
+    }
+}
+
+const IVF_GUID_0: [u8; 16] = [0x50, 0xEF, 0x81, 0x19, 0xB3, 0xBD, 0xD0, 0x11, 0xA3, 0xE5, 0x00, 0xA0, 0xC9, 0x24, 0x44, 0x36];
+const IVF_GUID_1: [u8; 16] = [0x50, 0xEF, 0x81, 0x19, 0xB3, 0xBD, 0xD0, 0x11, 0xA3, 0xE5, 0x00, 0xA0, 0xC9, 0x24, 0x44, 0x37];
+
+
+impl<'a> DemuxCore<'a> for IVFDemuxer<'a> {
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let mut guid = [0; 16];
+                                          self.src.read_buf(&mut guid)?;
+        let version = match &guid {
+            &IVF_GUID_0 => 0,
+            &IVF_GUID_1 => 1,
+            _ => return Err(DemuxerError::InvalidData),
+        };
+        let flags                       = self.src.read_u32le()?;
+        // file header - 0x9C bytes
+        let aframes                     = self.src.read_u32le()? as usize;
+                                          self.src.read_skip(12)?;
+        self.size                       = u64::from(self.src.read_u32le()?);
+                                          self.src.read_skip(136)?;
+        // video stream header - 0x8C bytes
+        let tag                         = self.src.read_tag()?;
+        validate!(&tag == b"vids");
+                                          self.src.read_skip(16)?;
+        let tb_num                      = self.src.read_u32le()?;
+        let tb_den                      = self.src.read_u32le()?;
+                                          self.src.read_skip(4)?;
+        self.nframes                    = self.src.read_u32le()?;
+                                          self.src.read_skip(104)?;
+
+        let (atb_num, atb_den, aduration) = if (flags & 1) != 0 {
+            // audio stream header - 0x8C bytes
+                let tag                 = self.src.read_tag()?;
+                validate!(&tag == b"auds");
+                                          self.src.read_skip(16)?;
+                let tb_num              = self.src.read_u32le()?;
+                let tb_den              = self.src.read_u32le()?;
+                                          self.src.read_skip(4)?;
+                let duration            = self.src.read_u32le()?;
+                                          self.src.read_skip(104)?;
+                (tb_num, tb_den, duration)
+            } else { (0, 0, 0) };
+
+        let vhdr_size                   = self.src.read_u32le()? as usize;
+        validate!(vhdr_size >= 40);
+        let bmpi_size                   = self.src.read_u32le()? as usize;
+        validate!(bmpi_size == vhdr_size);
+        let width                       = self.src.read_u32le()? as usize;
+        let height                      = self.src.read_u32le()? as i32;
+        let planes                      = self.src.read_u16le()?;
+        let bitcount                    = self.src.read_u16le()?;
+        let fcc                         = self.src.read_tag()?;
+                                          self.src.read_skip(20)?;
+
+        let mut vhdr = NAVideoInfo::new(width, height.abs() as usize, height < 0, YUV420_FORMAT);
+        vhdr.bits = (planes as u8) * (bitcount as u8);
+        let cname = match &fcc {
+                b"IV31" | b"IV32" => "indeo3",
+                b"IV41" => "indeo4",
+                b"IV50" => "indeo5",
+                _ => "unknown",
+            };
+        let edata = if vhdr_size > 40 {
+                let mut buf = vec![0; vhdr_size - 40];
+                                          self.src.read_buf(&mut buf)?;
+                Some(buf)
+            } else {
+                None
+            };
+        let vinfo = NACodecInfo::new(cname, NACodecTypeInfo::Video(vhdr), edata);
+        let res = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, tb_num, tb_den, u64::from(self.nframes)));
+        if res.is_none() { return Err(DemuxerError::MemoryError); }
+
+        if (flags & 1) != 0 {
+            let ahdr_size               = self.src.read_u32le()? as usize;
+            validate!(ahdr_size >= 16);
+            let w_format_tag            = self.src.read_u16le()?;
+            let channels                = self.src.read_u16le()?;
+            let samplespersec           = self.src.read_u32le()?;
+            let _avgbytespersec         = self.src.read_u32le()?;
+            let block_align             = self.src.read_u16le()?;
+            let bits_per_sample         = self.src.read_u16le()?;
+
+            let signed = bits_per_sample > 8;
+            let soniton = NASoniton::new(bits_per_sample as u8, if signed { SONITON_FLAG_SIGNED } else { 0 });
+            let ahdr = NAAudioInfo::new(samplespersec, channels as u8, soniton, block_align as usize);
+            let edata = if ahdr_size > 16 {
+                    let edata_size      = self.src.read_u16le()? as usize;
+                    validate!(edata_size + 18 == ahdr_size);
+                    if edata_size > 0 {
+                        let mut buf = vec![0; edata_size];
+                                          self.src.read_buf(&mut buf)?;
+                        Some(buf)
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                };
+
+            let cname = match w_format_tag {
+                    0x401 => "iac",
+                    0x402 => "imc",
+                    _ =>     "unknown",
+                };
+
+            let ainfo = NACodecInfo::new(cname, NACodecTypeInfo::Audio(ahdr), edata);
+            let res = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, atb_num, atb_den, u64::from(aduration)));
+            if res.is_none() { return Err(DemuxerError::MemoryError); }
+        }
+
+        // video frame table
+        self.vsizes.reserve(self.nframes as usize);
+        for _ in 0..self.nframes {
+            let size                    = self.src.read_u32le()?;
+            self.vsizes.push(size);
+        }
+
+        if version == 1 {
+                                          self.src.read_skip(128)?;
+        }
+
+        let comment_len                 = self.src.read_u32le()? as usize;
+                                          self.src.read_skip(comment_len)?;
+
+        self.vframe = 0;
+        self.aframe = 0;
+
+        self.vframes = Vec::with_capacity(self.nframes as usize);
+        self.aframes = Vec::with_capacity(aframes);
+        for _ in 0..self.nframes {
+            self.vframes.push(Vec::new());
+        }
+        for _ in 0..aframes {
+            self.aframes.push(Vec::new());
+        }
+
+        let mut last_ts = 1 << 31;
+        let mut pass = 0;
+        while self.src.tell() < self.size {
+            let flg                     = self.src.read_u32le()?;
+            let fsize                   = self.src.read_u32le()? as usize;
+
+            let tstamp = (flg >> 1) as usize;
+
+            if (flg & 1) != 0 {
+                if last_ts > tstamp {
+                    pass += 1;
+                    if self.passes != 0 && pass > self.passes {
+                        break;
+                    }
+                }
+                last_ts = tstamp;
+            }
+
+            let dst = if (flg & 1) != 0 { &mut self.vframes[tstamp] } else { &mut self.aframes[tstamp] };
+            let cur_size = dst.len();
+            dst.resize(cur_size + fsize, 0);
+                                          self.src.read_buf(&mut dst[cur_size..])?;
+        }
+
+        // remove provisionary code for drop frames if real data is present
+        for frm in self.vframes.iter_mut() {
+            if frm.len() > 2 && frm[0] == 0x9F && frm[1] == 0x00 {
+                frm.remove(0);
+                frm.remove(0);
+            }
+        }
+
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        let has_next = if self.do_v { self.vframe < self.nframes } else { self.aframe < self.nframes };
+        if has_next {
+            let (stream_id, tstamp, buf) = if self.do_v {
+                    self.vframe += 1;
+                    (0, self.vframe - 1, self.vframes[self.vframe as usize - 1].clone())
+                } else {
+                    self.aframe += 1;
+                    (1, self.aframe - 1, self.aframes[self.aframe as usize - 1].clone())
+                };
+            if !self.do_v || (self.aframe as usize) < self.aframes.len() {
+                self.do_v = !self.do_v;
+            }
+
+            if let Some(stream) = strmgr.get_stream(stream_id) {
+                let (tb_num, tb_den) = stream.get_timebase();
+                let ts = NATimeInfo::new(Some(tstamp as u64), None, None, tb_num, tb_den);
+                return Ok(NAPacket::new_from_refbuf(stream, ts, false, NABufferRef::new(buf)));
+            } else {
+                return Err(DemuxerError::InvalidData);
+            }
+        }
+        Err(DemuxerError::EOF)
+    }
+
+    fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
+        Err(DemuxerError::NotPossible)
+    }
+    fn get_duration(&self) -> u64 { 0 }
+}
+
+const PASSES: &str = "passes";
+
+const DEMUXER_OPTS: &[NAOptionDefinition] = &[
+    NAOptionDefinition {
+        name: PASSES, description: "Number of passes to assemble data (0 = all)",
+        opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) },
+];
+
+impl<'a> NAOptionHandler for IVFDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { DEMUXER_OPTS }
+    fn set_options(&mut self, options: &[NAOption]) {
+       for option in options.iter() {
+            for opt_def in DEMUXER_OPTS.iter() {
+                if opt_def.check(option).is_ok() {
+                    match option.name {
+                        PASSES => {
+                            if let NAValue::Int(intval) = option.value {
+                                self.passes = intval as u8;
+                            }
+                        },
+                        _ => {},
+                    }
+                }
+            }
+        }
+    }
+    fn query_option_value(&self, name: &str) -> Option<NAValue> {
+        match name {
+            PASSES => Some(NAValue::Int(i64::from(self.passes))),
+            _ => None,
+        }
+    }
+}
+
+pub struct IVFDemuxerCreator { }
+
+impl DemuxerCreator for IVFDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(IVFDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "ivf" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_ivf_demux() {
+        // sample is a trailer for Heart of Darkness game
+        let mut file = File::open("assets/Indeo/TRAILERIIE.IVF").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = IVFDemuxer::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 == DemuxerError::EOF { break; }
+                panic!("error");
+            }
+            let pkt = pktres.unwrap();
+            println!("Got {}", pkt);
+        }
+    }
+}
diff --git a/nihav-indeo/src/demuxers/mod.rs b/nihav-indeo/src/demuxers/mod.rs
new file mode 100644 (file)
index 0000000..db8046a
--- /dev/null
@@ -0,0 +1,22 @@
+use nihav_core::demuxers::*;
+
+
+#[allow(unused_macros)]
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(DemuxerError::InvalidData); } };
+}
+
+#[cfg(feature="demuxer_ivf")]
+mod ivf;
+
+const DEMUXERS: &[&dyn DemuxerCreator] = &[
+#[cfg(feature="demuxer_ivf")]
+    &ivf::IVFDemuxerCreator {},
+];
+
+/// Registers all available demuxers provided by this crate.
+pub fn indeo_register_all_demuxers(rd: &mut RegisteredDemuxers) {
+    for demuxer in DEMUXERS.iter() {
+        rd.add_demuxer(*demuxer);
+    }
+}
index 6c7056f392ac13f680c63ef8614f0840f818b222..c709b237153ec24a5533b9582d5d02ce2b44d18c 100644 (file)
@@ -12,5 +12,9 @@ mod codecs;
 
 pub use crate::codecs::indeo_register_all_decoders;
 
+mod demuxers;
+
+pub use crate::demuxers::indeo_register_all_demuxers;
+
 #[cfg(test)]
 extern crate nihav_commonfmt;
\ No newline at end of file
index 67fba7023b57cc543d71d2ae2d9971ab5748ec97..3e1d26cfb842b6698bc7fb8905c9f347c7f6244c 100644 (file)
@@ -241,6 +241,12 @@ const DETECTORS: &[DetectConditions] = &[
         conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"FLV") },
                       CheckItem{offs: 3, cond: &CC::Le(Arg::Byte(1)) }],
     },
+    DetectConditions {
+        demux_name: "ivf",
+        extensions: ".ivf",
+        conditions: &[CheckItem{offs: 0, cond: &CC::Str(&[0x50, 0xEF, 0x81, 0x19, 0xB3, 0xBD, 0xD0, 0x11, 0xA3, 0xE5, 0x00, 0xA0, 0xC9, 0x24, 0x44])},
+                      CheckItem{offs: 15, cond: &CC::Or(&CC::Eq(Arg::Byte(0x36)), &CC::Eq(Arg::Byte(0x37)))}],
+    },
     DetectConditions {
         demux_name: "dkivf",
         extensions: ".ivf",