From dd3e6fb6586abc83bc174b39822e6a13ac4e8ea2 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Thu, 8 May 2025 18:24:54 +0200 Subject: [PATCH] Escape 102 codec support --- nihav-acorn/src/codecs/escape.rs | 197 +++++++++++++++++++++++++++++++ nihav-acorn/src/codecs/mod.rs | 4 + 2 files changed, 201 insertions(+) diff --git a/nihav-acorn/src/codecs/escape.rs b/nihav-acorn/src/codecs/escape.rs index e910c9c..16a07ce 100644 --- a/nihav-acorn/src/codecs/escape.rs +++ b/nihav-acorn/src/codecs/escape.rs @@ -3,6 +3,8 @@ use nihav_core::io::byteio::*; use nihav_core::io::bitreader::*; use nihav_codec_support::codecs::imaadpcm::*; use std::str::FromStr; +use super::RGB555_FORMAT; +use super::yuvtab::YUV2RGB; const BGR555_FORMAT: NAPixelFormaton = NAPixelFormaton { model: ColorModel::RGB(RGBSubmodel::RGB), components: 3, comp_info: [ @@ -37,6 +39,143 @@ impl<'a> ReadECode for BitReader<'a> { } } +struct Escape102Decoder { + info: NACodecInfoRef, + frame: Vec, + width: usize, +} + +impl Escape102Decoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + frame: Vec::new(), + width: 0, + } + } + fn read_skip(br: &mut BitReader) -> DecoderResult { + if br.read_bool()? { + let val3 = br.read(3)? as usize; + if val3 == 7 { + let val7 = br.read(7)? as usize; + if val7 == 127 { + let val15 = br.read(15)? as usize; + Ok(val15 + 1 + 7 + 127) + } else { + Ok(val7 + 1 + 7) + } + } else { + Ok(val3 + 1) + } + } else { + Ok(0) + } + } + fn read_chroma(br: &mut BitReader) -> DecoderResult { + let mut idx = br.read(6)? as usize; + if idx > 0x30 { + let hibits = br.read(2)?; + idx += (hibits << 6) as usize; + } + Ok(E102_CHROMA_TAB[idx]) + } +} + +impl NADecoder for Escape102Decoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, RGB555_FORMAT)); + validate!((vinfo.get_width() | vinfo.get_height()) & 1 == 0); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + self.frame = vec![0; vinfo.get_width() * vinfo.get_height()]; + self.width = vinfo.get_width(); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 8); + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + let codec_id = br.read_u32le()?; + validate!(codec_id == 0x102); + let vsize = br.read_u32le()? as usize; + validate!(src.len() >= vsize); + + let mut is_intra = true; + let mut br = BitReader::new(&src[8..], BitReaderMode::LE); + let mut skip = 0; + let mut new_skip = false; + for strip in self.frame.chunks_exact_mut(self.width * 2) { + for x in (0..self.width).step_by(2) { + if !new_skip { + skip = Self::read_skip(&mut br)?; + new_skip = true; + } + if skip > 0 { + skip -= 1; + is_intra = false; + continue; + } + + if !br.read_bool()? { + let chr = Self::read_chroma(&mut br)?; + for i in 0..4 { + strip[x + (i & 1) + (i >> 1) * self.width] &= 0x1F; + strip[x + (i & 1) + (i >> 1) * self.width] |= chr; + } + } else { + let pattern = br.read(3)?; + let y0 = br.read(5)? as u16; + let y1 = if pattern == 0 { 0 } else { br.read(5)? as u16 }; + let chr = if !br.read_bool()? { + strip[x] & !0x1F + } else { + Self::read_chroma(&mut br)? + }; + let clr0 = y0 | chr; + let clr1 = y1 | chr; + strip[x] = clr0; + strip[x + 1] = if (pattern & 1) == 0 { clr0 } else { clr1 }; + strip[x + self.width] = if (pattern & 2) == 0 { clr0 } else { clr1 }; + strip[x + 1 + self.width] = if (pattern & 4) == 0 { clr0 } else { clr1 }; + } + new_skip = false; + } + } + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + let mut buf = bufinfo.get_vbuf16().unwrap(); + let stride = buf.get_stride(0); + let data = buf.get_data_mut().unwrap(); + + for (dline, sline) in data.chunks_exact_mut(stride) + .zip(self.frame.chunks_exact(self.width)) { + for (dst, &src) in dline.iter_mut().zip(sline.iter()) { + *dst = YUV2RGB[src as usize]; + } + } + + 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 Escape102Decoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + struct Escape122Decoder { info: NACodecInfoRef, frame: Vec, @@ -547,6 +686,9 @@ impl NAOptionHandler for Escape130Decoder { fn query_option_value(&self, _name: &str) -> Option { None } } +pub fn get_decoder102() -> Box { + Box::new(Escape102Decoder::new()) +} pub fn get_decoder122() -> Box { Box::new(Escape122Decoder::new()) } @@ -721,6 +863,26 @@ mod test { use nihav_codec_support::test::dec_video::*; use crate::*; #[test] + fn test_escape102() { + let mut dmx_reg = RegisteredRawDemuxers::new(); + acorn_register_all_raw_demuxers(&mut dmx_reg); + let mut pkt_reg = RegisteredPacketisers::new(); + acorn_register_all_packetisers(&mut pkt_reg); + let mut dec_reg = RegisteredDecoders::new(); + acorn_register_all_decoders(&mut dec_reg); + + // sample from Archive Magazine CD 1995 + test_decoding_raw("armovie", "escape102", "assets/Acorn/sprite1", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x54a114a9, 0x2183370a, 0x4d12cd7d, 0x83da183b], + [0x8c2a3641, 0x2b81b458, 0xdae4df2c, 0x6c8d80c5], + [0x27d5f25d, 0x3cb31a7e, 0x9f3f9904, 0xe28c3419], + [0x82f2f3b6, 0xe2b9931d, 0x2df8f388, 0x33b4e307], + [0xf13e7c8e, 0xde49e589, 0x555a90fd, 0xa67f6641], + [0x4ab76426, 0x0ef60466, 0x5613869d, 0xbac97a4a]])); + } + #[test] fn test_escape122() { let mut dmx_reg = RegisteredRawDemuxers::new(); acorn_register_all_raw_demuxers(&mut dmx_reg); @@ -820,3 +982,38 @@ const E130_Y_SIGNS: [[i8; 4]; 64] = [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ]; + +const E102_CHROMA_TAB: [u16; 256] = [ + 0x77A0, 0x77C0, 0x77E0, 0x7400, 0x7420, 0x7440, 0x7460, 0x7BA0, + 0x7BC0, 0x7BE0, 0x7800, 0x7820, 0x7840, 0x7860, 0x7FA0, 0x7FC0, + 0x7FE0, 0x7C00, 0x7C20, 0x7C40, 0x7C60, 0x03A0, 0x03C0, 0x03E0, + 0x0000, 0x0020, 0x0040, 0x0060, 0x07A0, 0x07C0, 0x07E0, 0x0400, + 0x0420, 0x0440, 0x0460, 0x0BA0, 0x0BC0, 0x0BE0, 0x0800, 0x0820, + 0x0840, 0x0860, 0x0FA0, 0x0FC0, 0x0FE0, 0x0C00, 0x0C20, 0x0C40, + 0x0C60, 0x3340, 0x30C0, 0x2400, 0x1A80, 0x1800, 0x1980, 0x0CC0, + 0x02E0, 0x0180, 0x74C0, 0x6AE0, 0x6860, 0x5EE0, 0x5C60, 0x53A0, + 0x77A0, 0x77C0, 0x77E0, 0x7400, 0x7420, 0x7440, 0x7460, 0x7BA0, + 0x7BC0, 0x7BE0, 0x7800, 0x7820, 0x7840, 0x7860, 0x7FA0, 0x7FC0, + 0x7FE0, 0x7C00, 0x7C20, 0x7C40, 0x7C60, 0x03A0, 0x03C0, 0x03E0, + 0x0000, 0x0020, 0x0040, 0x0060, 0x07A0, 0x07C0, 0x07E0, 0x0400, + 0x0420, 0x0440, 0x0460, 0x0BA0, 0x0BC0, 0x0BE0, 0x0800, 0x0820, + 0x0840, 0x0860, 0x0FA0, 0x0FC0, 0x0FE0, 0x0C00, 0x0C20, 0x0C40, + 0x0C60, 0x33A0, 0x26E0, 0x2460, 0x1AE0, 0x1860, 0x0E80, 0x0D20, + 0x0340, 0x7680, 0x7520, 0x6B40, 0x68C0, 0x5F40, 0x5CC0, 0x5000, + 0x77A0, 0x77C0, 0x77E0, 0x7400, 0x7420, 0x7440, 0x7460, 0x7BA0, + 0x7BC0, 0x7BE0, 0x7800, 0x7820, 0x7840, 0x7860, 0x7FA0, 0x7FC0, + 0x7FE0, 0x7C00, 0x7C20, 0x7C40, 0x7C60, 0x03A0, 0x03C0, 0x03E0, + 0x0000, 0x0020, 0x0040, 0x0060, 0x07A0, 0x07C0, 0x07E0, 0x0400, + 0x0420, 0x0440, 0x0460, 0x0BA0, 0x0BC0, 0x0BE0, 0x0800, 0x0820, + 0x0840, 0x0860, 0x0FA0, 0x0FC0, 0x0FE0, 0x0C00, 0x0C20, 0x0C40, + 0x0C60, 0x3000, 0x2740, 0x24C0, 0x1B40, 0x18C0, 0x0EE0, 0x0D80, + 0x00C0, 0x76E0, 0x7580, 0x6BA0, 0x6920, 0x5FA0, 0x5D20, 0x5060, + 0x77A0, 0x77C0, 0x77E0, 0x7400, 0x7420, 0x7440, 0x7460, 0x7BA0, + 0x7BC0, 0x7BE0, 0x7800, 0x7820, 0x7840, 0x7860, 0x7FA0, 0x7FC0, + 0x7FE0, 0x7C00, 0x7C20, 0x7C40, 0x7C60, 0x03A0, 0x03C0, 0x03E0, + 0x0000, 0x0020, 0x0040, 0x0060, 0x07A0, 0x07C0, 0x07E0, 0x0400, + 0x0420, 0x0440, 0x0460, 0x0BA0, 0x0BC0, 0x0BE0, 0x0800, 0x0820, + 0x0840, 0x0860, 0x0FA0, 0x0FC0, 0x0FE0, 0x0C00, 0x0C20, 0x0C40, + 0x0C60, 0x33A0, 0x27A0, 0x2520, 0x1BA0, 0x1920, 0x0F40, 0x0280, + 0x0120, 0x7740, 0x6A80, 0x6800, 0x6980, 0x5C00, 0x5340, 0x50C0 +]; diff --git a/nihav-acorn/src/codecs/mod.rs b/nihav-acorn/src/codecs/mod.rs index 1c77ecb..3dcdd15 100644 --- a/nihav-acorn/src/codecs/mod.rs +++ b/nihav-acorn/src/codecs/mod.rs @@ -61,6 +61,8 @@ const ACORN_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_linepack")] DecoderInfo { name: "linepack", get_decoder: linepack::get_decoder }, +#[cfg(feature="decoder_escape")] + DecoderInfo { name: "escape102", get_decoder: escape::get_decoder102 }, #[cfg(feature="decoder_escape")] DecoderInfo { name: "escape122", get_decoder: escape::get_decoder122 }, #[cfg(feature="decoder_escape")] @@ -104,6 +106,8 @@ const ACORN_PACKETISERS: &[PacketiserInfo] = &[ #[cfg(feature="decoder_linepack")] PacketiserInfo { name: "linepack", get_packetiser: linepack::get_packetiser }, +#[cfg(feature="decoder_escape")] + PacketiserInfo { name: "escape102", get_packetiser: escape::get_packetiser }, #[cfg(feature="decoder_escape")] PacketiserInfo { name: "escape122", get_packetiser: escape::get_packetiser }, #[cfg(feature="decoder_escape")] -- 2.39.5