support raw video in ARMovie
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 4 May 2024 14:58:57 +0000 (16:58 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 4 May 2024 14:58:57 +0000 (16:58 +0200)
nihav-acorn/Cargo.toml
nihav-acorn/src/codecs/mod.rs
nihav-acorn/src/codecs/rawvideo.rs [new file with mode: 0644]
nihav-acorn/src/demuxers/armovie.rs
nihav-registry/src/register.rs

index 34368732b1eefe451915f177d81f691b9654e781..db93d87306282a9741b1cf44912928db1a5a1fea 100644 (file)
@@ -14,13 +14,14 @@ path = "../nihav-codec-support"
 default = ["all_decoders", "all_demuxers"]
 
 all_decoders = ["all_video_decoders"]
-all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_linepack"]
+all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_linepack", "decoder_rawvideo"]
 decoders = []
 
 decoder_movinglines = ["decoders"]
 decoder_movingblocks = ["decoders"]
 decoder_movingblockshq = ["decoders"]
 decoder_linepack = ["decoders"]
+decoder_rawvideo = ["decoders"]
 
 all_demuxers = ["demuxer_armovie"]
 demuxers = []
index f5d5b5aa8af85d088df567341bfb8610c3a65a0f..bb1c5adc7d605242c19017de8b6b2bee7b635d17 100644 (file)
@@ -27,11 +27,15 @@ mod movinglines;
 mod movingblocks;
 #[cfg(feature="decoder_movingblockshq")]
 mod movingblockshq;
+#[cfg(feature="decoder_rawvideo")]
+mod rawvideo;
 
 #[cfg(feature="decoder_linepack")]
 mod linepack;
 
 const ACORN_CODECS: &[DecoderInfo] = &[
+#[cfg(feature="decoder_rawvideo")]
+    DecoderInfo { name: "arm_rawvideo", get_decoder: rawvideo::get_decoder },
 #[cfg(feature="decoder_movinglines")]
     DecoderInfo { name: "movinglines", get_decoder: movinglines::get_decoder },
 #[cfg(feature="decoder_movingblocks")]
@@ -51,6 +55,8 @@ pub fn acorn_register_all_decoders(rd: &mut RegisteredDecoders) {
 }
 
 const ACORN_PACKETISERS: &[PacketiserInfo] = &[
+#[cfg(feature="decoder_rawvideo")]
+    PacketiserInfo { name: "arm_rawvideo", get_packetiser: rawvideo::get_packetiser },
 #[cfg(feature="decoder_movinglines")]
     PacketiserInfo { name: "movinglines", get_packetiser: movinglines::get_packetiser },
 #[cfg(feature="decoder_movingblocks")]
diff --git a/nihav-acorn/src/codecs/rawvideo.rs b/nihav-acorn/src/codecs/rawvideo.rs
new file mode 100644 (file)
index 0000000..98ce043
--- /dev/null
@@ -0,0 +1,321 @@
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use nihav_core::io::bitreader::*;
+
+use super::RGB555_FORMAT;
+use super::yuvtab::YUV2RGB;
+
+const YUV422_FORMAT: NAPixelFormaton = NAPixelFormaton { model: ColorModel::
+YUV(YUVSubmodel::YUVJ), components: 3,
+        comp_info: [
+            Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 0, next_elem: 1}),
+            Some(NAPixelChromaton{ h_ss: 1, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 1, next_elem: 1}),
+            Some(NAPixelChromaton{ h_ss: 1, v_ss: 0, packed: false, depth: 8, shift: 0, comp_offs: 2, next_elem: 1}),
+            None, None],
+        elem_size: 0, be: false, alpha: false, palette: false };
+
+
+trait ReadYUV5 {
+    fn read_y(&mut self) -> DecoderResult<u8>;
+    fn read_uv(&mut self) -> DecoderResult<u8>;
+}
+
+impl<'a> ReadYUV5 for BitReader<'a> {
+    fn read_y(&mut self) -> DecoderResult<u8> {
+        let v = self.read(5)? as u8;
+        Ok((v << 3) | (v >> 2))
+    }
+    fn read_uv(&mut self) -> DecoderResult<u8> {
+        const EXPAND: [u8; 16] = [
+            0x00, 0x08, 0x11, 0x19, 0x22, 0x2A, 0x33, 0x3B, 0x44, 0x4C, 0x55, 0x5D, 0x66, 0x6E, 0x77, 0x7F];
+        let v = self.read(5)? as u8;
+        if v < 16 {
+            Ok(EXPAND[v as usize] | 0x80)
+        } else {
+            Ok(EXPAND[(v & 0xF) as usize])
+        }
+    }
+}
+
+#[derive(Default)]
+struct RawDecoder {
+    info:           NACodecInfoRef,
+    width:          usize,
+    height:         usize,
+    is_yuv:         bool,
+    codec_id:       u16,
+}
+
+impl RawDecoder {
+    fn new() -> Self { Self::default() }
+}
+
+impl NADecoder for RawDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+            self.width  = vinfo.get_width();
+            self.height = vinfo.get_height();
+            validate!(info.get_extradata().is_some());
+
+            if let Some(edata) = info.get_extradata() {
+                validate!(edata.len() > 1);
+                self.codec_id = u16::from(edata[0]) + 256 * u16::from(edata[1]);
+
+                for triplet in edata.windows(3) {
+                    if triplet == b"YUV" {
+                        self.is_yuv = true;
+                        break;
+                    }
+                }
+            } else {
+                return Err(DecoderError::InvalidData);
+            }
+
+            let fmt = match self.codec_id {
+                    2 => RGB555_FORMAT,
+                    3 if self.is_yuv => {
+                        validate!((self.width  & 1) == 0);
+                        YUV422_FORMAT
+                    },
+                    5 if self.is_yuv => {
+                        validate!((self.width  & 1) == 0);
+                        validate!((self.height & 1) == 0);
+                        YUV420_FORMAT
+                    },
+                    _ => return Err(DecoderError::NotImplemented),
+                };
+
+            let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, fmt));
+            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<NAFrameRef> {
+        let src = pkt.get_buffer();
+        validate!(src.len() > 1);
+
+        let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?;
+        match self.codec_id {
+            2 => {
+                let mut mr = MemoryReader::new_read(&src);
+                let mut br = ByteReader::new(&mut mr);
+
+                let mut buf = bufinfo.get_vbuf16().unwrap();
+                let stride = buf.get_stride(0);
+                let data = buf.get_data_mut().unwrap();
+
+                for dline in data.chunks_exact_mut(stride).take(self.height) {
+                    for el in dline[..self.width].iter_mut().take(self.width) {
+                        *el = br.read_u16le()?;
+                        if self.is_yuv {
+                            *el = YUV2RGB[(*el as usize) & 0x7FFF];
+                        }
+                    }
+                }
+            },
+            3 => {
+                let mut br = BitReader::new(&src, BitReaderMode::LE);
+                let mut buf = bufinfo.get_vbuf().unwrap();
+                let dst = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap();
+
+                let mut yoff = dst.offset[0];
+                let mut uoff = dst.offset[1];
+                let mut voff = dst.offset[2];
+                for _y in 0..self.height {
+                    for x in (0..self.width).step_by(2) {
+                        dst.data[yoff + x]     = br.read_y()?;
+                        dst.data[yoff + x + 1] = br.read_y()?;
+                        dst.data[uoff + x / 2] = br.read_uv()?;
+                        dst.data[voff + x / 2] = br.read_uv()?;
+                    }
+                    yoff += dst.stride[0];
+                    uoff += dst.stride[1];
+                    voff += dst.stride[2];
+                }
+            },
+            5 => {
+                let mut br = BitReader::new(&src, BitReaderMode::LE);
+                let mut buf = bufinfo.get_vbuf().unwrap();
+                let dst = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap();
+
+                let mut yoff = dst.offset[0];
+                let mut uoff = dst.offset[1];
+                let mut voff = dst.offset[2];
+                for _y in (0..self.height).step_by(2) {
+                    for x in (0..self.width).step_by(2) {
+                        dst.data[yoff + x]                     = br.read_y()?;
+                        dst.data[yoff + x + 1]                 = br.read_y()?;
+                        dst.data[yoff + x + dst.stride[0]]     = br.read_y()?;
+                        dst.data[yoff + x + dst.stride[0] + 1] = br.read_y()?;
+                        dst.data[uoff + x / 2]                 = br.read_uv()?;
+                        dst.data[voff + x / 2]                 = br.read_uv()?;
+                                                                 br.skip(2)?;
+                    }
+                    yoff += dst.stride[0] * 2;
+                    uoff += dst.stride[1];
+                    voff += dst.stride[2];
+                }
+            },
+            _ => unreachable!(),
+        }
+
+        let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo);
+        frm.set_keyframe(true);
+        frm.set_frame_type(FrameType::I);
+        Ok(frm.into_ref())
+    }
+    fn flush(&mut self) {}
+}
+
+impl NAOptionHandler for RawDecoder {
+    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() -> Box<dyn NADecoder + Send> {
+    Box::new(RawDecoder::new())
+}
+
+#[derive(Default)]
+struct RawPacketiser {
+    stream:     Option<NAStreamRef>,
+    buf:        Vec<u8>,
+    frameno:    u32,
+    size:       usize,
+}
+
+impl RawPacketiser {
+    fn new() -> Self { Self::default() }
+}
+
+impl NAPacketiser for RawPacketiser {
+    fn attach_stream(&mut self, stream: NAStreamRef) {
+        let vinfo = stream.get_info().get_properties().get_video_info().unwrap();
+        let width  = vinfo.width;
+        let height = vinfo.height;
+        if let Some(edata) = stream.get_info().get_extradata() {
+            if edata.len() > 1 {
+                let codec_id = u16::from(edata[0]) + 256 * u16::from(edata[1]);
+                /*let mut is_yuv = false;
+                for triplet in edata.windows(3) {
+                    if triplet == b"YUV" {
+                        is_yuv = true;
+                        break;
+                    }
+                }*/
+                self.size = match codec_id {
+                        2 => width * height * 2,
+                        3 => width * height * 10 / 8,
+                        5 => width * height,
+                        _ => unimplemented!(),
+                    };
+println!(" raw frame size {}", self.size);
+            }
+        }
+        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.size == 0 {
+            return Err(DecoderError::MissingReference);
+        }
+        if self.buf.len() < self.size {
+            return Ok(None);
+        }
+
+        let mut data = Vec::with_capacity(self.size);
+        data.extend_from_slice(&self.buf[..self.size]);
+        self.buf.drain(..self.size);
+
+        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, true, data)))
+    }
+    fn reset(&mut self) {
+        self.buf.clear();
+    }
+    fn bytes_left(&self) -> usize { self.buf.len() }
+}
+
+pub fn get_packetiser() -> Box<dyn NAPacketiser + Send> {
+    Box::new(RawPacketiser::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_format2() {
+        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);
+
+        // a sample from Acorn Replay Demonstration Disc 2
+        test_decoding_raw("armovie", "arm_rawvideo", "assets/Acorn/ROBIN2", Some(1),
+                          &dmx_reg, &pkt_reg, &dec_reg,
+                          ExpectedTestResult::MD5Frames(vec![
+                                [0x9a452976, 0x5fa64428, 0x71172412, 0x6db21372],
+                                [0xabc70d88, 0x2431a96b, 0xfc8d58a6, 0xef1bb1c9]]));
+    }
+
+    #[test]
+    fn test_format3() {
+        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);
+
+        // a sample from Cine Clips by Oregan Software Developments
+        test_decoding_raw("armovie", "arm_rawvideo", "assets/Acorn/TROPICLSUN", Some(1),
+                          &dmx_reg, &pkt_reg, &dec_reg,
+                          ExpectedTestResult::MD5Frames(vec![
+                                [0xcd5fe3d0, 0x60454448, 0x9f91180a, 0x8e73370d],
+                                [0x148b07bb, 0xbf647ddd, 0x2bf8c9e5, 0x4b37122a]]));
+    }
+
+    #[test]
+    fn test_format5() {
+        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);
+
+        // a sample from Empire video editor demo
+        test_decoding_raw("armovie", "arm_rawvideo", "assets/Acorn/CLIP3", Some(1),
+                          &dmx_reg, &pkt_reg, &dec_reg,
+                          ExpectedTestResult::MD5Frames(vec![
+                                [0x816ccb08, 0x5e86539c, 0x1bb51e98, 0x849936c4],
+                                [0xa42cf122, 0x296f3825, 0xedb7f0fc, 0x25a7825e]]));
+    }
+}
index 5d97da9d7e317b52e2c46a930d52fcf9bff88ef4..9b9d8ee4c2f2db636101ee174577cf81af09a457 100644 (file)
@@ -2,6 +2,9 @@ use nihav_core::demuxers::*;
 
 const VIDEO_CODECS: &[(i32, &str)] = &[
     (  1, "movinglines"),
+    (  2, "arm_rawvideo"),
+    (  3, "arm_rawvideo"),
+    (  5, "arm_rawvideo"),
     (  7, "movingblocks"),
     ( 17, "movingblockshq"),
     ( 19, "supermovingblocks"),
@@ -272,8 +275,11 @@ impl<'a> RawDemuxCore<'a> for ARMovieDemuxer<'a> {
             }
             let tb_den = tbase as u32;
 
+            let mut edata = vec![video_codec as u8, (video_codec >> 8) as u8];
+            edata.extend_from_slice(&vformat);
+
             let vci = NACodecTypeInfo::Video(NAVideoInfo::new(width, height, false, YUV420_FORMAT));
-            let vinfo = NACodecInfo::new(codec_name, vci, Some(vformat));
+            let vinfo = NACodecInfo::new(codec_name, vci, Some(edata));
             let ret = strmgr.add_stream(NAStream::new(StreamType::Video, stream_id, vinfo, tb_num, tb_den, (frm_per_chunk * num_chunks) as u64));
             if ret.is_some() {
                 stream_id += 1;
index edcd03744951a3aa6d5bb01972881e842d5b0777..1672b7e50c165a3ba78a68d05cac5bb8700f4ea1 100644 (file)
@@ -207,6 +207,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(audio;    "qdesign-music2",       "QDesign Music v2"),
     desc!(audio;    "qualcomm-purevoice",   "Qualcomm PureVoice"),
 
+    desc!(video-ll; "arm_rawvideo",         "Acorn Replay Movie raw video formats"),
     desc!(video;    "movinglines",          "Acorn Moving Lines"),
     desc!(video;    "movingblocks",         "Acorn Moving Blocks"),
     desc!(video;    "movingblockshq",       "Acorn Moving Blocks HQ"),