]> git.nihav.org Git - nihav.git/commitdiff
add QPEG-DVC demuxer and decoder
authorKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 27 Mar 2025 17:15:28 +0000 (18:15 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Thu, 27 Mar 2025 17:15:28 +0000 (18:15 +0100)
nihav-allstuff/src/lib.rs
nihav-misc/Cargo.toml
nihav-misc/src/codecs/mod.rs
nihav-misc/src/codecs/qpeg.rs [new file with mode: 0644]
nihav-misc/src/demuxers/mod.rs [new file with mode: 0644]
nihav-misc/src/demuxers/qpeg.rs [new file with mode: 0644]
nihav-misc/src/lib.rs
nihav-registry/src/detect.rs
nihav-registry/src/register.rs

index 2c3db75b747124494ca6a51e7615f90b97b03a52..89a608fde07fce9ddafcf8c50b95c79b30ca5ec0 100644 (file)
@@ -62,6 +62,7 @@ pub fn nihav_register_all_demuxers(rd: &mut RegisteredDemuxers) {
     game_register_all_demuxers(rd);
     indeo_register_all_demuxers(rd);
     llaudio_register_all_demuxers(rd);
+    misc_register_all_demuxers(rd);
     rad_register_all_demuxers(rd);
     realmedia_register_all_demuxers(rd);
     vivo_register_all_demuxers(rd);
index 9ebabe94944383e871c8b09bdaf77beee1cd3914..088613a5a3aef2a536ff32b1ab3897cde62f0993 100644 (file)
@@ -14,13 +14,18 @@ path = "../nihav-codec-support"
 nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers"] }
 
 [features]
-default = ["all_decoders"]
+default = ["all_decoders", "all_demuxers"]
 decoders = []
+demuxers = []
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 
-all_video_decoders = ["decoder_mwv1", "decoder_pgvv"]
+all_video_decoders = ["decoder_mwv1", "decoder_pgvv", "decoder_qpeg"]
 decoder_mwv1 = ["decoders"]
 decoder_pgvv = ["decoders"]
+decoder_qpeg = ["decoders"]
 
 all_audio_decoders = []
+
+all_demuxers = ["demuxer_qpeg"]
+demuxer_qpeg = ["demuxers"]
\ No newline at end of file
index c0693cc4aec7c067bb77dbf040ac49434f45875a..c0a4067b3b51d8c225ed17a154e35e985b0a57d5 100644 (file)
@@ -15,11 +15,16 @@ mod mwv1;
 #[cfg(feature="decoder_pgvv")]
 mod pgvv;
 
+#[cfg(feature="decoder_qpeg")]
+mod qpeg;
+
 const DECODERS: &[DecoderInfo] = &[
 #[cfg(feature="decoder_mwv1")]
     DecoderInfo { name: "mwv1", get_decoder: mwv1::get_decoder },
 #[cfg(feature="decoder_pgvv")]
     DecoderInfo { name: "pgvv", get_decoder: pgvv::get_decoder },
+#[cfg(feature="decoder_qpeg")]
+    DecoderInfo { name: "qpeg-dvc", get_decoder: qpeg::get_decoder_dvc },
 ];
 
 /// Registers all available codecs provided by this crate.
diff --git a/nihav-misc/src/codecs/qpeg.rs b/nihav-misc/src/codecs/qpeg.rs
new file mode 100644 (file)
index 0000000..e9702f1
--- /dev/null
@@ -0,0 +1,159 @@
+use nihav_core::io::byteio::{ByteReader,MemoryReader};
+use nihav_core::codecs::*;
+
+struct DVCDecoder {
+    info:       NACodecInfoRef,
+    pal:        [u8; 768],
+    frame:      Vec<u8>,
+    width:      usize,
+    height:     usize,
+}
+
+impl DVCDecoder {
+    fn new() -> Self {
+        Self {
+            info:       NACodecInfo::new_dummy(),
+            pal:        [0; 768],
+            frame:      Vec::new(),
+            width:      0,
+            height:     0,
+        }
+    }
+}
+
+impl NADecoder for DVCDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            let w = vinfo.get_width();
+            let h = vinfo.get_height();
+            self.width = w;
+            self.height = h;
+            self.frame = vec![0; self.width * self.height];
+            if let Some(pal) = info.get_extradata() {
+                validate!(pal.len() == 768);
+                self.pal.copy_from_slice(&pal);
+            }
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, 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();
+        let mut mr = MemoryReader::new_read(&src);
+        let mut br = ByteReader::new(&mut mr);
+
+        let mut pos = 0;
+        let mut is_intra = true;
+        br.read_skip(128)?;
+        let remap = &src[..128];
+        loop {
+            let op = br.read_byte()?;
+            match op {
+                0x00 => {
+                    validate!(pos < self.frame.len());
+                    pos += 1;
+                    is_intra = false;
+                },
+                0x01..=0x7F => {
+                    validate!(pos < self.frame.len());
+                    self.frame[pos] = remap[usize::from(op)];
+                    pos += 1;
+                },
+                0x80..=0xBF => {
+                    let skip = usize::from(op & 0x3F);
+                    let skip = match skip {
+                            0x00 => usize::from(br.read_byte()?) + 0x40,
+                            0x01 => usize::from(br.read_byte()?) + 0x140,
+                            _ => skip,
+                        };
+                    validate!(pos + skip <= self.frame.len());
+                    pos += skip;
+                    is_intra = false;
+                },
+                0xC0..=0xDF => {
+                    let len = usize::from(op & 0x1F) + 1;
+                    validate!(pos + len <= self.frame.len());
+                    br.read_buf(&mut self.frame[pos..][..len])?;
+                    pos += len;
+                },
+                0xE0 => {
+                    if pos != self.frame.len() {
+                        is_intra = false;
+                    }
+                    break;
+                },
+                0xE1..=0xFF => {
+                    let run = usize::from(op & 0x1F) + 1;
+                    validate!(pos + run <= self.frame.len());
+                    let clr = br.read_byte()?;
+                    for el in self.frame[pos..][..run].iter_mut() {
+                        *el = clr;
+                    }
+                    pos += run;
+                },
+            }
+        }
+
+        let vinfo = NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT);
+        let bufinfo = alloc_video_buffer(vinfo, 0)?;
+
+        if let Some(mut buf) = bufinfo.get_vbuf() {
+            let stride = buf.get_stride(0);
+            let pal_off = buf.get_offset(1);
+            let data = buf.get_data_mut().unwrap();
+
+            for (dline, sline) in data.chunks_mut(stride).zip(self.frame.chunks_exact(self.width)) {
+                dline[..self.width].copy_from_slice(sline);
+            }
+            data[pal_off..][..768].copy_from_slice(&self.pal);
+        } else { unreachable!(); }
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+        frm.set_keyframe(is_intra);
+        frm.set_frame_type(if is_intra { FrameType::I } else { FrameType::P });
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {
+        for el in self.frame.iter_mut() {
+            *el = 0;
+        }
+    }
+}
+
+impl NAOptionHandler for DVCDecoder {
+    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_dvc() -> Box<dyn NADecoder + Send> {
+    Box::new(DVCDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::RegisteredDecoders;
+    use nihav_core::demuxers::RegisteredDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::*;
+    use nihav_commonfmt::generic_register_all_demuxers;
+    #[test]
+    fn test_qpeg_dvc() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        misc_register_all_demuxers(&mut dmx_reg);
+        generic_register_all_demuxers(&mut dmx_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        misc_register_all_decoders(&mut dec_reg);
+
+        // sample from Inside Multimedia 1994 October
+        test_decoding("qpeg", "qpeg-dvc", "assets/Misc/club01.dvc", Some(3), &dmx_reg,
+                     &dec_reg, ExpectedTestResult::MD5Frames(vec![
+                            [0x90f20329, 0x305bf02c, 0xb14217d1, 0x9213dfe6],
+                            [0x22e11154, 0x51deb566, 0xda570987, 0xa044b123],
+                            [0x7eba24ae, 0x3b786669, 0xb49681b1, 0x447d48c9],
+                            [0xdad2b6bf, 0xa533f767, 0x6ace31cf, 0x5d8a7318]]));
+    }
+}
diff --git a/nihav-misc/src/demuxers/mod.rs b/nihav-misc/src/demuxers/mod.rs
new file mode 100644 (file)
index 0000000..8e7895f
--- /dev/null
@@ -0,0 +1,27 @@
+use nihav_core::demuxers::*;
+
+
+#[allow(unused_macros)]
+#[cfg(debug_assertions)]
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(DemuxerError::InvalidData); } };
+}
+#[cfg(not(debug_assertions))]
+macro_rules! validate {
+    ($a:expr) => { if !$a { return Err(DemuxerError::InvalidData); } };
+}
+
+#[cfg(feature="demuxer_qpeg")]
+mod qpeg;
+
+const DEMUXERS: &[&dyn DemuxerCreator] = &[
+#[cfg(feature="demuxer_qpeg")]
+    &qpeg::QPEGDemuxerCreator {},
+];
+
+/// Registers all available demuxers provided by this crate.
+pub fn misc_register_all_demuxers(rd: &mut RegisteredDemuxers) {
+    for demuxer in DEMUXERS.iter() {
+        rd.add_demuxer(*demuxer);
+    }
+}
diff --git a/nihav-misc/src/demuxers/qpeg.rs b/nihav-misc/src/demuxers/qpeg.rs
new file mode 100644 (file)
index 0000000..a80a01a
--- /dev/null
@@ -0,0 +1,103 @@
+use nihav_core::demuxers::*;
+
+struct QPEGDemuxer<'a> {
+    src:            &'a mut ByteReader<'a>,
+    frameno:        u32,
+}
+
+impl<'a> QPEGDemuxer<'a> {
+    fn new(src: &'a mut ByteReader<'a>) -> Self {
+        QPEGDemuxer {
+            src,
+            frameno:    0,
+        }
+    }
+}
+
+impl<'a> DemuxCore<'a> for QPEGDemuxer<'a> {
+    fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
+        let mut magic = [0; 6];
+                                          self.src.read_buf(&mut magic)?;
+        validate!(&magic == b"IDVCd\0");
+        let height                      = self.src.read_u16le()? as usize;
+        let width                       = self.src.read_u16le()? as usize;
+        validate!((1..=640).contains(&width) && (1..=480).contains(&height));
+        self.src.seek(SeekFrom::Start(0x60))?;
+
+        let mut pal = vec![0; 768];
+                                          self.src.read_buf(&mut pal[60..])?;
+
+        let vci = NACodecTypeInfo::Video(NAVideoInfo::new(width, height, false, PAL8_FORMAT));
+        let vinfo = NACodecInfo::new("qpeg-dvc", vci, Some(pal));
+        if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 24, 0)).is_none() {
+            return Err(DemuxerError::MemoryError);
+        }
+
+        Ok(())
+    }
+
+    fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
+        let tag                         = self.src.read_u16le()?;
+        if tag == 0 {
+            return Err(DemuxerError::EOF);
+        }
+        let fsize                       = self.src.read_u32le()? as usize;
+        validate!(fsize > 4);
+        self.frameno += 1;
+
+        if let Some(stream) = strmgr.get_stream(0) {
+            let ts = stream.make_ts(Some(u64::from(self.frameno - 1)), None, None);
+            self.src.read_packet(stream, ts, self.frameno == 1, fsize - 4)
+        } else {
+            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 QPEGDemuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct QPEGDemuxerCreator { }
+
+impl DemuxerCreator for QPEGDemuxerCreator {
+    fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> {
+        Box::new(QPEGDemuxer::new(br))
+    }
+    fn get_name(&self) -> &'static str { "qpeg" }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn test_qpeg_demux() {
+        // sample from Inside Multimedia 1994 October
+        let mut file = File::open("assets/Misc/club01.dvc").unwrap();
+        let mut fr = FileReader::new_read(&mut file);
+        let mut br = ByteReader::new(&mut fr);
+        let mut dmx = QPEGDemuxer::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);
+        }
+    }
+}
index 9c5a75473cd59b68e279770f73287fbcc113e3d3..7452fa3257ad50b347be72c1ed0f8b9fb137b0e7 100644 (file)
@@ -5,5 +5,11 @@ extern crate nihav_codec_support;
 #[cfg(feature="decoders")]
 mod codecs;
 
+#[cfg(feature="demuxers")]
+mod demuxers;
+
 #[cfg(feature="decoders")]
 pub use crate::codecs::misc_register_all_decoders;
+
+#[cfg(feature="demuxers")]
+pub use crate::demuxers::misc_register_all_demuxers;
index 4af9ac1bec5745c751dffdbac0392efcfe811f47..fcfafefc1eb89debbf31c79dd77914b7f7cb94f0 100644 (file)
@@ -309,6 +309,11 @@ const DETECTORS: &[DetectConditions] = &[
         conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"SANM")},
                       CheckItem{offs: 8, cond: &CC::Str(b"SHDR")}],
     },
+    DetectConditions {
+        demux_name: "qpeg",
+        extensions: ".dvc",
+        conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"IDVCd")}],
+    },
     DetectConditions {
         demux_name: "realaudio",
         extensions: ".ra,.ram",
index ecaed89ca24250cb008aac1e20cc9b0f067be977..07b68159fd8ba2d04b966b247c0700f7b39f2245 100644 (file)
@@ -315,6 +315,8 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video-im; "mwv1",          "Aware MotionWavelets"),
 
     desc!(video-im; "pgvv",          "Radius Studio Video"),
+
+    desc!(video-llp; "qpeg-dvc",          "QPEG video in DVC"),
 ];
 
 static AVI_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[