From: Kostya Shishkov Date: Wed, 4 Mar 2026 17:18:20 +0000 (+0100) Subject: Adorage decoder X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=2cf7006d768669497d08b867c7e0d2735de97797;p=nihav.git Adorage decoder --- diff --git a/nihav-misc/Cargo.toml b/nihav-misc/Cargo.toml index 36c8b29..8347d9f 100644 --- a/nihav-misc/Cargo.toml +++ b/nihav-misc/Cargo.toml @@ -24,7 +24,8 @@ demuxers = [] all_decoders = ["all_video_decoders", "all_audio_decoders"] -all_video_decoders = ["decoder_arbc", "decoder_fif", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] +all_video_decoders = ["decoder_adorage", "decoder_arbc", "decoder_fif", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] +decoder_adorage = ["decoders"] decoder_arbc = ["decoders"] decoder_fif = ["decoders"] decoder_moviepak = ["decoders"] diff --git a/nihav-misc/src/codecs/adorage.rs b/nihav-misc/src/codecs/adorage.rs new file mode 100644 index 0000000..96af78d --- /dev/null +++ b/nihav-misc/src/codecs/adorage.rs @@ -0,0 +1,441 @@ +use nihav_core::io::byteio::{ByteIO,MemoryReader}; +use nihav_core::codecs::*; +use nihav_codec_support::codecs::jpeg::*; + +struct DecoderWrapper { + info: NACodecInfoRef, + jpeg: JPEGDecoder, + buf: Vec, +} + +impl DecoderWrapper { + fn new() -> Self { + Self { + info: NACodecInfo::new_dummy(), + jpeg: JPEGDecoder::new(), + buf: Vec::new(), + } + } + fn reset(&mut self) { + self.jpeg.quant = [[0; 64]; 4]; + self.jpeg.cb = JCodebooks::default(); + self.jpeg.width = 0; + self.jpeg.height = 0; + self.jpeg.depth = 0; + } + + fn parse_sof(&mut self, br: &mut dyn ByteIO) -> DecoderResult { + validate!(self.jpeg.width == 0); + + let len = br.read_u16be()? as usize; + validate!(len >= 11); + let p = br.read_byte()?; + validate!(p > 2); + if p != 8 { + return Err(DecoderError::NotImplemented); + } + let y = br.read_u16be()? as usize; + let x = br.read_u16be()? as usize; + validate!(x > 0); + if y == 0 { + return Err(DecoderError::NotImplemented); + } + self.jpeg.depth = p; + self.jpeg.width = x; + self.jpeg.height = y; + let nf = br.read_byte()? as usize; + validate!(nf > 0); + validate!(len == 8 + nf * 3); + if nf > MAX_CHROMATONS { + return Err(DecoderError::NotImplemented); + } + self.jpeg.max_h = 0; + self.jpeg.max_v = 0; + for i in 0..nf { + let c = br.read_byte()?; + self.jpeg.comp_id[i] = c; + let hv = br.read_byte()?; + let t = br.read_byte()?; + validate!(t < 4); + self.jpeg.qselect[i] = t; + self.jpeg.subsamp[i] = hv; + let hs = hv >> 4; + + validate!(hs == 1 || hs == 2 || (i == 0 && hs == 4)); + let vs = hv & 0xF; + validate!(vs == 1 || vs == 2 || (i == 0 && vs == 4)); + self.jpeg.max_h = self.jpeg.max_h.max(hs); + self.jpeg.max_v = self.jpeg.max_v.max(vs); + } + let mut chromatons = [None; MAX_CHROMATONS]; + for (i, chr) in chromatons[..nf].iter_mut().enumerate() { + let h_ss = match self.jpeg.max_h / (self.jpeg.subsamp[i] >> 4) { + 1 => 0, + 2 => 1, + 4 => 2, + _ => unreachable!(), + }; + let v_ss = match self.jpeg.max_v / (self.jpeg.subsamp[i] & 0xF) { + 1 => 0, + 2 => 1, + 4 => 2, + _ => return Err(DecoderError::InvalidData), + }; + + *chr = Some(NAPixelChromaton { + h_ss, v_ss, + packed: false, + depth: p, + shift: 0, + comp_offs: i as u8, + next_elem: (p + 7) >> 3, + }); + } + for i in 0..nf { + for j in i + 1..nf { + validate!(self.jpeg.comp_id[i] != self.jpeg.comp_id[j]); + } + } + let formaton = NAPixelFormaton { + model: ColorModel::YUV(YUVSubmodel::YUVJ), + components: nf as u8, + comp_info: chromatons, + elem_size: 0, + be: false, + alpha: nf == 2 || nf == 4, + palette: false, + }; + let vinfo = NAVideoInfo::new(x, y, true, formaton); + Ok(alloc_video_buffer(vinfo, 4)?) + } +} + +impl NADecoder for DecoderWrapper { + 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(); + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, h, true, YUV420_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(); + if src.len() <= 24 { return Err(DecoderError::ShortData); } + + let mut bufinfo = NABufferType::None; + let mut br = MemoryReader::new_read(&src); + let twelve = br.read_u32le()?; + validate!(twelve == 12); + let tag = br.read_tag()?; + if &tag[..2] != b"JP" { + return Err(DecoderError::NotImplemented); + } + br.read_u32le()?; + let jpg_size = br.read_u32le()?; + validate!(jpg_size as usize + 20 <= src.len()); + let mask_size = br.read_u32le()?; // PNG or JPG mask + validate!(mask_size as usize <= src.len() - 20 - (jpg_size as usize)); + + let start_tag = br.read_u16be()?; + validate!(start_tag == 0xFFD8); + + let mut jtype = JPEGType::None; + let mut arith = false; + self.reset(); + loop { + let tag = br.read_u16be()?; + match tag { + 0xFFC0 => { //baseline DCT header + jtype = JPEGType::Baseline; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC1 => { + jtype = JPEGType::Extended; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC2 => { + jtype = JPEGType::Progressive; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC3 => { + jtype = JPEGType::Lossless; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC5 => { + jtype = JPEGType::Differential; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC6 => { + jtype = JPEGType::DiffProgressive; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC7 => { + jtype = JPEGType::DiffLossless; + arith = false; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC8 => return Err(DecoderError::NotImplemented), + 0xFFC9 => { + jtype = JPEGType::Extended; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFCA => { + jtype = JPEGType::Progressive; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFCB => { + jtype = JPEGType::Lossless; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFCD => { + jtype = JPEGType::Differential; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFCE => { + jtype = JPEGType::DiffProgressive; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFCF => { + jtype = JPEGType::DiffLossless; + arith = true; + bufinfo = self.parse_sof(&mut br)?; + }, + 0xFFC4 => { //huff table + validate!(!arith); + let len = u64::from(br.read_u16be()?); + validate!(len > 2); + let end = br.tell() + len - 2; + let mut lens = [0; 16]; + let mut syms = [0; 256]; + while br.tell() < end { + let tctn = br.read_byte()? as usize; + let tclass = tctn >> 4; + validate!(tclass < 2); + let id = tctn & 0xF; + validate!(id < 4); + br.read_buf(&mut lens)?; + let mut tot_len = 0usize; + for &el in lens.iter() { + tot_len += usize::from(el); + } + validate!(tot_len > 0 && tot_len <= 256); + br.read_buf(&mut syms[..tot_len])?; + self.jpeg.cb.codebook[tclass][id] = Some(JCodebooks::generate_cb(&lens, &syms[..tot_len])?); + } + validate!(br.tell() == end); + }, + 0xFFCC => { // arith coding conditioning + return Err(DecoderError::NotImplemented); + } + 0xFFD0..=0xFFD7 => return Err(DecoderError::NotImplemented), + 0xFFD9 => break, + 0xFFDA => { //start of scan + let len = br.read_u16be()? as usize; + let ns = br.read_byte()? as usize; + validate!(len == ns * 2 + 6); + let mut ci = [ComponentInfo::default(); MAX_CHROMATONS]; + for info in ci[..ns].iter_mut() { + let id = br.read_byte()?; + let mut found = false; + for (i, &c_id) in self.jpeg.comp_id.iter().enumerate() { + if c_id == id { + info.component_id = i; + found = true; + break; + } + } + validate!(found); + let tdta = br.read_byte()? as usize; + let dc_id = tdta >> 4; + validate!(dc_id < 4); + if self.jpeg.cb.codebook[0][dc_id].is_none() { + validate!(dc_id < 2); + self.jpeg.cb.codebook[0][dc_id] = Some(JCodebooks::build_default_cb(true, dc_id)?); + } + let ac_id = tdta & 0xF; + validate!(ac_id < 4); + if self.jpeg.cb.codebook[1][ac_id].is_none() { + validate!(ac_id < 2); + self.jpeg.cb.codebook[1][ac_id] = Some(JCodebooks::build_default_cb(false, ac_id)?); + } + info.dc_table_id = dc_id; + info.ac_table_id = ac_id; + } + let ss = br.read_byte()? as usize; + let se = br.read_byte()? as usize; + let ahal = br.read_byte()?; + let ah = ahal >> 4; + let al = ahal & 0xF; + match jtype { + JPEGType::Baseline | JPEGType::Extended => { + if arith { + println!("arithmetic coding!"); + return Err(DecoderError::NotImplemented); + } + validate!(ss == 0 && se == 63); + validate!(ah == 0 && al == 0); + if let Some(buf) = bufinfo.get_vbuf() { + let max_size = src.len() - (br.tell() as usize); + self.buf.clear(); + self.buf.reserve(max_size); + loop { + let b = br.read_byte()?; + if b != 0xFF { + self.buf.push(b); + } else { + let b2 = br.read_byte()?; + if b2 == 0 { + self.buf.push(b); + } else { + br.seek(std::io::SeekFrom::Current(-2))?; + break; + } + } + } + + let mut data = Vec::new(); + std::mem::swap(&mut self.buf, &mut data); + let ret = self.jpeg.decode_scan(jtype, &data, buf, &ci[..ns], ss, se, None); + std::mem::swap(&mut self.buf, &mut data); + ret?; + } else { unreachable!(); } + }, + JPEGType::Progressive => { + validate!(ss < 64 && se < 64 && se >= ss); + validate!(ah < 14 && al < 14); + println!("Progressive JPEG"); + return Err(DecoderError::NotImplemented); + }, + JPEGType::Lossless => { + validate!((1..8).contains(&ss) && se == 0); + validate!(ah == 0); + println!("LJPEG"); + return Err(DecoderError::NotImplemented); + }, + _ => return Err(DecoderError::NotImplemented), + }; + let tag = br.peek_u16be()?; + validate!(matches!(tag, 0xFFC4 | 0xFFD0..=0xFFD7 | 0xFFD9)); + }, + 0xFFDB => { //quant tables + let mut len = br.read_u16be()? as usize; + validate!(len >= 64 + 3); + len -= 2; + while len > 0 { + let pt = br.read_byte()?; + let precision = pt >> 4; + validate!(precision < 2); + let id = (pt & 0xF) as usize; + validate!(id < 4); + let qsize = if precision == 0 { 64 } else { 64 * 2 } + 1; + validate!(len >= qsize); + if precision == 0 { + for el in self.jpeg.quant[id].iter_mut() { + *el = i16::from(br.read_byte()?); + } + } else { + for el in self.jpeg.quant[id].iter_mut() { + *el = br.read_u16be()? as i16; + } + } + len -= qsize; + } + }, + 0xFFDC => { //number of lines + return Err(DecoderError::NotImplemented); + }, + 0xFFDD => { + let len = br.read_u16be()?; + validate!(len == 4); + let ri = br.read_u16be()?; + if ri != 0 { + println!("restart interval {}", ri); + return Err(DecoderError::NotImplemented); + } + }, + 0xFFDE => return Err(DecoderError::NotImplemented), + 0xFFDF => return Err(DecoderError::NotImplemented), + 0xFFE0..=0xFFEF => { // application data + let len = br.read_u16be()? as usize; + validate!(len >= 2); + br.read_skip(len - 2)?; + }, + 0xFFF0..=0xFFF6 => return Err(DecoderError::NotImplemented), + 0xFFF7 => { + //jtype = JPEGType::JPEGLS; + //arith = false; + println!("JPEG-LS"); + return Err(DecoderError::NotImplemented); + }, + 0xFFF8 => return Err(DecoderError::NotImplemented), //JPEG-LS parameters + 0xFFF9..=0xFFFD => return Err(DecoderError::NotImplemented), + 0xFFFE => { //comment + let len = br.read_u16be()? as usize; + validate!(len >= 2); + br.read_skip(len - 2)?; + }, + 0xFF01 => return Err(DecoderError::NotImplemented), + 0xFF02..=0xFFBF => return Err(DecoderError::NotImplemented), + _ => return Err(DecoderError::InvalidData), + }; + } + validate!(jtype != JPEGType::None); + + if let NABufferType::None = bufinfo { +println!("no buffer"); + return Err(DecoderError::InvalidData); + } + + 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 DecoderWrapper { + 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(DecoderWrapper::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_adorage() { + 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 Adorage 2.0 effects + test_decoding("avi", "adorage", "assets/Misc/ballon6.avi", Some(0), &dmx_reg, + &dec_reg, ExpectedTestResult::MD5Frames(vec![ + [0x242e775c, 0x1ea0c40a, 0x5a6c4734, 0xd8fd852b]])); + } +} diff --git a/nihav-misc/src/codecs/mod.rs b/nihav-misc/src/codecs/mod.rs index efd399e..3144bf9 100644 --- a/nihav-misc/src/codecs/mod.rs +++ b/nihav-misc/src/codecs/mod.rs @@ -9,6 +9,9 @@ macro_rules! validate { ($a:expr) => { if !$a { return Err(DecoderError::InvalidData); } }; } +#[cfg(feature="decoder_adorage")] +mod adorage; + #[cfg(feature="decoder_arbc")] mod arbc; @@ -40,6 +43,8 @@ mod tealvid; mod ultimotion; const DECODERS: &[DecoderInfo] = &[ +#[cfg(feature="decoder_adorage")] + DecoderInfo { name: "adorage", get_decoder: adorage::get_decoder }, #[cfg(feature="decoder_arbc")] DecoderInfo { name: "gryphon-arbc-vfw", get_decoder: arbc::get_decoder_vfw }, #[cfg(feature="decoder_arbc")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 57b9a33..917ad10 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -336,6 +336,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video-llp; "pivideo", "PI-Video"), + desc!(video-im; "adorage", "proDAD Adorage"), desc!(video-im; "moviepak", "RasterOps MoviePak"), desc!(video-im; "pgvv", "Radius Studio Video"), @@ -404,6 +405,8 @@ static AVI_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"FVF1", "fif"), + (b"pDAD", "adorage"), + (b"pivc", "pivideo"), (b"ULTI", "ultimotion"),