From bac0d858f7662f16faa8cd58042893e38acbf7f9 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Thu, 27 Mar 2025 18:15:28 +0100 Subject: [PATCH] add QPEG-DVC demuxer and decoder --- nihav-allstuff/src/lib.rs | 1 + nihav-misc/Cargo.toml | 9 +- nihav-misc/src/codecs/mod.rs | 5 + nihav-misc/src/codecs/qpeg.rs | 159 ++++++++++++++++++++++++++++++++ nihav-misc/src/demuxers/mod.rs | 27 ++++++ nihav-misc/src/demuxers/qpeg.rs | 103 +++++++++++++++++++++ nihav-misc/src/lib.rs | 6 ++ nihav-registry/src/detect.rs | 5 + nihav-registry/src/register.rs | 2 + 9 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 nihav-misc/src/codecs/qpeg.rs create mode 100644 nihav-misc/src/demuxers/mod.rs create mode 100644 nihav-misc/src/demuxers/qpeg.rs diff --git a/nihav-allstuff/src/lib.rs b/nihav-allstuff/src/lib.rs index 2c3db75..89a608f 100644 --- a/nihav-allstuff/src/lib.rs +++ b/nihav-allstuff/src/lib.rs @@ -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); diff --git a/nihav-misc/Cargo.toml b/nihav-misc/Cargo.toml index 9ebabe9..088613a 100644 --- a/nihav-misc/Cargo.toml +++ b/nihav-misc/Cargo.toml @@ -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 diff --git a/nihav-misc/src/codecs/mod.rs b/nihav-misc/src/codecs/mod.rs index c0693cc..c0a4067 100644 --- a/nihav-misc/src/codecs/mod.rs +++ b/nihav-misc/src/codecs/mod.rs @@ -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 index 0000000..e9702f1 --- /dev/null +++ b/nihav-misc/src/codecs/qpeg.rs @@ -0,0 +1,159 @@ +use nihav_core::io::byteio::{ByteReader,MemoryReader}; +use nihav_core::codecs::*; + +struct DVCDecoder { + info: NACodecInfoRef, + pal: [u8; 768], + frame: Vec, + 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 { + 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 { None } +} + +pub fn get_decoder_dvc() -> Box { + 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 index 0000000..8e7895f --- /dev/null +++ b/nihav-misc/src/demuxers/mod.rs @@ -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 index 0000000..a80a01a --- /dev/null +++ b/nihav-misc/src/demuxers/qpeg.rs @@ -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 { + 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 { None } +} + +pub struct QPEGDemuxerCreator { } + +impl DemuxerCreator for QPEGDemuxerCreator { + fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box + '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); + } + } +} diff --git a/nihav-misc/src/lib.rs b/nihav-misc/src/lib.rs index 9c5a754..7452fa3 100644 --- a/nihav-misc/src/lib.rs +++ b/nihav-misc/src/lib.rs @@ -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; diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs index 4af9ac1..fcfafef 100644 --- a/nihav-registry/src/detect.rs +++ b/nihav-registry/src/detect.rs @@ -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", diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index ecaed89..07b6815 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -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)] = &[ -- 2.39.5