aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKostya Shishkov <kostya.shishkov@gmail.com>2022-10-13 18:21:33 +0200
committerKostya Shishkov <kostya.shishkov@gmail.com>2022-10-13 18:22:10 +0200
commite01d4987aa11af424461a7c0e6f2a4d82a458fc5 (patch)
tree88ef015e08635b83f852bd9f2621b1e655ddc264
parentb8e71e0a090fb00f3b3e97279dd9f65590238f5c (diff)
downloadnihav-e01d4987aa11af424461a7c0e6f2a4d82a458fc5.tar.gz
add Indeo IVF demuxer
-rw-r--r--nihav-allstuff/src/lib.rs3
-rw-r--r--nihav-indeo/Cargo.toml7
-rw-r--r--nihav-indeo/src/demuxers/ivf.rs306
-rw-r--r--nihav-indeo/src/demuxers/mod.rs22
-rw-r--r--nihav-indeo/src/lib.rs4
-rw-r--r--nihav-registry/src/detect.rs6
6 files changed, 346 insertions, 2 deletions
diff --git a/nihav-allstuff/src/lib.rs b/nihav-allstuff/src/lib.rs
index 1ef01c4..90ee6c3 100644
--- a/nihav-allstuff/src/lib.rs
+++ b/nihav-allstuff/src/lib.rs
@@ -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);
diff --git a/nihav-indeo/Cargo.toml b/nihav-indeo/Cargo.toml
index a5591bb..4243e43 100644
--- a/nihav-indeo/Cargo.toml
+++ b/nihav-indeo/Cargo.toml
@@ -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
index 0000000..952e526
--- /dev/null
+++ b/nihav-indeo/src/demuxers/ivf.rs
@@ -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
index 0000000..db8046a
--- /dev/null
+++ b/nihav-indeo/src/demuxers/mod.rs
@@ -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);
+ }
+}
diff --git a/nihav-indeo/src/lib.rs b/nihav-indeo/src/lib.rs
index 6c7056f..c709b23 100644
--- a/nihav-indeo/src/lib.rs
+++ b/nihav-indeo/src/lib.rs
@@ -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
diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs
index 67fba70..3e1d26c 100644
--- a/nihav-registry/src/detect.rs
+++ b/nihav-registry/src/detect.rs
@@ -242,6 +242,12 @@ const DETECTORS: &[DetectConditions] = &[
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",
conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"DKIF\x00\x00")},