From afe1e5babec1591d397725fbb7d37285e5b7d70c Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sun, 30 May 2021 17:21:18 +0200 Subject: [PATCH] Legend Entertainment Q format demuxer and decoder --- nihav-game/Cargo.toml | 6 +- nihav-game/src/codecs/mod.rs | 4 + nihav-game/src/codecs/q.rs | 594 +++++++++++++++++++++++++++++++++ nihav-game/src/demuxers/mod.rs | 4 + nihav-game/src/demuxers/q.rs | 249 ++++++++++++++ nihav-registry/src/detect.rs | 6 + nihav-registry/src/register.rs | 1 + 7 files changed, 862 insertions(+), 2 deletions(-) create mode 100644 nihav-game/src/codecs/q.rs create mode 100644 nihav-game/src/demuxers/q.rs diff --git a/nihav-game/Cargo.toml b/nihav-game/Cargo.toml index c06e8f0..cfbfca8 100644 --- a/nihav-game/Cargo.toml +++ b/nihav-game/Cargo.toml @@ -18,20 +18,21 @@ nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, feature [features] default = ["all_decoders", "all_demuxers"] demuxers = [] -all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_imax", "demuxer_vmd", "demuxer_vx"] +all_demuxers = ["demuxer_bmv", "demuxer_bmv3", "demuxer_fcmp", "demuxer_fst", "demuxer_gdv", "demuxer_imax", "demuxer_q", "demuxer_vmd", "demuxer_vx"] demuxer_bmv = ["demuxers"] demuxer_bmv3 = ["demuxers"] demuxer_fcmp = ["demuxers"] demuxer_fst = ["demuxers"] demuxer_gdv = ["demuxers"] demuxer_imax = ["demuxers"] +demuxer_q = ["demuxers"] demuxer_vmd = ["demuxers"] demuxer_vx = ["demuxers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] -all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_midivid", "decoder_midivid3", "decoder_vmd", "decoder_vx"] +all_video_decoders = ["decoder_bmv", "decoder_bmv3", "decoder_fstvid", "decoder_gdvvid", "decoder_imax", "decoder_midivid", "decoder_midivid3", "decoder_q", "decoder_vmd", "decoder_vx"] decoder_bmv = ["decoders"] decoder_bmv3 = ["decoders"] decoder_fstvid = ["decoders"] @@ -39,6 +40,7 @@ decoder_gdvvid = ["decoders"] decoder_imax = ["decoders"] decoder_midivid = ["decoders"] decoder_midivid3 = ["decoders"] +decoder_q = ["decoders"] decoder_vmd = ["decoders"] decoder_vx = ["decoders"] diff --git a/nihav-game/src/codecs/mod.rs b/nihav-game/src/codecs/mod.rs index 3caa51c..799ecc8 100644 --- a/nihav-game/src/codecs/mod.rs +++ b/nihav-game/src/codecs/mod.rs @@ -20,6 +20,8 @@ pub mod lhst500f22; pub mod midivid; #[cfg(feature="decoder_midivid3")] pub mod midivid3; +#[cfg(feature="decoder_q")] +pub mod q; #[cfg(feature="decoder_vmd")] pub mod vmd; #[cfg(feature="decoder_vx")] @@ -46,6 +48,8 @@ const GAME_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "fst-video", get_decoder: futurevision::get_decoder_video }, #[cfg(feature="decoder_imax")] DecoderInfo { name: "fable-imax", get_decoder: imax::get_decoder }, +#[cfg(feature="decoder_q")] + DecoderInfo { name: "legend-q-video", get_decoder: q::get_decoder }, #[cfg(feature="decoder_vmd")] DecoderInfo { name: "vmd-audio", get_decoder: vmd::get_decoder_audio }, #[cfg(feature="decoder_vmd")] diff --git a/nihav-game/src/codecs/q.rs b/nihav-game/src/codecs/q.rs new file mode 100644 index 0000000..df1c7a6 --- /dev/null +++ b/nihav-game/src/codecs/q.rs @@ -0,0 +1,594 @@ +use nihav_core::frame::*; +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +#[derive(Clone,Copy,Debug,PartialEq)] +enum TileMode { + Start, + Fill, + ShortPattern(u8, u8), + LongPattern(u8, u8), + Run, + Reuse, + FB, + MV, + Forward(u16), + Backward(u16), + Skip, +} + +struct QVideoDecoder { + info: NACodecInfoRef, + pal: [u8; 768], + frame: Vec, + w: usize, + h: usize, + tile_w: usize, + tile_h: usize, + tile_off: Vec, + mode: u8, + patterns: [u16; 128], + version: u8, +} + +macro_rules! copy_tile { + ($self: expr, $doff: expr, $soff: expr) => { + let mut doff = $doff; + let mut soff = $soff; + for _y in 0..$self.tile_h { + for x in 0..$self.tile_w { + $self.frame[doff + x] = $self.frame[soff + x]; + } + doff += $self.w; + soff += $self.w; + } + } +} + +impl QVideoDecoder { + fn new() -> Self { + QVideoDecoder { + info: NACodecInfoRef::default(), + pal: [0; 768], + frame: Vec::new(), + w: 0, + h: 0, + tile_off: Vec::new(), + mode: 0, + tile_w: 0, + tile_h: 0, + patterns: [0; 128], + version: 0, + } + } + + fn decode_mode7_tile(dst: &mut [u8], stride: usize, br: &mut ByteReader) -> DecoderResult<()> { + let op = br.peek_byte()?; + if op < 0xF8 { + for dline in dst.chunks_mut(stride).take(4) { + br.read_buf(&mut dline[..4])?; + } + } else if op == 0xF8 || op == 0xFF { + br.read_byte()?; + } else { + br.read_byte()?; + let mut clr = [0; 8]; + let nclr = (op - 0xF6) as usize; + if nclr <= 4 { + let mut pattern = br.read_u32le()?; + br.read_buf(&mut clr[..nclr])?; + for dline in dst.chunks_mut(stride).take(4) { + for el in dline[..4].iter_mut() { + *el = clr[(pattern & 3) as usize]; + pattern >>= 2; + } + } + } else { + let mut pattern = br.read_u24le()?; + let pattern2 = br.read_u24le()?; + br.read_buf(&mut clr[..nclr])?; + for (y, dline) in dst.chunks_mut(stride).take(4).enumerate() { + for el in dline[..4].iter_mut() { + *el = clr[(pattern & 7) as usize]; + pattern >>= 3; + } + if y == 1 { + pattern = pattern2; + } + } + } + } + Ok(()) + } + + fn decode_frame_v3(&mut self, br: &mut ByteReader, _ctype: u16) -> DecoderResult<()> { + let mut titer = self.tile_off.iter().enumerate(); + let mut skip_mode = false; + while let Some((tile_no, &tile_off)) = titer.next() { + let op = br.read_byte()?; + if op < 0xF8 { + let clr0 = op; + let clr1 = br.read_byte()?; + if clr0 == clr1 { + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = clr0; + } + } + } else { + let mut pattern = br.read_u16le()?; + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = if (pattern & 0x8000) == 0 { clr0 } else { clr1 }; + pattern <<= 1; + } + } + } + } else { + match op { + 0xF8 => { + unimplemented!(); + }, + 0xF9 => { + let run = br.read_byte()? as usize; + validate!(run > 0); + + validate!(tile_no > 0); + let mut tile_off = tile_off; + for i in 0..run { + if !skip_mode { + copy_tile!(self, tile_off, self.tile_off[tile_no - 1]); + } + if i + 1 < run { + let (_tno, &toff) = titer.next().unwrap(); + tile_off = toff; + } + } + }, + 0xFA => { + let off = br.read_u16le()? as usize; + validate!(tile_no + off < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[tile_no + off]); + }, + 0xFB => { + let off = br.read_u16le()? as usize; + validate!(off <= tile_no); + copy_tile!(self, tile_off, self.tile_off[tile_no - off]); + }, + 0xFC => { + const MV_PART: [i8; 16] = [ 0, 4, 8, 12, 16, 20, 24, 28, -32, -4, -8, -12, -16, -20, -24, -28 ]; + + let idx = br.read_byte()? as usize; + let x = MV_PART[(idx & 0xF) as usize] as isize; + let y = MV_PART[(idx >> 4) as usize] as isize; + let src_off = (tile_off as isize) + x + y * (self.w as isize); + validate!(src_off >= 0); + validate!((src_off as usize) + self.tile_w + (self.tile_h - 1) * self.w <= self.w * self.h); + + copy_tile!(self, tile_off, src_off as usize); + }, + 0xFD => { + let off = (br.read_byte()? as usize) + 1; + validate!(tile_no + off < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[tile_no + off]); + }, + 0xFE => { + let off = (br.read_byte()? as usize) + 1; + validate!(off <= tile_no); + copy_tile!(self, tile_off, self.tile_off[tile_no - off]); + }, + _ => {}, + }; + } + skip_mode = op == 0xFF; + } + + Ok(()) + } + + fn decode_frame(&mut self, br: &mut ByteReader, _ctype: u16) -> DecoderResult<()> { + let mut titer = self.tile_off.iter().enumerate(); + let mut last_mode = TileMode::Start; + + while let Some((tile_no, &tile_off)) = titer.next() { + let op = br.read_byte()?; + if op < 0xF8 { + let clr0 = op; + let clr1 = br.read_byte()?; + if clr0 == clr1 { + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = clr0; + } + } + last_mode = TileMode::Fill; + } else { + let pat = br.read_byte()?; + let mut pattern = if pat < 128 { + last_mode = TileMode::ShortPattern(clr0, clr1); + self.patterns[pat as usize] + } else { + last_mode = TileMode::LongPattern(clr0, clr1); + u16::from(pat) | (u16::from(br.read_byte()?) << 8) + }; + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = if (pattern & 0x8000) == 0 { clr0 } else { clr1 }; + pattern <<= 1; + } + } + } + } else { + match op { + 0xF8 => { + unimplemented!(); + }, + 0xF9 => { + let run = br.read_byte()? as usize; + validate!(run > 0); + + validate!(tile_no > 0); + validate!(last_mode != TileMode::Start); + let mut tile_no = tile_no; + let mut tile_off = tile_off; + for i in 0..run { + let copy_off = match last_mode { + TileMode::Forward(off) => { + tile_no + (off as usize) + }, + TileMode::Backward(off) => { + validate!(tile_no >= (off as usize)); + tile_no - (off as usize) + }, + TileMode::Skip => self.tile_off.len(), + _ => tile_no - 1, + }; + if copy_off < self.tile_off.len() { + copy_tile!(self, tile_off, self.tile_off[copy_off]); + } + if i + 1 < run { + let (tno, &toff) = titer.next().unwrap(); + tile_no = tno; + tile_off = toff; + } + } + last_mode = TileMode::Run; + }, + 0xFA => { + let rtile = br.read_u16le()? as usize; + validate!(rtile < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[rtile]); + last_mode = TileMode::Reuse; + }, + 0xFB => { + match self.mode { + 6 => { + let run = br.read_byte()? as usize; + validate!(run >= 2); + let mut tile_no = tile_no; + let mut tile_off = tile_off; + for i in 0..run { + match last_mode { + TileMode::Start => return Err(DecoderError::InvalidData), + TileMode::Fill => { + let clr = br.read_byte()?; + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = clr; + } + } + }, + TileMode::ShortPattern(clr0, clr1) => { + let pat = br.read_byte()?; + let mut pattern = if pat < 128 { + self.patterns[pat as usize] + } else { + u16::from(pat) | (u16::from(br.read_byte()?) << 8) + }; + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = if (pattern & 0x8000) == 0 { clr0 } else { clr1 }; + pattern <<= 1; + } + } + }, + TileMode::LongPattern(clr0, clr1) => { + let mut pattern = br.read_u16le()?; + for dline in self.frame[tile_off..].chunks_mut(self.w).take(self.tile_h) { + for el in dline[..self.tile_w].iter_mut() { + *el = if (pattern & 0x8000) == 0 { clr0 } else { clr1 }; + pattern <<= 1; + } + } + }, + TileMode::Reuse => { + let rtile = br.read_u16le()? as usize; + validate!(rtile < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[rtile]); + }, + TileMode::MV => { + let idx = br.read_byte()? as usize; + let (x, y) = DEF_MVS[idx]; + let src_off = (tile_off as isize) + (x as isize) * 4 + (y as isize) * 4 * (self.w as isize); + validate!(src_off >= 0); + validate!((src_off as usize) + self.tile_w + (self.tile_h - 1) * self.w <= self.w * self.h); + copy_tile!(self, tile_off, src_off as usize); + }, + TileMode::Forward(_) => { + let off = (br.read_byte()? as usize) + 1; + validate!(tile_no + off < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[tile_no + off]); + }, + TileMode::Backward(_) => { + let off = (br.read_byte()? as usize) + 1; + validate!(off <= tile_no); + copy_tile!(self, tile_off, self.tile_off[tile_no - off]); + }, + _ => unimplemented!(), + }; + + if i + 1 < run { + let (tno, &toff) = titer.next().unwrap(); + tile_no = tno; + tile_off = toff; + } + } + }, + 7 => { + validate!(self.tile_w == 4 && self.tile_h == 4); + let run = br.read_byte()? as usize; + validate!(run > 0); + + let mut tile_off = tile_off; + for i in 0..run { + Self::decode_mode7_tile(&mut self.frame[tile_off..], self.w, br)?; + + if i + 1 < run { + let (_tno, &toff) = titer.next().unwrap(); + tile_off = toff; + } + } + }, + _ => { + unimplemented!(); + }, + }; + last_mode = TileMode::FB; + }, + 0xFC => { + let idx = br.read_byte()? as usize; + let (x, y) = DEF_MVS[idx]; + let src_off = (tile_off as isize) + (x as isize) * 4 + (y as isize) * 4 * (self.w as isize); + validate!(src_off >= 0); + validate!((src_off as usize) + self.tile_w + (self.tile_h - 1) * self.w <= self.w * self.h); + + copy_tile!(self, tile_off, src_off as usize); + last_mode = TileMode::MV; + }, + 0xFD => { + let off = (br.read_byte()? as usize) + 1; + validate!(tile_no + off < self.tile_off.len()); + copy_tile!(self, tile_off, self.tile_off[tile_no + off]); + last_mode = TileMode::Forward(off as u16); + }, + 0xFE => { + let off = (br.read_byte()? as usize) + 1; + validate!(off <= tile_no); + copy_tile!(self, tile_off, self.tile_off[tile_no - off]); + last_mode = TileMode::Backward(off as u16); + }, + _ => { + last_mode = TileMode::Skip; + }, + }; + } + } + + Ok(()) + } + + fn output_frame(&mut self, bufinfo: &mut NABufferType, w: usize, h: usize) { + let bufo = bufinfo.get_vbuf(); + let mut buf = bufo.unwrap(); + let paloff = buf.get_offset(1); + let stride = buf.get_stride(0); + let data = buf.get_data_mut().unwrap(); + let dst = data.as_mut_slice(); + + dst[paloff..][..768].copy_from_slice(&self.pal); + for (dline, sline) in dst.chunks_mut(stride).zip(self.frame.chunks(w)).take(h) { + dline[..w].copy_from_slice(sline); + } + } +} + +impl NADecoder for QVideoDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(_vinfo) = info.get_properties() { + let mut w; + let mut h; + if let Some(buf) = info.get_extradata() { + validate!(buf.len() >= 22); + w = read_u16le(&buf[4..])? as usize; + h = read_u16le(&buf[6..])? as usize; + self.tile_w = buf[8] as usize; + self.tile_h = buf[9] as usize; + validate!(self.tile_w > 0 && self.tile_h > 0); + if self.tile_w != 4 || self.tile_h != 4 { + return Err(DecoderError::NotImplemented); + } + self.version = buf[2]; + if self.version != 3{ + w *= self.tile_w; + h *= self.tile_h; + } else { + validate!((w % self.tile_w) == 0); + validate!((h % self.tile_h) == 0); + } + } else { + return Err(DecoderError::InvalidData); + } + 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(); + self.w = w; + self.h = h; + + self.mode = match self.version { + 4 => 6, + 5 => 7, + _ => 0, + }; + + self.frame.resize(w * h, 0); + self.pal = [0; 768]; + self.tile_off = Vec::with_capacity((w / self.tile_w) * (h / self.tile_h)); + let mut off = 0; + for _y in (0..h).step_by(self.tile_h) { + for x in (0..w).step_by(self.tile_w) { + self.tile_off.push(off + x); + } + off += w * self.tile_h; + } + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(!src.is_empty()); + + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + while br.left() >= 6 { + let ctype = br.read_u16le()?; + let csize = br.read_u32le()? as usize; + validate!(csize <= (br.left() as usize)); + match ctype { + 1 => { + validate!(csize <= 768); + br.read_buf(&mut self.pal[..csize])?; + for el in self.pal[..csize].iter_mut() { + *el = (*el << 2) | (*el >> 4); + } + }, + 2 | 3 | 4 | 9 | 11 => { + if self.version == 5 { + self.mode = if ctype == 9 || ctype == 11 { 7 } else { 6 }; + } + if self.version == 3 { + self.decode_frame_v3(&mut br, ctype)?; + } else { + self.decode_frame(&mut br, ctype)?; + } + }, + 5 => { + validate!(csize <= 256 && (csize & 1) == 0); + for el in self.patterns[..csize/2].iter_mut() { + *el = br.read_u16le()?; + } + }, + 6 | 7 => { + self.mode = ctype as u8; + }, + _ => return Err(DecoderError::InvalidData), + }; + } + + let mut bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + + self.output_frame(&mut bufinfo, self.w, self.h); + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + frm.set_frame_type(if pkt.is_keyframe() { FrameType::I } else { FrameType::P }); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for QVideoDecoder { + 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() -> Box { + Box::new(QVideoDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::game_register_all_decoders; + use crate::game_register_all_demuxers; + + #[test] + fn test_q_video3() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("legend-q", "legend-q-video", "assets/Game/dgate101.q", Some(31), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x9cc0014c, 0xf6332802, 0xfabeb715, 0xdfaa11c0])); + } + #[test] + fn test_q_video4() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("legend-q", "legend-q-video", "assets/Game/1925.Q", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xe1af971a, 0xfb509816, 0x9d60f5d6, 0xbcf48a3b])); + } + #[test] + fn test_q_video5() { + let mut dmx_reg = RegisteredDemuxers::new(); + game_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + game_register_all_decoders(&mut dec_reg); + + test_decoding("legend-q", "legend-q-video", "assets/Game/mc703.q", Some(16), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xf65ea3ce, 0x3052b2bb, 0xb10f8f69, 0x530d60f9])); + } +} + +const DEF_MVS: [(i8, i8); 256] = [ + ( 0, 8), ( 1, 8), ( 2, 8), ( 3, 8), ( 4, 8), ( 5, 8), ( 6, 8), ( 7, 8), + (-8, 8), (-1, 8), (-2, 8), (-3, 8), (-4, 8), (-5, 8), (-6, 8), (-7, 8), + ( 0, 9), ( 1, 9), ( 2, 9), ( 3, 9), ( 4, 9), ( 5, 9), ( 6, 9), ( 7, 9), + (-8, 9), (-1, 9), (-2, 9), (-3, 9), (-4, 9), (-5, 9), (-6, 9), (-7, 9), + ( 0, 2), ( 1, 2), ( 2, 2), ( 3, 2), ( 4, 2), ( 5, 2), ( 6, 2), ( 7, 2), + (-8, 2), (-1, 2), (-2, 2), (-3, 2), (-4, 2), (-5, 2), (-6, 2), (-7, 2), + ( 0, 3), ( 1, 3), ( 2, 3), ( 3, 3), ( 4, 3), ( 5, 3), ( 6, 3), ( 7, 3), + (-8, 3), (-1, 3), (-2, 3), (-3, 3), (-4, 3), (-5, 3), (-6, 3), (-7, 3), + ( 0, 4), ( 1, 4), ( 2, 4), ( 3, 4), ( 4, 4), ( 5, 4), ( 6, 4), ( 7, 4), + (-8, 4), (-1, 4), (-2, 4), (-3, 4), (-4, 4), (-5, 4), (-6, 4), (-7, 4), + ( 0, 5), ( 1, 5), ( 2, 5), ( 3, 5), ( 4, 5), ( 5, 5), ( 6, 5), ( 7, 5), + (-8, 5), (-1, 5), (-2, 5), (-3, 5), (-4, 5), (-5, 5), (-6, 5), (-7, 5), + ( 0, 6), ( 1, 6), ( 2, 6), ( 3, 6), ( 4, 6), ( 5, 6), ( 6, 6), ( 7, 6), + (-8, 6), (-1, 6), (-2, 6), (-3, 6), (-4, 6), (-5, 6), (-6, 6), (-7, 6), + ( 0, 7), ( 1, 7), ( 2, 7), ( 3, 7), ( 4, 7), ( 5, 7), ( 6, 7), ( 7, 7), + (-8, 7), (-1, 7), (-2, 7), (-3, 7), (-4, 7), (-5, 7), (-6, 7), (-7, 7), + ( 0,-8), ( 1,-8), ( 2,-8), ( 3,-8), ( 4,-8), ( 5,-8), ( 6,-8), ( 7,-8), + (-8,-8), (-1,-8), (-2,-8), (-3,-8), (-4,-8), (-5,-8), (-6,-8), (-7,-8), + ( 0,-9), ( 1,-9), ( 2,-9), ( 3,-9), ( 4,-9), ( 5,-9), ( 6,-9), ( 7,-9), + (-8,-9), (-1,-9), (-2,-9), (-3,-9), (-4,-9), (-5,-9), (-6,-9), (-7,-9), + ( 0,-2), ( 1,-2), ( 2,-2), ( 3,-2), ( 4,-2), ( 5,-2), ( 6,-2), ( 7,-2), + (-8,-2), (-1,-2), (-2,-2), (-3,-2), (-4,-2), (-5,-2), (-6,-2), (-7,-2), + ( 0,-3), ( 1,-3), ( 2,-3), ( 3,-3), ( 4,-3), ( 5,-3), ( 6,-3), ( 7,-3), + (-8,-3), (-1,-3), (-2,-3), (-3,-3), (-4,-3), (-5,-3), (-6,-3), (-7,-3), + ( 0,-4), ( 1,-4), ( 2,-4), ( 3,-4), ( 4,-4), ( 5,-4), ( 6,-4), ( 7,-4), + (-8,-4), (-1,-4), (-2,-4), (-3,-4), (-4,-4), (-5,-4), (-6,-4), (-7,-4), + ( 0,-5), ( 1,-5), ( 2,-5), ( 3,-5), ( 4,-5), ( 5,-5), ( 6,-5), ( 7,-5), + (-8,-5), (-1,-5), (-2,-5), (-3,-5), (-4,-5), (-5,-5), (-6,-5), (-7,-5), + ( 0,-6), ( 1,-6), ( 2,-6), ( 3,-6), ( 4,-6), ( 5,-6), ( 6,-6), ( 7,-6), + (-8,-6), (-1,-6), (-2,-6), (-3,-6), (-4,-6), (-5,-6), (-6,-6), (-7,-6), + ( 0,-7), ( 1,-7), ( 2,-7), ( 3,-7), ( 4,-7), ( 5,-7), ( 6,-7), ( 7,-7), + (-8,-7), (-1,-7), (-2,-7), (-3,-7), (-4,-7), (-5,-7), (-6,-7), (-7,-7) +]; diff --git a/nihav-game/src/demuxers/mod.rs b/nihav-game/src/demuxers/mod.rs index 9aec01f..2377a84 100644 --- a/nihav-game/src/demuxers/mod.rs +++ b/nihav-game/src/demuxers/mod.rs @@ -13,6 +13,8 @@ mod fst; mod gdv; #[cfg(feature="demuxer_imax")] mod imax; +#[cfg(feature="demuxer_q")] +mod q; #[cfg(feature="demuxer_vmd")] mod vmd; #[cfg(feature="demuxer_vx")] @@ -31,6 +33,8 @@ const GAME_DEMUXERS: &[&dyn DemuxerCreator] = &[ &gdv::GDVDemuxerCreator {}, #[cfg(feature="demuxer_imax")] &imax::IMAXDemuxerCreator {}, +#[cfg(feature="demuxer_q")] + &q::QDemuxerCreator {}, #[cfg(feature="demuxer_vmd")] &vmd::VMDDemuxerCreator {}, #[cfg(feature="demuxer_vx")] diff --git a/nihav-game/src/demuxers/q.rs b/nihav-game/src/demuxers/q.rs new file mode 100644 index 0000000..3f38ef7 --- /dev/null +++ b/nihav-game/src/demuxers/q.rs @@ -0,0 +1,249 @@ +use nihav_core::frame::*; +use nihav_core::demuxers::*; + +#[allow(dead_code)] +struct QDemuxer<'a> { + src: &'a mut ByteReader<'a>, + vpts: u64, + apts: u64, + bps: usize, + a_id: Option, + v_id: Option, + nframes: usize, + duration: u64, + side_data: Vec, +} + +impl<'a> DemuxCore<'a> for QDemuxer<'a> { + #[allow(unused_variables)] + fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> { + let src = &mut self.src; + + let mut hdr = [0; 22]; + src.read_buf(&mut hdr)?; + validate!(hdr[0] == 0x39); + validate!(hdr[1] == 0x68); + let version = hdr[2]; + validate!(version >= 3 && version <= 5); + let mut width = read_u16le(&hdr[4..])? as usize; + let mut height = read_u16le(&hdr[6..])? as usize; + if version > 3 { + width *= hdr[8] as usize; + height *= hdr[9] as usize; + } + validate!(width > 0 && width <= 800); + validate!(height > 0 && height <= 600); + + self.nframes = read_u16le(&hdr[10..])? as usize; + validate!(self.nframes > 0); + let fps = if hdr[16] == 0 { 5 } else { hdr[16] as u32 }; + self.duration = (self.nframes as u64) * 1000 / u64::from(fps); + let asize = if version > 3 { + src.read_u32le()? + } else { 0 }; + + let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT); + let vci = NACodecTypeInfo::Video(vhdr); + let vinfo = NACodecInfo::new("legend-q-video", vci, Some(hdr.to_vec())); + self.v_id = strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, fps, self.nframes as u64)); + if asize != 0 { + let ntype = self.src.peek_byte()?; + if ntype == 8 { + let _ = self.src.read_u16le()?; + let size = self.src.read_u32le()? as usize; + validate!(size >= 44); + let mut buf = vec![0; size]; + self.src.read_buf(&mut buf)?; + let arate = read_u32le(&buf[24..])?; + let channels = buf[22]; + let abits = buf[34] as usize; + validate!(abits == 8 || abits == 16); + self.bps = (channels as usize) * abits / 8; + let bsize = read_u16le(&buf[32..])? as usize; + let ahdr = NAAudioInfo::new(arate, channels as u8, if abits == 16 { SND_S16_FORMAT } else { SND_U8_FORMAT }, bsize); + let ainfo = NACodecInfo::new("pcm", NACodecTypeInfo::Audio(ahdr), None); + self.a_id = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, arate, 2)); + } + } + self.apts = 0; + self.vpts = 0; + self.side_data.truncate(0); + Ok(()) + } + + fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult { + loop { + let ctype = self.src.read_u16le()?; + let size = self.src.read_u32le()? as usize; + match ctype { + 0xFFFF => return Err(DemuxerError::EOF), + 0 => { + if let Some(a_id) = self.a_id { + let str = strmgr.get_stream(a_id).unwrap(); + let (tb_num, tb_den) = str.get_timebase(); + let ts = NATimeInfo::new(Some(self.apts), None, None, tb_num, tb_den); + self.apts += (size / self.bps) as u64; + return self.src.read_packet(str, ts, true, size); + } else { + return Err(DemuxerError::InvalidData); + } + }, + 1 => { + validate!(size <= 768); + let cur_len = self.side_data.len(); + self.side_data.resize(cur_len + size + 6, 0); + self.side_data[cur_len] = ctype as u8; + write_u32le(&mut self.side_data[cur_len + 2..], size as u32)?; + self.src.read_buf(&mut self.side_data[cur_len + 6..])?; + }, + 2 | 3 | 4 | 11 => { + validate!(self.v_id.is_some()); + let str = strmgr.get_stream(self.v_id.unwrap_or(0)).unwrap(); + let (tb_num, tb_den) = str.get_timebase(); + let ts = NATimeInfo::new(Some(self.vpts), None, None, tb_num, tb_den); + self.vpts += 1; + + let cur_len = self.side_data.len(); + self.side_data.resize(cur_len + size + 6, 0); + self.side_data[cur_len] = ctype as u8; + self.side_data[cur_len] = ctype as u8; + write_u32le(&mut self.side_data[cur_len + 2..], size as u32)?; + if let Err(err) = self.src.read_buf(&mut self.side_data[cur_len + 6..]) { + self.side_data.truncate(cur_len); + return Err(err.into()); + } + let mut buf = Vec::new(); + std::mem::swap(&mut buf, &mut self.side_data); + return Ok(NAPacket::new(str, ts, self.vpts == 1, buf)); + }, + 5 => { + validate!(size <= 256); + let cur_len = self.side_data.len(); + self.side_data.resize(cur_len + size + 6, 0); + self.side_data[cur_len] = ctype as u8; + write_u32le(&mut self.side_data[cur_len + 2..], size as u32)?; + self.src.read_buf(&mut self.side_data[cur_len + 6..])?; + }, + 6 | 7 => { + self.side_data.push(ctype as u8); + self.side_data.push(0); + self.side_data.push(0); + self.side_data.push(0); + self.side_data.push(0); + self.side_data.push(0); + }, + 8 => return Err(DemuxerError::InvalidData), //should be handled before main loop + 9 => { // first part of interlaced frame + let cur_len = self.side_data.len(); + self.side_data.resize(cur_len + size + 6, 0); + self.side_data[cur_len] = ctype as u8; + write_u32le(&mut self.side_data[cur_len + 2..], size as u32)?; + self.src.read_buf(&mut self.side_data[cur_len + 6..])?; + }, + _ => { + self.src.read_skip(size)?; + }, + }; + } + } + + fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { + Err(DemuxerError::NotPossible) + } + fn get_duration(&self) -> u64 { self.duration } +} +impl<'a> NAOptionHandler for QDemuxer<'a> { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} +impl<'a> QDemuxer<'a> { + fn new(io: &'a mut ByteReader<'a>) -> Self { + QDemuxer { + src: io, + vpts: 0, + apts: 0, + bps: 0, + a_id: None, + v_id: None, + nframes: 0, + duration: 0, + side_data: Vec::with_capacity(256 + 6), + } + } +} + +pub struct QDemuxerCreator { } + +impl DemuxerCreator for QDemuxerCreator { + fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box + 'a> { + Box::new(QDemuxer::new(br)) + } + fn get_name(&self) -> &'static str { "legend-q" } +} + +#[cfg(test)] +mod test { + use super::*; + use std::fs::File; + + #[test] + fn test_q_demux_v3() { + let mut file = File::open("assets/Game/dgate101.q").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = QDemuxer::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 as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } + + #[test] + fn test_q_demux_v4() { + let mut file = File::open("assets/Game/1425A5.Q").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = QDemuxer::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 as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } + + #[test] + fn test_q_demux_v5() { + let mut file = File::open("assets/Game/mc703.q").unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = QDemuxer::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 as i32) == (DemuxerError::EOF as i32) { break; } + panic!("error"); + } + let pkt = pktres.unwrap(); + println!("Got {}", pkt); + } + } +} diff --git a/nihav-registry/src/detect.rs b/nihav-registry/src/detect.rs index 2d2c3ac..b883f02 100644 --- a/nihav-registry/src/detect.rs +++ b/nihav-registry/src/detect.rs @@ -251,6 +251,12 @@ const DETECTORS: &[DetectConditions] = &[ conditions: &[CheckItem{offs: 0, cond: &CC::Str(b"IMAX") }, CheckItem{offs: 10, cond: &CC::Eq(Arg::U16LE(0x102)) }], }, + DetectConditions { + demux_name: "legend-q", + extensions: ".q", + conditions: &[CheckItem{offs: 0, cond: &CC::Eq(Arg::U16LE(0x6839))}, + CheckItem{offs: 2, cond: &CC::In(Arg::Byte(3), Arg::Byte(5))}], + }, DetectConditions { demux_name: "realaudio", extensions: ".ra,.ram", diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 5e06224..48d4541 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -232,6 +232,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "fable-imax", "Fable IMAX video"), desc!(video; "fst-video", "FutureVision video"), desc!(audio; "fst-audio", "FutureVision audio"), + desc!(video; "legend-q-video", "Legend Entertainment Q video"), desc!(video; "midivid", "MidiVid"), desc!(video; "midivid3", "MidiVid 3"), desc!(video-ll; "midivid-ll", "MidiVid Lossless"), -- 2.30.2