From 419d536b4117d8eb4d47f731f4a81f2d9179345d Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sat, 8 Mar 2025 14:39:32 +0100 Subject: [PATCH] nihav-acorn: add support for known Eidos Escape codecs --- nihav-acorn/Cargo.toml | 3 +- nihav-acorn/src/codecs/escape.rs | 823 ++++++++++++++++++++++++++++ nihav-acorn/src/codecs/mod.rs | 21 + nihav-acorn/src/demuxers/armovie.rs | 9 +- nihav-registry/src/register.rs | 1 + 5 files changed, 854 insertions(+), 3 deletions(-) create mode 100644 nihav-acorn/src/codecs/escape.rs diff --git a/nihav-acorn/Cargo.toml b/nihav-acorn/Cargo.toml index 43365b7..e430a58 100644 --- a/nihav-acorn/Cargo.toml +++ b/nihav-acorn/Cargo.toml @@ -14,7 +14,7 @@ path = "../nihav-codec-support" default = ["all_decoders", "all_demuxers", "all_packetisers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] -all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_supermovingblocks", "decoder_linepack", "decoder_rawvideo"] +all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_supermovingblocks", "decoder_linepack", "decoder_escape", "decoder_rawvideo"] all_audio_decoders = ["decoder_rawaudio"] decoders = [] @@ -24,6 +24,7 @@ decoder_movingblockshq = ["decoders"] decoder_supermovingblocks = ["decoders"] decoder_linepack = ["decoders"] decoder_rawvideo = ["decoders"] +decoder_escape = ["decoders"] decoder_rawaudio = ["decoders"] diff --git a/nihav-acorn/src/codecs/escape.rs b/nihav-acorn/src/codecs/escape.rs new file mode 100644 index 0000000..4aeefde --- /dev/null +++ b/nihav-acorn/src/codecs/escape.rs @@ -0,0 +1,823 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use nihav_core::io::bitreader::*; +use nihav_codec_support::codecs::imaadpcm::*; +use std::str::FromStr; + +const BGR555_FORMAT: NAPixelFormaton = NAPixelFormaton { model: ColorModel::RGB(RGBSubmodel::RGB), components: 3, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 10, comp_offs: 0, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 5, comp_offs: 0, next_elem: 2 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 5, shift: 0, comp_offs: 0, next_elem: 2 }), + None, None], + elem_size: 2, be: false, alpha: false, palette: false }; + +trait ReadECode { + fn read_ecode(&mut self) -> DecoderResult; +} + +impl<'a> ReadECode for BitReader<'a> { + fn read_ecode(&mut self) -> DecoderResult { + if self.read_bool()? { + let val3 = self.read(3)? as usize; + if val3 == 7 { + let val7 = self.read(7)? as usize; + if val7 == 127 { + let val12 = self.read(12)? as usize; + Ok(val12 + 1 + 7 + 127) + } else { + Ok(val7 + 1 + 7) + } + } else { + Ok(val3 + 1) + } + } else { + Ok(0) + } + } +} + +struct Escape122Decoder { + info: NACodecInfoRef, + frame: Vec, + width: usize, + pal: [u8; 768], +} + +impl Escape122Decoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + frame: Vec::new(), + width: 0, + pal: [0; 768], + } + } + fn read_blk2x2(br: &mut BitReader) -> DecoderResult<[u8; 4]> { + let mask = br.read(4)? as usize; + match mask { + 0x0 => { + let idx = br.read(7)? as u8; + Ok([idx * 2; 4]) + }, + 0xF => { + let idx = br.read(7)? as u8; + Ok([idx * 2 + 1; 4]) + }, + _ => { + let clr0 = br.read(8)? as u8; + let clr1 = br.read(8)? as u8; + let clrs = [clr0, clr1]; + Ok([clrs[mask & 1], clrs[(mask >> 1) & 1], clrs[(mask >> 2) & 1], clrs[mask >> 3]]) + } + } + } +} + +impl NADecoder for Escape122Decoder { + 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, PAL8_FORMAT)); + validate!((vinfo.get_width() | vinfo.get_height()) & 7 == 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 == 0x100 + 22); + let vsize = br.read_u32le()? as usize; + validate!(src.len() >= vsize); + let pal_size = br.read_u16le()? as usize; + validate!((pal_size + 2) & 3 == 0); + let nentries = (pal_size / 3).min(256); + if nentries > 0 { + br.read_buf(&mut self.pal[..nentries * 3])?; + for el in self.pal[..nentries * 3].iter_mut() { + *el = (*el << 2) | (*el >> 4); + } + } + br.read_skip(pal_size - nentries * 3)?; + let frm_start = br.tell() as usize; + + let mut is_intra = true; + let mut br = BitReader::new(&src[frm_start..], BitReaderMode::LE); + let mut skip = 0; + let mut new_skip = false; + let mut offsets = [0; 16]; + for (i, dst) in offsets.iter_mut().enumerate() { + *dst = (i & 3) * 2 + (i >> 2) * 2 * self.width; + } + for strip in self.frame.chunks_exact_mut(self.width * 8) { + for x in (0..self.width).step_by(8) { + if !new_skip { + skip = br.read_ecode()?; + new_skip = true; + } + if skip > 0 { + skip -= 1; + is_intra = false; + continue; + } + + while !br.read_bool()? { + let blk = Self::read_blk2x2(&mut br)?; + let mut mask = br.read(16)?; + for &offset in offsets.iter() { + if (mask & 1) != 0 { + strip[offset + x] = blk[0]; + strip[offset + x + 1] = blk[1]; + strip[offset + x + self.width] = blk[2]; + strip[offset + x + self.width + 1] = blk[3]; + } + mask >>= 1; + } + } + + if !br.read_bool()? { + let mut mask = br.read(16)?; + for &offset in offsets.iter() { + if (mask & 1) != 0 { + let blk = Self::read_blk2x2(&mut br)?; + strip[offset + x] = blk[0]; + strip[offset + x + 1] = blk[1]; + strip[offset + x + self.width] = blk[2]; + strip[offset + x + self.width + 1] = blk[3]; + } + mask >>= 1; + } + } + new_skip = false; + } + } + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 3)?; + let mut buf = bufinfo.get_vbuf().unwrap(); + let stride = buf.get_stride(0); + let paloff = buf.get_offset(1); + let data = buf.get_data_mut().unwrap(); + + for (dline, sline) in data.chunks_exact_mut(stride) + .zip(self.frame.chunks_exact(self.width)) { + dline[..self.width].copy_from_slice(sline); + } + data[paloff..][..768].copy_from_slice(&self.pal); + + 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 Escape122Decoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +#[derive(Default)] +struct E124Codebook { + entries: Vec<[u16; 4]>, + bits: u8, +} + +impl E124Codebook { + fn read(&mut self, br: &mut BitReader, size: usize, bits: u8) -> DecoderResult<()> { + self.entries.clear(); + self.entries.reserve(size); + self.bits = bits; + for _ in 0..size { + let mask = br.read(4)? as usize; + let clr0 = br.read(15)? as u16; + let clr1 = br.read(15)? as u16; + let clrs = [clr0, clr1]; + self.entries.push([clrs[mask & 1], clrs[(mask >> 1) & 1], clrs[(mask >> 2) & 1], clrs[mask >> 3]]); + } + Ok(()) + } +} + +struct Escape124Decoder { + info: NACodecInfoRef, + frame: Vec, + width: usize, + num_tiles: usize, + codebook: [E124Codebook; 3], +} + +impl Escape124Decoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + frame: Vec::new(), + width: 0, + num_tiles: 0, + codebook: [E124Codebook::default(), E124Codebook::default(), E124Codebook::default()], + } + } + fn read_block(br: &mut BitReader, cb_idx: &mut usize, codebook: &[E124Codebook; 3], blk_idx: usize) -> DecoderResult<[u16; 4]> { + if br.read_bool()? { + const NEXT_INDEX: [[usize; 2]; 3] = [[2, 1], [0, 2], [1, 0]]; + *cb_idx = NEXT_INDEX[*cb_idx][br.read(1)? as usize]; + } + + let mut idx = br.read(codebook[*cb_idx].bits)? as usize; + if *cb_idx == 1 { + idx += blk_idx << codebook[1].bits; + } + validate!(idx < codebook[*cb_idx].entries.len()); + Ok(codebook[*cb_idx].entries[idx]) + } +} + +impl NADecoder for Escape124Decoder { + 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, BGR555_FORMAT)); + validate!((vinfo.get_width() | vinfo.get_height()) & 7 == 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(); + self.num_tiles = (vinfo.get_width() / 8) * (vinfo.get_height() / 8); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 8); + validate!(src[0] == 20 && src[1] == 1); + let flags = read_u16le(&src[2..])?; + let vsize = read_u32le(&src[4..])? as usize; + validate!(src.len() >= vsize); + + let mut br = BitReader::new(&src[8..], BitReaderMode::LE); + + let mut is_intra = true; + if (flags & 0x2) != 0 { + let bits = br.read(4)? as u8; + self.codebook[0].read(&mut br, 1 << bits, bits)?; + } + if (flags & 0x4) != 0 { + let bits = br.read(4)? as u8; + self.codebook[1].read(&mut br, self.num_tiles << bits, bits)?; + } + if (flags & 0x8) != 0 { + let cb_size = br.read(20)? as usize; + if cb_size > 0 { + let bits = 32 - (cb_size as u32 - 1).leading_zeros(); + self.codebook[2].read(&mut br, cb_size, bits as u8)?; + } else { + self.codebook[2].bits = 0; + self.codebook[2].entries.clear(); + } + } + + const MASKS: [u32; 16] = [ + 0x0001, 0x0002, 0x0010, 0x0020, + 0x0004, 0x0008, 0x0040, 0x0080, + 0x0100, 0x0200, 0x1000, 0x2000, + 0x0400, 0x0800, 0x4000, 0x8000 + ]; + let mut skip = 0; + let mut new_skip = false; + let mut offsets = [0; 16]; + for (i, dst) in offsets.iter_mut().enumerate() { + *dst = (i & 3) * 2 + (i >> 2) * 2 * self.width; + } + let mut cb_idx = 1; + let mut blk_idx = 0; + for strip in self.frame.chunks_exact_mut(self.width * 8) { + for x in (0..self.width).step_by(8) { + blk_idx += 1; + if !new_skip { + skip = br.read_ecode()?; + new_skip = true; + } + if skip > 0 { + skip -= 1; + is_intra = false; + continue; + } + + let mut full_cbp = 0; + while !br.read_bool()? { + let blk = Self::read_block(&mut br, &mut cb_idx, &self.codebook, blk_idx - 1)?; + let mask = br.read(16)?; + full_cbp |= mask; + for (&offset, &ref_mask) in offsets.iter().zip(MASKS.iter()) { + if (mask & ref_mask) != 0 { + strip[offset + x] = blk[0]; + strip[offset + x + 1] = blk[1]; + strip[offset + x + self.width] = blk[2]; + strip[offset + x + self.width + 1] = blk[3]; + } + } + } + + if !br.read_bool()? { + let mut mask = br.read(4)?; + for i in 0..4 { + let seg = if (mask & 1) != 0 { 0xF } else { br.read(4)? }; + full_cbp ^= seg << (i * 4); + mask >>= 1; + } + for (&offset, &ref_mask) in offsets.iter().zip(MASKS.iter()) { + if (full_cbp & ref_mask) != 0 { + let blk = Self::read_block(&mut br, &mut cb_idx, &self.codebook, blk_idx - 1)?; + strip[offset + x] = blk[0]; + strip[offset + x + 1] = blk[1]; + strip[offset + x + self.width] = blk[2]; + strip[offset + x + self.width + 1] = blk[3]; + } + } + } else if (flags & 1) != 0 { + while !br.read_bool()? { + let blk = Self::read_block(&mut br, &mut cb_idx, &self.codebook, blk_idx - 1)?; + let offset = offsets[br.read(4)? as usize]; + strip[offset + x] = blk[0]; + strip[offset + x + 1] = blk[1]; + strip[offset + x + self.width] = blk[2]; + strip[offset + x + self.width + 1] = blk[3]; + } + } + + new_skip = false; + } + } + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 3)?; + 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)) { + dline[..self.width].copy_from_slice(sline); + } + + 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 Escape124Decoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +#[derive(Clone,Copy)] +struct E130Block { + y: [u8; 4], + y_avg: u8, + cb: u8, + cr: u8, +} + +impl Default for E130Block { + fn default() -> Self { + Self { + y: [0; 4], + y_avg: 0, + cb: 0x10, + cr: 0x10, + } + } +} + +#[derive(Default)] +struct Escape130Decoder { + info: NACodecInfoRef, + width: usize, + blocks: Vec, +} + +impl Escape130Decoder { + fn new() -> Self { Self::default() } + fn read_skip(br: &mut BitReader) -> DecoderResult { + if br.read_bool()? { + Ok(0) + } else { + let val3 = br.read(3)? as usize; + if val3 != 0 { + Ok(val3) + } else { + let val8 = br.read(8)? as usize; + if val8 != 0 { + Ok(val8 + 7) + } else { + let val15 = br.read(15)? as usize; + Ok(val15 + 7 + 255) + } + } + } + } +} + +impl NADecoder for Escape130Decoder { + 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, YUV420_FORMAT)); + validate!((vinfo.get_width() | vinfo.get_height()) & 7 == 0); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + self.width = vinfo.get_width(); + self.blocks = vec![E130Block::default(); vinfo.get_width() * vinfo.get_height() / 4]; + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 16); + validate!(src[0] == 0x30 && src[1] == 1); + let vsize = read_u32le(&src[4..])? as usize; + validate!(src.len() >= vsize); + + let mut br = BitReader::new(&src[16..], BitReaderMode::LE); + + let mut is_intra = true; + let mut blk = E130Block::default(); + let mut blk_pos = 0; + while blk_pos < self.blocks.len() { + let skip = Self::read_skip(&mut br)?; + validate!(blk_pos + skip <= self.blocks.len()); + blk_pos += skip; + if skip > 0 { + if blk_pos >= self.blocks.len() { + break; + } + blk = self.blocks[blk_pos - 1]; + is_intra = false; + } + + if br.read_bool()? { + const Y_STEPS: [i16; 4] = [2, 4, 10, 20]; + let sign_idx = br.read(6)? as usize; + let step_idx = br.read(2)? as usize; + blk.y_avg = br.read(5)? as u8 * 2; + for (dst, &sign) in blk.y.iter_mut().zip(E130_Y_SIGNS[sign_idx].iter()) { + *dst = (i16::from(blk.y_avg) + i16::from(sign) * Y_STEPS[step_idx]).max(0).min(0x3F) as u8; + } + } else if br.read_bool()? { + blk.y_avg = if br.read_bool()? { + br.read(6)? as u8 + } else { + const Y_DIFF: [i8; 8] = [ -4, -3, -2, -1, 1, 2, 3, 4 ]; + let diff_idx = br.read(3)? as usize; + blk.y_avg.wrapping_add(Y_DIFF[diff_idx] as u8) & 0x3F + }; + blk.y = [blk.y_avg; 4]; + } + + if br.read_bool()? { + if br.read_bool()? { + blk.cb = br.read(5)? as u8; + blk.cr = br.read(5)? as u8; + } else { + const CB_DIFF: [i8; 8] = [ 1, 1, 0, -1, -1, -1, 0, 1 ]; + const CR_DIFF: [i8; 8] = [ 0, 1, 1, 1, 0, -1, -1, -1 ]; + let delta_idx = br.read(3)? as usize; + blk.cb = blk.cb.wrapping_add(CB_DIFF[delta_idx] as u8) & 0x1F; + blk.cr = blk.cr.wrapping_add(CR_DIFF[delta_idx] as u8) & 0x1F; + } + } + + self.blocks[blk_pos] = blk; + blk_pos += 1; + } + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 3)?; + let mut buf = bufinfo.get_vbuf().unwrap(); + let frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + + let mut yoff = frm.offset[0]; + let mut cboff = frm.offset[1]; + let mut croff = frm.offset[2]; + for row in self.blocks.chunks(self.width / 2) { + for (x, blk) in row.iter().enumerate() { + frm.data[yoff + x * 2] = blk.y[0] << 2; + frm.data[yoff + x * 2 + 1] = blk.y[1] << 2; + frm.data[yoff + x * 2 + frm.stride[0]] = blk.y[2] << 2; + frm.data[yoff + x * 2 + frm.stride[0] + 1] = blk.y[3] << 2; + frm.data[cboff + x] = CHROMA_VALUES[usize::from(blk.cb) & 0x1F]; + frm.data[croff + x] = CHROMA_VALUES[usize::from(blk.cr) & 0x1F]; + } + yoff += frm.stride[0] * 2; + cboff += frm.stride[1]; + croff += frm.stride[2]; + } + + 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.blocks.iter_mut() { + *el = E130Block::default(); + } + } +} + +impl NAOptionHandler for Escape130Decoder { + 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_decoder122() -> Box { + Box::new(Escape122Decoder::new()) +} +pub fn get_decoder124() -> Box { + Box::new(Escape124Decoder::new()) +} +pub fn get_decoder130() -> Box { + Box::new(Escape130Decoder::new()) +} + +pub struct EscapeIMAState { + pub predictor: i32, + pub step: usize, +} + +impl EscapeIMAState { + pub fn new() -> Self { + Self { + predictor: 0, + step: 0, + } + } + pub fn reset(&mut self, predictor: i16, step: u8) { + self.predictor = i32::from(predictor); + self.step = step.min(IMA_MAX_STEP) as usize; + } + pub fn expand_sample(&mut self, nibble: u8) -> i16 { + let istep = (self.step as isize) + (IMA_STEPS[(nibble & 0xF) as usize] as isize); + let sign = (nibble & 8) != 0; + let diff = (i32::from(nibble & 7) * IMA_STEP_TABLE[self.step]) >> 2; + let sample = if !sign { self.predictor + diff } else { self.predictor - diff }; + self.predictor = sample.max(i32::from(std::i16::MIN)).min(i32::from(std::i16::MAX)); + self.step = istep.max(0).min(IMA_MAX_STEP as isize) as usize; + self.predictor as i16 + } +} + +struct EscapeADPCMDecoder { + ainfo: NAAudioInfo, + chmap: NAChannelMap, + ch_state: [EscapeIMAState; 2], +} + +impl EscapeADPCMDecoder { + fn new() -> Self { + Self { + ainfo: NAAudioInfo::new(0, 1, SND_S16P_FORMAT, 0), + chmap: NAChannelMap::new(), + ch_state: [EscapeIMAState::new(), EscapeIMAState::new()], + } + } +} + +impl NADecoder for EscapeADPCMDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { + let channels = ainfo.get_channels(); + validate!(channels == 1 || channels == 2); + self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels, SND_S16_FORMAT, 2); + self.chmap = NAChannelMap::from_str(if channels == 1 { "C" } else { "L,R" }).unwrap(); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + #[allow(clippy::identity_op)] + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let info = pkt.get_stream().get_info(); + if let NACodecTypeInfo::Audio(_) = info.get_properties() { + let pktbuf = pkt.get_buffer(); + let channels = self.ainfo.get_channels(); + let nsamples = pktbuf.len() * 2 / usize::from(channels); + let abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?; + let mut adata = abuf.get_abuf_i16().unwrap(); + let dst = adata.get_data_mut().unwrap(); + let idx2 = if channels == 1 { 0 } else { 1 }; + for (dpair, &src) in dst.chunks_exact_mut(2).zip(pktbuf.iter()) { + dpair[0] = self.ch_state[0].expand_sample(src >> 4); + dpair[1] = self.ch_state[idx2].expand_sample(src & 0xF); + } + + let mut frm = NAFrame::new_from_pkt(pkt, info, abuf); + frm.set_duration(Some(nsamples as u64)); + frm.set_keyframe(false); + Ok(frm.into_ref()) + } else { + Err(DecoderError::InvalidData) + } + } + fn flush(&mut self) { + self.ch_state[0].reset(0, 0); + self.ch_state[1].reset(0, 0); + } +} + +impl NAOptionHandler for EscapeADPCMDecoder { + 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_audio() -> Box { + Box::new(EscapeADPCMDecoder::new()) +} + +#[derive(Default)] +struct EscapePacketiser { + stream: Option, + buf: Vec, + end: usize, + frameno: u32, +} + +impl EscapePacketiser { + fn new() -> Self { Self::default() } +} + +impl NAPacketiser for EscapePacketiser { + fn attach_stream(&mut self, stream: NAStreamRef) { + self.stream = Some(stream); + } + fn add_data(&mut self, src: &[u8]) -> bool { + self.buf.extend_from_slice(src); + self.buf.len() < (1 << 10) + } + fn parse_stream(&mut self, id: u32) -> DecoderResult { + if let Some(ref stream) = self.stream { + let mut stream = NAStream::clone(stream); + stream.id = id; + Ok(stream.into_ref()) + } else { + Err(DecoderError::MissingReference) + } + } + fn skip_junk(&mut self) -> DecoderResult { + Err(DecoderError::NotImplemented) + } + fn get_packet(&mut self, stream: NAStreamRef) -> DecoderResult> { + if self.buf.len() >= 8 { + if self.buf[1] != 1 { + return Err(DecoderError::InvalidData); + } + let vsize = read_u32le(&self.buf[4..]).unwrap_or_default() as usize; + if self.buf.len() < vsize { + return Ok(None); + } + let mut data = vec![0; vsize]; + data.copy_from_slice(&self.buf[..vsize]); + self.buf.drain(..vsize); + let ts = NATimeInfo::new(Some(u64::from(self.frameno)), None, None, stream.tb_num, stream.tb_den); + self.frameno += 1; + Ok(Some(NAPacket::new(stream, ts, false, data))) + } else { + Ok(None) + } + } + fn reset(&mut self) { + self.buf.clear(); + self.end = 0; + } + fn bytes_left(&self) -> usize { self.buf.len() } +} + +pub fn get_packetiser() -> Box { + Box::new(EscapePacketiser::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::{RegisteredDecoders, RegisteredPacketisers}; + use nihav_core::demuxers::RegisteredRawDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::*; + #[test] + fn test_escape122() { + 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); + + // https://samples.mplayerhq.hu/game-formats/rpl/bigredracing-escape122/INTROSND.RPL + test_decoding_raw("armovie", "escape122", "assets/Acorn/INTROSND.RPL", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x26550b92, 0xfd6d69e4, 0x42e692da, 0x5d3772d7], + [0x23cdd0fd, 0x7eecc973, 0xd3c6b2f0, 0xefff8259], + [0x48df421d, 0x6bb3cfaf, 0xfde6eaa4, 0xd667c14a], + [0x73242dff, 0xe991da8b, 0x3626d3a5, 0x54ed4205], + [0x36afa58b, 0x421580c0, 0x9232c164, 0x94dc9659], + [0xc76b37de, 0x51ab2a18, 0x2fb67559, 0x2d3fef13]])); + } + #[test] + fn test_escape124() { + 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); + + // https://samples.mplayerhq.hu/game-formats/rpl/escape124/ESCAPE.RPL + test_decoding_raw("armovie", "escape124", "assets/Acorn/ESCAPE.RPL", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x7062e4b6, 0x75ec8ada, 0x924b63e0, 0xa160d83c], + [0x22ecee28, 0xe4651563, 0x606fd6de, 0x03eac684], + [0xd5b51cb5, 0x252df07f, 0x758864d1, 0x919dd7db], + [0x4cba97c5, 0x6f693716, 0x2698e741, 0xbe60be37], + [0x2bf69c95, 0xc61889b6, 0xaa60f946, 0xd9ee23ae], + [0xec79d033, 0xd0f394fb, 0xec139ab7, 0xdc89a3e9]])); + } + #[test] + fn test_escape130() { + 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); + + // https://samples.mplayerhq.hu/game-formats/rpl/warzone/res_struttech.rpl + test_decoding_raw("armovie", "escape130", "assets/Acorn/res_struttech.rpl", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xdacfb155, 0xe729c6a5, 0xc5a77201, 0xf7818d6b], + [0xdacfb155, 0xe729c6a5, 0xc5a77201, 0xf7818d6b], + [0x0065306d, 0x5501862d, 0x4105ec8a, 0x823c0d53], + [0xf690e4de, 0xe0a12b58, 0xc5a4e00f, 0x0c183261], + [0xa0598121, 0xa434bead, 0xf605a6cc, 0xe8affc6a], + [0xd1444e15, 0xa23bd2f7, 0xfe66690a, 0x7f2d5a2f]])); + } + #[test] + fn test_escape_adpcm() { + 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); + + // https://samples.mplayerhq.hu/game-formats/rpl/bigredracing-escape122/INTROSND.RPL + test_decoding_raw("armovie", "escape-adpcm", "assets/Acorn/INTROSND.RPL", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5([0xcc1b7281, 0x30f005d2, 0xac7e9622, 0x4af24026])); + } +} + +const CHROMA_VALUES: [u8; 32] = [ + 20, 28, 36, 44, 52, 60, 68, 76, + 84, 92, 100, 106, 112, 116, 120, 124, + 128, 132, 136, 140, 144, 150, 156, 164, + 172, 180, 188, 196, 204, 212, 220, 228 +]; +const E130_Y_SIGNS: [[i8; 4]; 64] = [ + [ 0, 0, 0, 0 ], [ -1, 1, 0, 0 ], [ 1, -1, 0, 0 ], [ -1, 0, 1, 0 ], + [ -1, 1, 1, 0 ], [ 0, -1, 1, 0 ], [ 1, -1, 1, 0 ], [ -1, -1, 1, 0 ], + [ 1, 0, -1, 0 ], [ 0, 1, -1, 0 ], [ 1, 1, -1, 0 ], [ -1, 1, -1, 0 ], + [ 1, -1, -1, 0 ], [ -1, 0, 0, 1 ], [ -1, 1, 0, 1 ], [ 0, -1, 0, 1 ], + [ 0, 0, 0, 0 ], [ 1, -1, 0, 1 ], [ -1, -1, 0, 1 ], [ -1, 0, 1, 1 ], + [ -1, 1, 1, 1 ], [ 0, -1, 1, 1 ], [ 1, -1, 1, 1 ], [ -1, -1, 1, 1 ], + [ 0, 0, -1, 1 ], [ 1, 0, -1, 1 ], [ -1, 0, -1, 1 ], [ 0, 1, -1, 1 ], + [ 1, 1, -1, 1 ], [ -1, 1, -1, 1 ], [ 0, -1, -1, 1 ], [ 1, -1, -1, 1 ], + [ 0, 0, 0, 0 ], [ -1, -1, -1, 1 ], [ 1, 0, 0, -1 ], [ 0, 1, 0, -1 ], + [ 1, 1, 0, -1 ], [ -1, 1, 0, -1 ], [ 1, -1, 0, -1 ], [ 0, 0, 1, -1 ], + [ 1, 0, 1, -1 ], [ -1, 0, 1, -1 ], [ 0, 1, 1, -1 ], [ 1, 1, 1, -1 ], + [ -1, 1, 1, -1 ], [ 0, -1, 1, -1 ], [ 1, -1, 1, -1 ], [ -1, -1, 1, -1 ], + [ 0, 0, 0, 0 ], [ 1, 0, -1, -1 ], [ 0, 1, -1, -1 ], [ 1, 1, -1, -1 ], + [ -1, 1, -1, -1 ], [ 1, -1, -1, -1 ], [ 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 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] +]; diff --git a/nihav-acorn/src/codecs/mod.rs b/nihav-acorn/src/codecs/mod.rs index 7842b74..48d8a3e 100644 --- a/nihav-acorn/src/codecs/mod.rs +++ b/nihav-acorn/src/codecs/mod.rs @@ -35,6 +35,9 @@ mod rawvideo; #[cfg(feature="decoder_linepack")] mod linepack; +#[cfg(feature="decoder_escape")] +mod escape; + #[cfg(feature="decoder_rawaudio")] mod rawaudio; @@ -53,6 +56,15 @@ const ACORN_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_linepack")] DecoderInfo { name: "linepack", get_decoder: linepack::get_decoder }, +#[cfg(feature="decoder_escape")] + DecoderInfo { name: "escape122", get_decoder: escape::get_decoder122 }, +#[cfg(feature="decoder_escape")] + DecoderInfo { name: "escape124", get_decoder: escape::get_decoder124 }, +#[cfg(feature="decoder_escape")] + DecoderInfo { name: "escape130", get_decoder: escape::get_decoder130 }, +#[cfg(feature="decoder_escape")] + DecoderInfo { name: "escape-adpcm", get_decoder: escape::get_decoder_audio }, + #[cfg(feature="decoder_rawaudio")] DecoderInfo { name: "arm_rawaudio", get_decoder: rawaudio::get_decoder }, ]; @@ -82,6 +94,15 @@ const ACORN_PACKETISERS: &[PacketiserInfo] = &[ #[cfg(feature="decoder_linepack")] PacketiserInfo { name: "linepack", get_packetiser: linepack::get_packetiser }, +#[cfg(feature="decoder_escape")] + PacketiserInfo { name: "escape122", get_packetiser: escape::get_packetiser }, +#[cfg(feature="decoder_escape")] + PacketiserInfo { name: "escape124", get_packetiser: escape::get_packetiser }, +#[cfg(feature="decoder_escape")] + PacketiserInfo { name: "escape130", get_packetiser: escape::get_packetiser }, +#[cfg(feature="decoder_escape")] + PacketiserInfo { name: "escape-adpcm", get_packetiser: rawaudio::get_packetiser }, + #[cfg(feature="decoder_rawaudio")] PacketiserInfo { name: "arm_rawaudio", get_packetiser: rawaudio::get_packetiser }, diff --git a/nihav-acorn/src/demuxers/armovie.rs b/nihav-acorn/src/demuxers/armovie.rs index 4b2b3f6..1c21748 100644 --- a/nihav-acorn/src/demuxers/armovie.rs +++ b/nihav-acorn/src/demuxers/armovie.rs @@ -195,7 +195,7 @@ impl<'a> ARMovieDemuxer<'a> { } let tot_size: u32 = vid_size + aud_sizes.iter().sum::(); - validate!((tot_size as usize) <= cur_chunk_size); + validate!(cur_chunk_size == 0 || (tot_size as usize) <= cur_chunk_size); self.chunk_offs.push(ChunkInfo { offset, vid_size, aud_sizes }); } else { return Err(DemuxerError::InvalidData); @@ -305,11 +305,16 @@ impl<'a> RawDemuxCore<'a> for ARMovieDemuxer<'a> { for ((&id, &sratestr), (&chan, &fmt)) in sound_ids.iter().zip(srates.iter()) .zip(channels.iter().zip(sndformats.iter())) { let codec_id = parse_uint(id)?; - let codec_name = if codec_id == 1 { "arm_rawaudio" } else { "unknown" }; let channels = parse_uint(chan)?; validate!(channels > 0 && channels < 16); let edata = fmt.to_owned(); let bits = parse_uint(fmt)?; + let codec_name = match codec_id { + 1 => "arm_rawaudio", + 101 if bits == 8 => "arm_rawaudio", + 101 => "escape-adpcm", + _ => "unknown" + }; let mut srate = parse_uint(sratestr)?; if srate > 0 && srate < 1000 { // probably in microseconds instead of Hertz srate = 1000000 / srate; diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index ca51898..ecaed89 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -221,6 +221,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "escape122", "Eidos Escape 122"), desc!(video; "escape124", "Eidos Escape 124"), desc!(video; "escape130", "Eidos Escape 130"), + desc!(audio; "escape-adpcm", "Eidos Escape ADPCM"), desc!(video; "truemotion1", "TrueMotion 1"), desc!(video-im; "truemotionrt", "TrueMotion RT"), -- 2.39.5