From 57777a0a5ea7d4f36f25e4e2d90b4a33b920a88b Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sun, 17 May 2020 13:59:43 +0200 Subject: [PATCH] Cinepak decoder --- nihav-commonfmt/Cargo.toml | 3 +- nihav-commonfmt/src/codecs/cinepak.rs | 342 ++++++++++++++++++++++++++ nihav-commonfmt/src/codecs/mod.rs | 4 + nihav-registry/src/register.rs | 3 + 4 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 nihav-commonfmt/src/codecs/cinepak.rs diff --git a/nihav-commonfmt/Cargo.toml b/nihav-commonfmt/Cargo.toml index 2ec7154..6c0e810 100644 --- a/nihav-commonfmt/Cargo.toml +++ b/nihav-commonfmt/Cargo.toml @@ -27,7 +27,8 @@ demuxer_mov = ["demuxers"] all_decoders = ["all_video_decoders", "all_audio_decoders"] -all_video_decoders = ["decoder_clearvideo"] +all_video_decoders = ["decoder_cinepak", "decoder_clearvideo"] +decoder_cinepak = ["decoders"] decoder_clearvideo = ["decoders"] all_audio_decoders = ["decoder_pcm", "decoder_ts102366", "decoder_sipro", "decoder_atrac3", "decoder_aac"] diff --git a/nihav-commonfmt/src/codecs/cinepak.rs b/nihav-commonfmt/src/codecs/cinepak.rs new file mode 100644 index 0000000..a736a0b --- /dev/null +++ b/nihav-commonfmt/src/codecs/cinepak.rs @@ -0,0 +1,342 @@ +use nihav_core::io::byteio::{ByteReader,MemoryReader}; +use nihav_core::formats::YUV420_FORMAT; +use nihav_core::codecs::*; +use nihav_codec_support::codecs::HAMShuffler; + +struct CinepakDecoder { + info: NACodecInfoRef, + frmmgr: HAMShuffler, + cb_v1: [[u8; 6]; 256], + cb_v4: [[u8; 6]; 256], +} + +fn put_block(block: &[u8; 24], x: usize, y: usize, frm: &mut NASimpleVideoFrame) { + let mut yoff = frm.offset[0] + x + y * frm.stride[0]; + for i in 0..4 { + for j in 0..4 { + frm.data[yoff + j] = block[j + i * 4]; + } + yoff += frm.stride[0]; + } + let mut uoff = frm.offset[1] + x / 2 + y / 2 * frm.stride[1]; + for i in 0..2 { + for j in 0..2 { + frm.data[uoff + j] = block[j + i * 2 + 16]; + } + uoff += frm.stride[1]; + } + let mut voff = frm.offset[2] + x / 2 + y / 2 * frm.stride[2]; + for i in 0..2 { + for j in 0..2 { + frm.data[voff + j] = block[j + i * 2 + 20]; + } + voff += frm.stride[2]; + } +} + +impl CinepakDecoder { + fn new() -> Self { + CinepakDecoder { + info: NACodecInfo::new_dummy(), + frmmgr: HAMShuffler::new(), + cb_v1: [[0; 6]; 256], + cb_v4: [[0; 6]; 256], + } + } + fn read_cb(br: &mut ByteReader, size: usize, cb: &mut [[u8; 6]; 256], is_yuv: bool) -> DecoderResult<()> { + let cb_elem = if is_yuv { 6 } else { 4 }; + let cb_size = (size - 4) / cb_elem; + validate!(size - 4 == cb_size * cb_elem); + validate!(cb_size <= 256); + for i in 0..cb_size { + br.read_buf(&mut cb[i][..cb_elem])?; + if !is_yuv { + cb[i][4] = 0x80; + cb[i][5] = 0x80; + } else { + cb[i][4] ^= 0x80; + cb[i][5] ^= 0x80; + } + } + Ok(()) + } + fn read_cb_upd(br: &mut ByteReader, size: usize, cb: &mut [[u8; 6]; 256], is_yuv: bool) -> DecoderResult<()> { + let cb_elem = if is_yuv { 6 } else { 4 }; + let end = br.tell() + (size as u64) - 4; + for i in (0..256).step_by(32) { + if br.tell() >= end { + break; + } + let upd = br.read_u32be()?; + for j in 0..32 { + if ((upd >> (31 - j)) & 1) != 0 { + br.read_buf(&mut cb[i + j][..cb_elem])?; + if !is_yuv { + cb[i + j][4] = 0x80; + cb[i + j][5] = 0x80; + } else { + cb[i + j][4] ^= 0x80; + cb[i + j][5] ^= 0x80; + } + } + } + } + validate!(br.tell() == end); + Ok(()) + } + fn decode_strip(&mut self, src: &[u8], is_intra: bool, is_intra_strip: bool, xoff: usize, yoff: usize, xend: usize, yend: usize, frm: &mut NASimpleVideoFrame) -> DecoderResult<()> { + let mut mr = MemoryReader::new_read(src); + let mut br = ByteReader::new(&mut mr); + let mut idx_pos = 0; + let mut idx_size = 0; + let mut v1_only = false; + while br.left() > 0 { + let id = br.read_byte()?; + if (id & 0xF0) == 0x20 { + validate!(((id & 1) != 0) ^ is_intra_strip); + } + let size = br.read_u24be()? as usize; + validate!(size >= 4 && (size - 4 <= (br.left() as usize))); + match id { + 0x20 => Self::read_cb (&mut br, size, &mut self.cb_v4, true)?, + 0x21 => Self::read_cb_upd(&mut br, size, &mut self.cb_v4, true)?, + 0x22 => Self::read_cb (&mut br, size, &mut self.cb_v1, true)?, + 0x23 => Self::read_cb_upd(&mut br, size, &mut self.cb_v1, true)?, + 0x24 => Self::read_cb (&mut br, size, &mut self.cb_v4, false)?, + 0x25 => Self::read_cb_upd(&mut br, size, &mut self.cb_v4, false)?, + 0x26 => Self::read_cb (&mut br, size, &mut self.cb_v1, false)?, + 0x27 => Self::read_cb_upd(&mut br, size, &mut self.cb_v1, false)?, + 0x30 => { // intra indices + validate!(idx_pos == 0); + idx_pos = br.tell() as usize; + idx_size = size - 4; + br.read_skip(idx_size)?; + }, + 0x31 => { // inter indices + validate!(!is_intra); + validate!(idx_pos == 0); + idx_pos = br.tell() as usize; + idx_size = size - 4; + br.read_skip(idx_size)?; + }, + 0x32 => { // V1-only blocks + validate!(idx_pos == 0); + idx_pos = br.tell() as usize; + idx_size = size - 4; + v1_only = true; + br.read_skip(idx_size)?; + }, + _ => return Err(DecoderError::InvalidData), + }; + } + validate!(idx_pos != 0); + let mut mr = MemoryReader::new_read(&src[idx_pos..][..idx_size]); + let mut br = ByteReader::new(&mut mr); + + let mut x = xoff; + let mut y = yoff; + let mut block = [0u8; 24]; + while br.left() > 0 { + let flags = if !v1_only { br.read_u32be()? } else { 0xFFFFFFFF }; + let mut mask = 1 << 31; + while mask > 0 { + if !is_intra { + let skip = (flags & mask) == 0; + mask >>= 1; + if skip { + x += 4; + if x >= xend { + x = xoff; + y += 4; + if y == yend { + return Ok(()); + } + } + } + continue; + } + if (flags & mask) == 0 { + let idx = br.read_byte()? as usize; + let cb = &self.cb_v1[idx]; + block[ 0] = cb[0]; block[ 1] = cb[0]; block[ 2] = cb[1]; block[ 3] = cb[1]; + block[ 4] = cb[0]; block[ 5] = cb[0]; block[ 6] = cb[1]; block[ 7] = cb[1]; + block[ 8] = cb[2]; block[ 9] = cb[2]; block[10] = cb[3]; block[11] = cb[3]; + block[12] = cb[2]; block[13] = cb[2]; block[14] = cb[3]; block[15] = cb[3]; + block[16] = cb[4]; block[17] = cb[4]; + block[18] = cb[4]; block[19] = cb[4]; + block[20] = cb[5]; block[21] = cb[5]; + block[22] = cb[5]; block[23] = cb[5]; + } else { + let idx0 = br.read_byte()? as usize; + let cb0 = &self.cb_v4[idx0]; + let idx1 = br.read_byte()? as usize; + let cb1 = &self.cb_v4[idx1]; + let idx2 = br.read_byte()? as usize; + let cb2 = &self.cb_v4[idx2]; + let idx3 = br.read_byte()? as usize; + let cb3 = &self.cb_v4[idx3]; + block[ 0] = cb0[0]; block[ 1] = cb0[1]; block[ 2] = cb1[0]; block[ 3] = cb1[1]; + block[ 4] = cb0[2]; block[ 5] = cb0[3]; block[ 6] = cb1[2]; block[ 7] = cb1[3]; + block[ 8] = cb2[0]; block[ 9] = cb2[1]; block[10] = cb3[0]; block[11] = cb3[1]; + block[12] = cb2[2]; block[13] = cb2[3]; block[14] = cb3[2]; block[15] = cb3[3]; + block[16] = cb0[4]; block[17] = cb1[4]; + block[18] = cb2[4]; block[19] = cb3[4]; + block[20] = cb0[5]; block[21] = cb1[5]; + block[22] = cb2[5]; block[23] = cb3[5]; + } + mask >>= 1; + put_block(&block, x, y, frm); + x += 4; + if x >= xend { + x = xoff; + y += 4; + if y == yend { + return Ok(()); + } + } + } + } + Ok(()) + } +} + +impl NADecoder for CinepakDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + let w = vinfo.get_width(); + let h = vinfo.get_height(); + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(w, h, false, YUV420_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, None).into_ref(); + self.frmmgr.clear(); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + if src.len() <= 10 { return Err(DecoderError::ShortData); } + + let mut mr = MemoryReader::new_read(src.as_slice()); + let mut br = ByteReader::new(&mut mr); + + let flags = br.read_byte()?; + let size = br.read_u24be()? as usize; + validate!(src.len() >= size); + let width = br.read_u16be()? as usize; + let height = br.read_u16be()? as usize; + let nstrips = br.read_u16be()? as usize; + + let is_intra = (flags & 1) == 0; + + if let Some(ref vinfo) = self.info.get_properties().get_video_info() { + if vinfo.width != width || vinfo.height != height { + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(width, height, false, YUV420_FORMAT)); + self.info = NACodecInfo::new_ref(self.info.get_name(), myinfo, None).into_ref(); + self.frmmgr.clear(); + } + } + let mut buf; + if is_intra { + let vinfo = self.info.get_properties().get_video_info().unwrap(); + let bufinfo = alloc_video_buffer(vinfo, 2)?; + buf = bufinfo.get_vbuf().unwrap(); + } else { + let bufret = self.frmmgr.clone_ref(); + if let Some(vbuf) = bufret { + buf = vbuf; + } else { + return Err(DecoderError::MissingReference); + } + } + let mut frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + + let mut last_y = 0; + for i in 0..nstrips { + let flags = br.read_byte()?; + validate!(flags == 0x10 || flags == 0x11); + let is_intra_strip = (flags & 1) == 0; + let size = br.read_u24be()? as usize; + validate!(size > 12 && (size - 4) <= (br.left() as usize)); + let yoff = br.read_u16be()? as usize; + let xoff = br.read_u16be()? as usize; + if xoff != 0 || yoff != 0 { + return Err(DecoderError::NotImplemented); + } + let yend = br.read_u16be()? as usize; + let xend = br.read_u16be()? as usize; + if i == 0 && is_intra && !is_intra_strip { + return Err(DecoderError::InvalidData); + } + let start = br.tell() as usize; + let end = start + size - 12; + let strip_data = &src[start..end]; + self.decode_strip(strip_data, is_intra, is_intra_strip, 0, last_y, xend, last_y + yend, &mut frm)?; + br.read_skip(size - 12)?; + last_y += yend; + } + + self.frmmgr.add_frame(buf.clone()); + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), NABufferType::Video(buf)); + 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) { + self.frmmgr.clear(); + } +} + +pub fn get_decoder() -> Box { + Box::new(CinepakDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::generic_register_all_codecs; + use crate::generic_register_all_demuxers; + #[test] + fn test_cinepak() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + generic_register_all_codecs(&mut dec_reg); + test_decoding("avi", "cinepak", "assets/Misc/ot171.avi", Some(10), &dmx_reg, + &dec_reg, ExpectedTestResult::MD5Frames(vec![ + [0xd58326b0, 0xdbfc1dcc, 0x6d66a04c, 0x08a21bbb], + [0x9b2cb5c5, 0x69b5f261, 0xcaccaaaf, 0xff2a807d], + [0x55c322d5, 0xf76f81ce, 0x923ada8c, 0x4925a5c8], + [0x2d1a537a, 0x62233cb6, 0xc1d39c2f, 0xeec9ccf3], + [0xf3cc841d, 0x56603c01, 0x34f521cf, 0x61f8a0c9], + [0xd75c0802, 0x9e786186, 0xc7a05cdf, 0x52ddc59d], + [0xde19733b, 0x29633d17, 0x507e9f82, 0x94c09158], + [0x1ea11919, 0x133a282c, 0x8cee485c, 0x150cb3f4], + [0x55a6d8fb, 0x2ea287c0, 0x36b3083b, 0x954cfc64], + [0xfb8be1fb, 0x84ad10aa, 0xa00ee55c, 0x9e191e5b], + [0x9c090a08, 0x43071726, 0x26236b5a, 0x79595848]])); + } + #[test] + fn test_cinepak_gray() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + generic_register_all_codecs(&mut dec_reg); + test_decoding("mov", "cinepak", "assets/Misc/dday.mov", Some(10), &dmx_reg, + &dec_reg, ExpectedTestResult::MD5Frames(vec![ + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x75d4d701, 0x897b4a37, 0xdc2bfb95, 0x3c8871a5], + [0x4c67ee48, 0xbea36f9c, 0xde61338b, 0xec36cc90]])); + } +} + diff --git a/nihav-commonfmt/src/codecs/mod.rs b/nihav-commonfmt/src/codecs/mod.rs index 1421613..9e2a094 100644 --- a/nihav-commonfmt/src/codecs/mod.rs +++ b/nihav-commonfmt/src/codecs/mod.rs @@ -4,6 +4,8 @@ macro_rules! validate { ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(DecoderError::InvalidData); } }; } +#[cfg(feature="decoder_cinepak")] +mod cinepak; #[cfg(feature="decoder_clearvideo")] mod clearvideo; @@ -19,6 +21,8 @@ mod sipro; mod ts102366; const DECODERS: &[DecoderInfo] = &[ +#[cfg(feature="decoder_cinepak")] + DecoderInfo { name: "cinepak", get_decoder: cinepak::get_decoder }, #[cfg(feature="decoder_clearvideo")] DecoderInfo { name: "clearvideo", get_decoder: clearvideo::get_decoder }, #[cfg(feature="decoder_clearvideo")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 628cc6b..6ae8528 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -172,6 +172,8 @@ static CODEC_REGISTER: &'static [CodecDescription] = &[ desc!(audio; "atrac3", "Sony Atrac3"), desc!(audio; "sipro", "Sipro Labs ADPCM"), + desc!(video; "cinepak", "Cinepak"), + desc!(video; "truemotion1", "TrueMotion 1"), desc!(video-im; "truemotionrt", "TrueMotion RT"), desc!(video; "truemotion2", "TrueMotion 2"), @@ -229,6 +231,7 @@ static AVI_VIDEO_CODEC_REGISTER: &'static [(&[u8;4], &str)] = &[ (b"I263", "intel263"), (b"UCOD", "clearvideo"), + (b"cvid", "cinepak"), (b"MVDV", "midivid"), (b"MV30", "midivid3"), -- 2.39.5