From 672bcc7f2c258cbd5e09479e3ed397f740bed47f Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Fri, 26 Apr 2024 18:19:40 +0200 Subject: [PATCH] add LinePack decoder --- nihav-acorn/Cargo.toml | 3 +- nihav-acorn/src/codecs/linepack.rs | 314 +++++++++++++++++++++++++++++ nihav-acorn/src/codecs/mod.rs | 9 + 3 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 nihav-acorn/src/codecs/linepack.rs diff --git a/nihav-acorn/Cargo.toml b/nihav-acorn/Cargo.toml index 6d85cb7..b8711cb 100644 --- a/nihav-acorn/Cargo.toml +++ b/nihav-acorn/Cargo.toml @@ -14,11 +14,12 @@ path = "../nihav-codec-support" default = ["all_decoders", "all_demuxers"] all_decoders = ["all_video_decoders"] -all_video_decoders = ["decoder_movinglines", "decoder_movingblocks"] +all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_linepack"] decoders = [] decoder_movinglines = ["decoders"] decoder_movingblocks = ["decoders"] +decoder_linepack = ["decoders"] all_demuxers = ["demuxer_armovie"] demuxers = [] diff --git a/nihav-acorn/src/codecs/linepack.rs b/nihav-acorn/src/codecs/linepack.rs new file mode 100644 index 0000000..3b4c7e9 --- /dev/null +++ b/nihav-acorn/src/codecs/linepack.rs @@ -0,0 +1,314 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +use super::RGB555_FORMAT; +use super::yuvtab::YUV2RGB; + +#[derive(Default)] +struct LinePackDecoder { + info: NACodecInfoRef, + cur_frm: Vec, + prev_frm: Vec, + width: usize, + is_yuv: bool, +} + +impl LinePackDecoder { + fn new() -> Self { Self::default() } +} + +impl NADecoder for LinePackDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, RGB555_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + self.cur_frm = vec![0; vinfo.get_width() * vinfo.get_height()]; + self.prev_frm = vec![0; vinfo.get_width() * vinfo.get_height()]; + self.width = vinfo.get_width(); + if let Some(edata) = info.get_extradata() { + for triplet in edata.windows(3) { + if triplet == b"YUV" { + self.is_yuv = true; + break; + } + } + } + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 2 && (src.len() & 1) == 0); + let mut mr = MemoryReader::new_read(&src); + let mut br = ByteReader::new(&mut mr); + + let mut is_intra = true; + let mut dpos = 0; + while dpos < self.cur_frm.len() { + let val = br.read_u16le()?; + if (val & 0x8000) == 0 { // raw pixel + self.cur_frm[dpos] = val; + dpos += 1; + } else { + let op = (val >> 12) & 7; + match op { + 0 => { // skip + let len = (val & 0xFFF) as usize; + validate!(dpos + len <= self.cur_frm.len()); + self.cur_frm[dpos..][..len].copy_from_slice(&self.prev_frm[dpos..][..len]); + dpos += len; + is_intra = false; + }, + 1 => { // motion + let dx = (( val & 7) as isize) - 4; + let dy = (((val >> 3) & 7) as isize) - 4; + let len = ((val >> 6) & 0x3F) as usize; + validate!(dpos + len <= self.cur_frm.len()); + if dx == 0 && dy == 0 { // previous line + validate!(dpos >= self.width); + for _ in 0..len { + self.cur_frm[dpos] = self.cur_frm[dpos - self.width]; + dpos += 1; + } + } else { + let offset = (dpos as isize) + dx + dy * (self.width as isize); + validate!(offset >= 0); + let offset = offset as usize; + validate!(offset + len <= self.prev_frm.len()); + self.cur_frm[dpos..][..len].copy_from_slice(&self.prev_frm[offset..][..len]); + dpos += len; + is_intra = false; + } + }, + 2 => { // run + let len = (val & 0xFFF) as usize; + validate!(dpos + len <= self.cur_frm.len()); + let pix = br.read_u16le()?; + for _ in 0..len { + self.cur_frm[dpos] = pix; + dpos += 1; + } + }, + 3 => { // raw + let len = (val & 0xFFF) as usize; + validate!(dpos + len <= self.cur_frm.len()); + for _ in 0..len { + self.cur_frm[dpos] = br.read_u16le()?; + dpos += 1; + } + }, + 4 => { // four-colour pattern + let len = (val & 0xFF) as usize; + validate!(dpos + len <= self.cur_frm.len()); + let clrs = [ + br.read_u16le()?, + br.read_u16le()?, + br.read_u16le()?, + br.read_u16le()? + ]; + let mut mask = 0; + let mut pos = 8; + + for _i in 0..len { + if pos == 8 { + mask = br.read_u16le()? as usize; + pos = 0; + } + self.cur_frm[dpos] = clrs[mask & 3]; + dpos += 1; + mask >>= 2; + pos += 1; + } + }, + 5 => { // interleaved + let len = (val & 0xFFF) as usize; + validate!(dpos + len * 2 <= self.cur_frm.len()); + let clrs = [ + br.read_u16le()?, + br.read_u16le()? + ]; + for _ in 0..len { + self.cur_frm[dpos] = clrs[0]; + dpos += 1; + self.cur_frm[dpos] = clrs[1]; + dpos += 1; + } + }, + _ => return Err(DecoderError::NotImplemented), + } + } + } + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + let mut buf = bufinfo.get_vbuf16().unwrap(); + let stride = buf.get_stride(0); + let data = buf.get_data_mut().unwrap(); + + for (dline, sline) in data.chunks_exact_mut(stride) + .zip(self.cur_frm.chunks_exact(self.width)) { + dline[..self.width].copy_from_slice(sline); + } + if self.is_yuv { + for el in data.iter_mut() { + *el = YUV2RGB[(*el as usize) & 0x7FFF]; + } + } + + std::mem::swap(&mut self.cur_frm, &mut self.prev_frm); + + 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.cur_frm.iter_mut() { + *el = 0; + } + for el in self.prev_frm.iter_mut() { + *el = 0; + } + } +} + +impl NAOptionHandler for LinePackDecoder { + 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() -> Box { + Box::new(LinePackDecoder::new()) +} + +#[derive(Default)] +struct LinePackPacketiser { + stream: Option, + buf: Vec, + end: usize, + frameno: u32, + intra: bool, + pos: usize, + img_size: usize, +} + +impl LinePackPacketiser { + fn new() -> Self { Self::default() } +} + +impl NAPacketiser for LinePackPacketiser { + fn attach_stream(&mut self, stream: NAStreamRef) { + let vinfo = stream.get_info().get_properties().get_video_info().unwrap(); + self.img_size = vinfo.width * vinfo.height; + 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() < self.end { + return Ok(None); + } + + if self.end == 0 { + self.intra = true; + self.pos = 0; + } + + while self.end + 2 <= self.buf.len() && self.pos < self.img_size { + let val = u16::from(self.buf[self.end + 1]) * 256 + u16::from(self.buf[self.end]); + self.end += 2; + + if (val & 0x8000) == 0 { + self.pos += 1; + } else { + let op = (val >> 12) & 7; + let common_len = (val & 0xFFF) as usize; + self.pos += match op { + 0 => common_len, // skip size + 1 => ((val >> 6) & 0x3F) as usize, // motion size + 2 => common_len, // run + 3 => common_len, // raw + 4 => common_len & 0xFF, // four-colour pattern + 5 => common_len * 2, // interleaved + _ => 0, // ??? + }; + self.end += match op { + 2 => 2, // run value + 3 => common_len * 2, // raw values + 4 => 8 + ((common_len & 0xFF) + 7) / 8 * 2, // 4 colours + masks + 5 => 4, // two values + _ => 0, + }; + if (op == 0) || (op == 1 && (val & 0x3F) != 0x24) || (op == 2) { + self.intra = false; + } + } + } + + if self.pos >= self.img_size && self.end <= self.buf.len() { + let mut data = Vec::with_capacity(self.end); + data.extend_from_slice(&self.buf[..self.end]); + self.buf.drain(..self.end); + let ts = NATimeInfo::new(Some(u64::from(self.frameno)), None, None, stream.tb_num, stream.tb_den); + self.end = 0; + self.frameno += 1; + + return Ok(Some(NAPacket::new(stream, ts, self.intra, data))); + } + + 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(LinePackPacketiser::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_linepack() { + 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", "linepack", "assets/Acorn/COLOURPLUS", Some(5), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x373eb9d6, 0xc52d7abd, 0xe1f3631b, 0xf509cb16], + [0x373eb9d6, 0xc52d7abd, 0xe1f3631b, 0xf509cb16], + [0x373eb9d6, 0xc52d7abd, 0xe1f3631b, 0xf509cb16], + [0x373eb9d6, 0xc52d7abd, 0xe1f3631b, 0xf509cb16], + [0x373eb9d6, 0xc52d7abd, 0xe1f3631b, 0xf509cb16], + [0x32033527, 0x3073331b, 0x83942239, 0x57f975ee]])); + } +} diff --git a/nihav-acorn/src/codecs/mod.rs b/nihav-acorn/src/codecs/mod.rs index 946ac05..e5dba8a 100644 --- a/nihav-acorn/src/codecs/mod.rs +++ b/nihav-acorn/src/codecs/mod.rs @@ -26,11 +26,17 @@ mod movinglines; #[cfg(feature="decoder_movingblocks")] mod movingblocks; +#[cfg(feature="decoder_linepack")] +mod linepack; + const ACORN_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_movinglines")] DecoderInfo { name: "movinglines", get_decoder: movinglines::get_decoder }, #[cfg(feature="decoder_movingblocks")] DecoderInfo { name: "movingblocks", get_decoder: movingblocks::get_decoder }, + +#[cfg(feature="decoder_linepack")] + DecoderInfo { name: "linepack", get_decoder: linepack::get_decoder }, ]; /// Registers all available codecs provided by this crate. @@ -45,6 +51,9 @@ const ACORN_PACKETISERS: &[PacketiserInfo] = &[ PacketiserInfo { name: "movinglines", get_packetiser: movinglines::get_packetiser }, #[cfg(feature="decoder_movingblocks")] PacketiserInfo { name: "movingblocks", get_packetiser: movingblocks::get_packetiser }, + +#[cfg(feature="decoder_linepack")] + PacketiserInfo { name: "linepack", get_packetiser: linepack::get_packetiser }, ]; /// Registers all available packetisers provided by this crate. -- 2.39.5