From: Kostya Shishkov Date: Fri, 24 Jul 2026 12:32:24 +0000 (+0200) Subject: NeXT Micro Wavelet decoder X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=0b89c5ab49175b825943a6f5c6004cfc100461b6;p=nihav.git NeXT Micro Wavelet decoder --- diff --git a/nihav-misc/Cargo.toml b/nihav-misc/Cargo.toml index 8dd6f43..b00fd69 100644 --- a/nihav-misc/Cargo.toml +++ b/nihav-misc/Cargo.toml @@ -25,10 +25,11 @@ encoders = [] all_decoders = ["all_video_decoders", "all_audio_decoders"] -all_video_decoders = ["decoder_adorage", "decoder_arbc", "decoder_fif", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_pixar", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] +all_video_decoders = ["decoder_adorage", "decoder_arbc", "decoder_fif", "decoder_microwavelet", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_pixar", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] decoder_adorage = ["decoders"] decoder_arbc = ["decoders"] decoder_fif = ["decoders"] +decoder_microwavelet = ["decoders"] decoder_moviepak = ["decoders"] decoder_mvi = ["decoders"] decoder_mwv1 = ["decoders"] diff --git a/nihav-misc/src/codecs/microwavelet.rs b/nihav-misc/src/codecs/microwavelet.rs new file mode 100644 index 0000000..406577d --- /dev/null +++ b/nihav-misc/src/codecs/microwavelet.rs @@ -0,0 +1,360 @@ +use nihav_core::io::byteio::*; +use nihav_core::io::bitreader::*; +use nihav_core::io::codebook::*; +use nihav_core::codecs::*; + +const ESCAPE_SYM: u16 = 0xFFFF; + +#[derive(Debug,PartialEq)] +enum Symbol { + Code(i8, i8, i8, i8, i8), + Skip(u8), + SkipBack, + SkipMany, + Unknown(u16), +} + +impl From for Symbol { + fn from(sym_code: u16) -> Symbol { + match sym_code { + 0..=63374 => // 13 * 15 * 13 * 5 * 5 { + Symbol::Code((sym_code % 13) as i8 - 6, + ((sym_code / 13) % 15) as i8 - 7, + ((sym_code / 13 / 15) % 13) as i8 - 6, + ((sym_code / 13 / 15 / 13) % 5) as i8 - 2, + ((sym_code / 13 / 15 / 13 / 5) % 5) as i8 - 2), + 63375 => Symbol::SkipBack, + 63376..=63488 => Symbol::Skip((sym_code - 63374) as u8), + 0xFFFE => Symbol::SkipMany, + _ => Symbol::Unknown(sym_code) + } + } +} + +fn read_huff_desc(cbs: &mut Vec>, br: &mut MemoryReader) -> DecoderResult<()> { + let num_desc = usize::from(br.read_u16be()?); + validate!((1..=16).contains(&num_desc)); + let num_lens = usize::from(br.read_u16be()?); + validate!((1..=16).contains(&num_lens)); + br.read_u16be()?; + let num_syms = usize::from(br.read_u16be()?); + validate!(num_syms > 0); + + let mut code_def = Vec::with_capacity(num_syms); + let mut syms = vec![0; num_syms]; + let mut counts = Vec::with_capacity(num_desc); + + for _ in 0..num_desc { + let mut cnt = [0; 16]; + br.read_u16be_arr(&mut cnt[..num_lens])?; + counts.push(cnt); + } + br.read_u16be_arr(&mut syms)?; + + for &sym in syms.iter() { + code_def.push(FullCodebookDesc{ code: 0, bits: 0, sym }); + } + + for cnt in counts.iter() { + let tot_count = cnt.iter().fold(0u32, |acc, &a| acc + u32::from(a)); + validate!(tot_count == 0 || tot_count == (num_syms as u32)); + if tot_count == 0 { + continue; + } + let mut citer = code_def.iter_mut(); + for (i, &bcount) in cnt.iter().enumerate() { + for _ in 0..bcount { + citer.next().unwrap().bits = i as u8 + 1; + } + } + + let mut prefix = 0; + for bitlen in 1..=16 { + for code in code_def.iter_mut() { + if code.bits == bitlen { + code.code = prefix; + prefix += 1; + } + } + prefix <<= 1; + } + + code_def.push(FullCodebookDesc{ code: 0xFFFF, bits: 16, sym: ESCAPE_SYM }); + + let mut cbr = FullCodebookDescReader::new(&code_def); + let cb = Codebook::new(&mut cbr, CodebookMode::MSB).unwrap(); + cbs.push(cb); + + code_def.pop(); + } + + Ok(()) +} + +fn decode_huff_data(br: &mut BitReader, cb: &Codebook, dst: &mut Vec) -> DecoderResult<()> { + dst.clear(); + + br.skip(8)?; + loop { + let sym = br.read_cb(cb)?; + if sym == ESCAPE_SYM { + let sym = br.read(16)? as u16; + if sym == ESCAPE_SYM { + return Ok(()); + } + dst.push(sym); + } else { + dst.push(sym); + } + } +} + +fn decode_lz_data(br: &mut dyn ByteIO, dst: &mut Vec) -> DecoderResult<()> { + dst.clear(); + loop { + let mut flags = br.read_byte()?; + for _ in 0..8 { + let code = br.read_u16be()?; + if (flags & 0x80) != 0 { + let len = usize::from(code >> 13); + if len == 0 { + return Ok(()); + } + let offset = (1 << 13) - usize::from(code & 0x1FFF); + validate!(offset <= dst.len()); + let pos = dst.len() - offset; + for i in 0..len { + let old = dst[pos + i]; + dst.push(old); + } + } else { + dst.push(code); + } + flags <<= 1; + } + } +} + +fn reconstruct_frame(dst: &mut [u8], width: usize, height: usize, stride: usize, syms: &[u16]) -> DecoderResult<()> { + + const RBSTEPS: [i8; 13] = [-0x4E, -0x38, -0x26, -0x17, -0x0B, -0x04, 0x00, 0x04, 0x0B, 0x17, 0x26, 0x38, 0x4E]; + const GSTEPS: [i8; 15] = [-0x4D, -0x37, -0x25, -0x17, -0x0C, -0x05, -0x01, 0x00, 0x01, 0x05, 0x0C, 0x17, 0x25, 0x37, 0x4D]; + const DELTAS: [i8; 5] = [-0x1B, -0x09, 0x00, 0x09, 0x1B]; + + let mut to_skip = 0; + let mut pos = 0; + for lines in dst.chunks_exact_mut(stride * 2).take(height / 2) { + let (line0, line1) = lines.split_at_mut(stride); + let mut rpred = 0i16; + let mut gpred = 0i16; + let mut bpred = 0i16; + for x in (0..width).step_by(2) { + if to_skip > 0 { + to_skip -= 1; + continue; + } + if pos >= syms.len() { + return Ok(()); + } + let sym = Symbol::from(syms[pos]); + pos += 1; + + match sym { + Symbol::Code(rd, gd, bd, hor, ver) => { + let rdelta = i16::from(RBSTEPS[(rd + 6) as usize]); + let gdelta = i16::from(GSTEPS [(gd + 7) as usize]); + let bdelta = i16::from(RBSTEPS[(bd + 6) as usize]); + let hor = i16::from(DELTAS[(hor + 2) as usize]); + let ver = i16::from(DELTAS[(ver + 2) as usize]); + + rpred += rdelta; + gpred += gdelta; + bpred += bdelta; + + let add0 = ( hor + ver) >> 1; + let add1 = ( hor - ver) >> 1; + let add2 = (-hor + ver) >> 1; + let add3 = (-hor - ver) >> 1; + + let red = bpred; + let green = gpred; + let blue = rpred; + + line0[x * 3] = (red + add0 + 128).clamp(0, 255) as u8; + line0[x * 3 + 1] = (green + add0 + 128).clamp(0, 255) as u8; + line0[x * 3 + 2] = (blue + add0 + 128).clamp(0, 255) as u8; + line0[x * 3 + 3] = (red + add1 + 128).clamp(0, 255) as u8; + line0[x * 3 + 4] = (green + add1 + 128).clamp(0, 255) as u8; + line0[x * 3 + 5] = (blue + add1 + 128).clamp(0, 255) as u8; + line1[x * 3] = (red + add2 + 128).clamp(0, 255) as u8; + line1[x * 3 + 1] = (green + add2 + 128).clamp(0, 255) as u8; + line1[x * 3 + 2] = (blue + add2 + 128).clamp(0, 255) as u8; + line1[x * 3 + 3] = (red + add3 + 128).clamp(0, 255) as u8; + line1[x * 3 + 4] = (green + add3 + 128).clamp(0, 255) as u8; + line1[x * 3 + 5] = (blue + add3 + 128).clamp(0, 255) as u8; + }, + Symbol::Skip(skip) => { + to_skip = usize::from(skip) - 1; + }, + Symbol::SkipBack => { + println!("skip back"); + return Err(DecoderError::NotImplemented); + }, + Symbol::SkipMany => { + to_skip = 0x830 - 1; + }, + Symbol::Unknown(val) => { + println!("unknown symbol {val:04X}"); + return Err(DecoderError::InvalidData); + }, + } + } + } + Ok(()) +} + +struct MicroWaveletDecoder { + info: NACodecInfoRef, + cbs: Vec>, + method: u32, + syms: Vec, +} + +impl MicroWaveletDecoder { + fn new() -> Self { + Self { + info: NACodecInfo::new_dummy(), + cbs: Vec::new(), + method: 0, + syms: Vec::new(), + } + } +} + +impl NADecoder for MicroWaveletDecoder { + 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(); + validate!(((w | h) & 1) == 0); + if let Some(edata) = info.get_extradata() { + let mut br = MemoryReader::new_read(&edata); + let tag = br.read_tag()?; + validate!(&tag == b"NC00"); + let cfg_size = br.read_u32be()?; + validate!(cfg_size == 0x638 && br.left() >= i64::from(cfg_size)); + br.read_skip(0x61B)?; + let num_defs = usize::from(br.read_byte()?); + br.read_skip(4)?; + validate!(num_defs <= 1); + let method = br.read_u32be()?; + match method { + 0 => { + println!("raw decoding is not implemented"); + return Err(DecoderError::NotImplemented); + }, + 1 | 2 => {}, + 3 => { + println!("LZSS mode is not tested"); + }, + _ => return Err(DecoderError::InvalidData), + } + self.method = method; + let format = br.read_u32be()?; + if format != 0 { + println!(" unsupported format {format}"); + return Err(DecoderError::NotImplemented); + } + let width = br.read_u32be()? as usize; + let height = br.read_u32be()? as usize; + validate!(width == w && height == h); + br.read_u32be()?; + br.read_u32be()?; + if num_defs > 0 { + self.cbs.clear(); + read_huff_desc(&mut self.cbs, &mut br)?; + } + } else { + println!("Extradata needs to be passed (from track udta)"); + return Err(DecoderError::InvalidData); + } + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, h, false, RGB24_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(); + validate!(src.len() > 1); + let cb_idx = usize::from(src[0]); + validate!(cb_idx < self.cbs.len()); + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 4)?; + + if let Some(mut buf) = bufinfo.get_vbuf() { + let (width, height) = buf.get_dimensions(0); + let stride = buf.get_stride(0); + let data = buf.get_data_mut().unwrap(); + match self.method { + 1 => { + let mut br = MemoryReader::new_read(&src); + validate!(br.left() > 0 && (br.left() & 1) == 0); + let nsyms = (br.left() / 2) as usize; + self.syms.resize(nsyms, 0); + br.read_u16be_arr(&mut self.syms)?; + }, + 2 => { + let mut br = BitReader::new(&src[1..], BitReaderMode::BE); + decode_huff_data(&mut br, &self.cbs[cb_idx], &mut self.syms)?; + }, + 3 => { + let mut br = MemoryReader::new_read(&src); + validate!(br.left() > 3); + decode_lz_data(&mut br, &mut self.syms)?; + }, + _ => unreachable!(), + } + reconstruct_frame(data, width, height, stride, &self.syms)?; + } else { unreachable!(); } + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + frm.set_keyframe(true); + frm.set_frame_type(FrameType::I); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for MicroWaveletDecoder { + 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(MicroWaveletDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::misc_register_all_decoders; + use nihav_commonfmt::generic_register_all_demuxers; + #[test] + fn test_microwavelet() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + // sample from NeXTIME CD + test_decoding("mov", "next-uwavelet", "assets/Misc/Omni_98k.mov", Some(2), &dmx_reg, + &dec_reg, ExpectedTestResult::MD5Frames(vec![ + [0x2978b089, 0xee15118e, 0xe89b2887, 0x1d170b42], + [0xd7dcc30b, 0xa0eab984, 0xd97cf4cf, 0xadb4172b], + [0xe2ec789e, 0xf0791284, 0x8286e1f8, 0x6550d8cd]])); + } +} diff --git a/nihav-misc/src/codecs/mod.rs b/nihav-misc/src/codecs/mod.rs index 6272570..1181b93 100644 --- a/nihav-misc/src/codecs/mod.rs +++ b/nihav-misc/src/codecs/mod.rs @@ -21,6 +21,9 @@ mod fif; #[cfg(feature="decoder_moviepak")] mod moviepak; +#[cfg(feature="decoder_microwavelet")] +mod microwavelet; + #[cfg(feature="decoder_mvi")] mod motionpixels; @@ -56,6 +59,8 @@ const DECODERS: &[DecoderInfo] = &[ DecoderInfo { name: "gryphon-arbc-qt", get_decoder: arbc::get_decoder_qt }, #[cfg(feature="decoder_fif")] DecoderInfo { name: "fif", get_decoder: fif::get_decoder }, +#[cfg(feature="decoder_microwavelet")] + DecoderInfo { name: "next-uwavelet", get_decoder: microwavelet::get_decoder }, #[cfg(feature="decoder_moviepak")] DecoderInfo { name: "moviepak", get_decoder: moviepak::get_decoder }, #[cfg(feature="decoder_mvi")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index edc038a..0754ced 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -350,6 +350,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video-im; "adorage", "proDAD Adorage"), desc!(video-im; "moviepak", "RasterOps MoviePak"), + desc!(video; "next-uwavelet", "NeXT Micro Wavelet"), desc!(video-im; "pgvv", "Radius Studio Video"), desc!(video-llp; "qpeg-dvc", "QPEG video in DVC"), @@ -509,6 +510,8 @@ static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"j420", "rawvideo"), (b"yuv2", "qt-yuv2"), (b"yuv4", "qt-yuv4"), + + (b"uWav", "next-uwavelet"), ]; static MOV_AUDIO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[