]> git.nihav.org Git - nihav.git/commitdiff
nihav-acorn: add support for known Eidos Escape codecs
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 8 Mar 2025 13:39:32 +0000 (14:39 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 8 Mar 2025 13:39:32 +0000 (14:39 +0100)
nihav-acorn/Cargo.toml
nihav-acorn/src/codecs/escape.rs [new file with mode: 0644]
nihav-acorn/src/codecs/mod.rs
nihav-acorn/src/demuxers/armovie.rs
nihav-registry/src/register.rs

index 43365b7b5431c0932daa42fb51dfc36bb515a32b..e430a58faf7aec070a0bde3aa3a2248405e51121 100644 (file)
@@ -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 (file)
index 0000000..4aeefde
--- /dev/null
@@ -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<usize>;
+}
+
+impl<'a> ReadECode for BitReader<'a> {
+    fn read_ecode(&mut self) -> DecoderResult<usize> {
+        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<u8>,
+    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<NAFrameRef> {
+        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<NAValue> { 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<u16>,
+    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<NAFrameRef> {
+        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<NAValue> { 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<E130Block>,
+}
+
+impl Escape130Decoder {
+    fn new() -> Self { Self::default() }
+    fn read_skip(br: &mut BitReader) -> DecoderResult<usize> {
+        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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+pub fn get_decoder122() -> Box<dyn NADecoder + Send> {
+    Box::new(Escape122Decoder::new())
+}
+pub fn get_decoder124() -> Box<dyn NADecoder + Send> {
+    Box::new(Escape124Decoder::new())
+}
+pub fn get_decoder130() -> Box<dyn NADecoder + Send> {
+    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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+pub fn get_decoder_audio() -> Box<dyn NADecoder + Send> {
+    Box::new(EscapeADPCMDecoder::new())
+}
+
+#[derive(Default)]
+struct EscapePacketiser {
+    stream:     Option<NAStreamRef>,
+    buf:        Vec<u8>,
+    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<NAStreamRef> {
+        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<usize> {
+        Err(DecoderError::NotImplemented)
+    }
+    fn get_packet(&mut self, stream: NAStreamRef) -> DecoderResult<Option<NAPacket>> {
+        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<dyn NAPacketiser + Send> {
+    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 ]
+];
index 7842b74bfa6a1d183af0a7c61eb25144cc367417..48d8a3e16d2503f3263d922f3b16bf4ec7beae30 100644 (file)
@@ -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 },
 
index 4b2b3f6c53a3295557eee3e749204351eb9880d2..1c2174893fe9e7f64ee1e741dcc127f4ed83a2c7 100644 (file)
@@ -195,7 +195,7 @@ impl<'a> ARMovieDemuxer<'a> {
                 }
 
                 let tot_size: u32 = vid_size + aud_sizes.iter().sum::<u32>();
-                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;
index ca518987a7900daa96d7148c690325642923745e..ecaed89ca24250cb008aac1e20cc9b0f067be977 100644 (file)
@@ -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"),