From: Kostya Shishkov Date: Wed, 15 Jul 2026 13:24:19 +0000 (+0200) Subject: add decoder for Pixar QuickTime codecs X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=0dc4130a167c559fa119c2003f00bd5c362da3ff;p=nihav.git add decoder for Pixar QuickTime codecs --- diff --git a/nihav-misc/Cargo.toml b/nihav-misc/Cargo.toml index 27cf26c..8dd6f43 100644 --- a/nihav-misc/Cargo.toml +++ b/nihav-misc/Cargo.toml @@ -25,7 +25,7 @@ encoders = [] all_decoders = ["all_video_decoders", "all_audio_decoders"] -all_video_decoders = ["decoder_adorage", "decoder_arbc", "decoder_fif", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] +all_video_decoders = ["decoder_adorage", "decoder_arbc", "decoder_fif", "decoder_moviepak", "decoder_mvi", "decoder_mwv1", "decoder_pgvv", "decoder_pivc", "decoder_pixar", "decoder_qpeg", "decoder_tealvid", "decoder_ultimotion"] decoder_adorage = ["decoders"] decoder_arbc = ["decoders"] decoder_fif = ["decoders"] @@ -34,6 +34,7 @@ decoder_mvi = ["decoders"] decoder_mwv1 = ["decoders"] decoder_pgvv = ["decoders"] decoder_pivc = ["decoders"] +decoder_pixar = ["decoders"] decoder_qpeg = ["decoders"] decoder_tealvid = ["decoders"] decoder_ultimotion = ["decoders"] diff --git a/nihav-misc/src/codecs/mod.rs b/nihav-misc/src/codecs/mod.rs index fb23d9e..6272570 100644 --- a/nihav-misc/src/codecs/mod.rs +++ b/nihav-misc/src/codecs/mod.rs @@ -33,6 +33,9 @@ mod pgvv; #[cfg(feature="decoder_pivc")] mod pivc; +#[cfg(feature="decoder_pixar")] +mod pixar; + #[cfg(feature="decoder_qpeg")] mod qpeg; @@ -67,6 +70,18 @@ const DECODERS: &[DecoderInfo] = &[ DecoderInfo { name: "pgvv", get_decoder: pgvv::get_decoder }, #[cfg(feature="decoder_pivc")] DecoderInfo { name: "pivideo", get_decoder: pivc::get_decoder }, +#[cfg(feature="decoder_pixar")] + DecoderInfo { name: "pixar-0", get_decoder: pixar::get_decoder_0 }, +#[cfg(feature="decoder_pixar")] + DecoderInfo { name: "pixar-3", get_decoder: pixar::get_decoder_3 }, +#[cfg(feature="decoder_pixar")] + DecoderInfo { name: "pixar-4", get_decoder: pixar::get_decoder_4 }, +#[cfg(feature="decoder_pixar")] + DecoderInfo { name: "pixar-5", get_decoder: pixar::get_decoder_5 }, +//#[cfg(feature="decoder_pixar")] +// DecoderInfo { name: "pixar-7", get_decoder: pixar::get_decoder_7 }, +#[cfg(feature="decoder_pixar")] + DecoderInfo { name: "pixar-a", get_decoder: pixar::get_decoder_a }, #[cfg(feature="decoder_qpeg")] DecoderInfo { name: "qpeg-dvc", get_decoder: qpeg::get_decoder_dvc }, #[cfg(feature="decoder_tealvid")] diff --git a/nihav-misc/src/codecs/pixar.rs b/nihav-misc/src/codecs/pixar.rs new file mode 100644 index 0000000..f52e82c --- /dev/null +++ b/nihav-misc/src/codecs/pixar.rs @@ -0,0 +1,391 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use nihav_codec_support::codecs::HAMShuffler; + +#[derive(Default)] +struct Pixar0Decoder { + info: NACodecInfoRef, + width: usize, + height: usize, +} + +impl Pixar0Decoder { + fn new() -> Self { + Self::default() + } +} + +impl NADecoder for Pixar0Decoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = (vinfo.get_width() + 3) & !3; + self.height = (vinfo.get_height() + 3) & !3; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT)); + 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 { + let src = pkt.get_buffer(); + validate!(src.len() == self.width * self.height); + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 2)?; + let mut buf = bufinfo.get_vbuf().unwrap(); + + let frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + let stride = frm.stride[0]; + let doff = frm.offset[0]; + + let mut tiles = src.chunks_exact(16); + for row in frm.data[doff..].chunks_exact_mut(stride * 4).take(frm.height[0] / 4) { + for x in (0..self.width).step_by(4) { + let tile = tiles.next().unwrap(); + for (dline, sline) in row.chunks_exact_mut(stride).zip(tile.chunks_exact(4)) { + dline[x..][..4].copy_from_slice(sline); + } + } + } + let dpal = &mut frm.data[frm.offset[1]..]; + for sd in pkt.side_data.iter() { + if let NASideData::Palette(_, ref pal) = sd { + for (dst, src) in dpal.chunks_mut(3).zip(pal.chunks(4)) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } + } + + let buftype = NABufferType::Video(buf); + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buftype); + frm.set_keyframe(true); + frm.set_frame_type(FrameType::I); + Ok(frm.into_ref()) + } + fn flush(&mut self) {} +} + +impl NAOptionHandler for Pixar0Decoder { + 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 PixarDecoder { + info: NACodecInfoRef, + hams: HAMShuffler, + width: usize, + height: usize, + tiles: Vec<[u8; 16]>, + version: u8, +} + +impl PixarDecoder { + fn new(version: u8) -> Self { + Self { version, tiles: vec![[0; 16]; 65535], ..Default::default() } + } +} + +impl NADecoder for PixarDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = (vinfo.get_width() + 3) & !3; + self.height = (vinfo.get_height() + 3) & !3; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, PAL8_FORMAT)); + 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 { + let src = pkt.get_buffer(); + validate!(src.len() >= 2); + let mut br = MemoryReader::new_read(src.as_slice()); + + if self.version == 5 { + let _frameno = br.read_u16be()?; + let tot_tiles = usize::from(br.read_u16be()?); + if tot_tiles != 0 && tot_tiles != 0xFFFF { + br.read_u16be()?; + br.read_u16be()?; + } + } + + let ntiles = usize::from(br.read_u16be()?); + for _ in 0..ntiles { + let id = usize::from(br.read_u16be()?); + br.read_buf(&mut self.tiles[id])?; + } + + let bufret = self.hams.clone_ref(); + let mut buf; + if let Some(bbuf) = bufret { + buf = bbuf; + } else { + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 2)?; + buf = bufinfo.get_vbuf().unwrap(); + self.hams.add_frame(buf); + buf = self.hams.get_output_frame().unwrap(); + } + let frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + let stride = frm.stride[0]; + let doff = frm.offset[0]; + + let mut is_intra = true; + for row in frm.data[doff..].chunks_exact_mut(stride * 4).take(frm.height[0] / 4) { + let mut x = 0; + loop { + let op = usize::from(br.read_u16be()?); + match op { + 0 => break, + 1..=159 => { + is_intra = false; + x += op * 4; + }, + 160..=319 => { + let copy_size = (op - 159) * 4; + validate!(x + copy_size <= frm.width[0]); + for line in row.chunks_exact_mut(stride) { + for el in line[x..][..copy_size].iter_mut() { + *el = 0; + } + } + x += copy_size; + }, + _ => { + validate!(x < frm.width[0]); + let tile_idx = op - 320; + for (dline, sline) in row.chunks_exact_mut(stride) + .zip(self.tiles[tile_idx].chunks_exact(4)) { + dline[x..][..4].copy_from_slice(sline); + } + x += 4; + }, + } + } + } + + let pal_off = frm.offset[1]; + let dpal = &mut frm.data[pal_off..]; + for sd in pkt.side_data.iter() { + if let NASideData::Palette(_, ref pal) = sd { + for (dst, src) in dpal.chunks_mut(3).zip(pal.chunks(4)) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } + } + + let buftype = NABufferType::Video(buf); + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buftype); + 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.hams.clear(); + } +} + +impl NAOptionHandler for PixarDecoder { + 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 PixarADecoder { + info: NACodecInfoRef, + width: usize, + height: usize, +} + +impl PixarADecoder { + fn new() -> Self { Self::default() } +} + +impl NADecoder for PixarADecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = (vinfo.get_width() + 3) & !3; + self.height = (vinfo.get_height() + 3) & !3; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, GREY_FORMAT)); + 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 { + let src = pkt.get_buffer(); + validate!(src.len() >= 4); + let mut br = MemoryReader::new_read(src.as_slice()); + + let buftype = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 2)?; + let mut buf = buftype.get_vbuf().unwrap(); + let frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + let stride = frm.stride[0]; + let doff = frm.offset[0]; + + br.read_skip(2)?; + + while br.left() >= 2 { + let size = u64::from(br.read_u16be()?); + if size == 0 { + break; + } + validate!(size > 12); + let end = br.tell() + size - 2; + validate!(size - 2 <= (br.left() as u64)); + let ftype = br.read_u16be()?; + validate!(ftype == 0x4100); + let top = usize::from(br.read_u16be()?); + let left = usize::from(br.read_u16be()?); + let bottom = usize::from(br.read_u16be()?); + let right = usize::from(br.read_u16be()?); + validate!(bottom > top && right > left); + validate!(bottom <= self.height && right <= self.width); + + for line in frm.data[doff..].chunks_exact_mut(stride).take(bottom).skip(top) { + let mut x = 0; + loop { + let op = br.read_byte()?; + let len = usize::from(op & 0x3F) + 1; + validate!(x + len <= self.width); + match op { + 0x00 => break, + 0x01..=0x3F => {}, + 0x40..=0x7F | 0xC0..=0xFF => { + for pix in line[x..][..len].iter_mut() { + br.read_u16le()?; + *pix = 0xFF; + } + }, + _ => { + br.read_buf(&mut line[x..][..len])?; + }, + } + x += len; + } + } + validate!(br.tell() == end); + } + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), buftype); + frm.set_keyframe(true); + frm.set_frame_type(FrameType::I); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for PixarADecoder { + 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_0() -> Box { + Box::new(Pixar0Decoder::new()) +} +pub fn get_decoder_3() -> Box { + Box::new(PixarDecoder::new(3)) +} +pub fn get_decoder_4() -> Box { + Box::new(Pixar0Decoder::new()) +} +pub fn get_decoder_5() -> Box { + Box::new(PixarDecoder::new(5)) +} +pub fn get_decoder_a() -> Box { + Box::new(PixarADecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::misc_register_all_decoders; + use nihav_commonfmt::generic_register_all_demuxers; + // samples from Toy Story game + #[test] + fn test_pixar_0() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + + test_decoding("mov", "pixar-0", "assets/QT/tiplogo.m0", Some(1), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x9606ff61, 0xd359dd8a, 0xafa47809, 0x30e99eb9]])); + } + #[test] + fn test_pixar_3() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + + test_decoding("mov", "pixar-3", "assets/QT/tititle2.m3u", Some(6), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xdef765d0, 0x9e5ece3d, 0xd8b5f5b1, 0x977bf647], + [0x93fe1182, 0x8846a90b, 0xbb6f6c2c, 0x6ad2d91a], + [0xc9cbcb9f, 0x69e270a4, 0xaf029a57, 0xf2607685], + [0x87cefdc3, 0x8479612e, 0x43f19f3e, 0x476685fa], + [0x439b57bf, 0xfb6f9b75, 0xe4a9c2fc, 0xdf021c7a], + [0x717638ab, 0xa310198f, 0x5b597402, 0xcec4cdd8], + [0x14cd0745, 0x7535856c, 0xf4102662, 0x3653062b]])); + } + #[test] + fn test_pixar_4() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + + test_decoding("mov-macbin", "pixar-4", "assets/QT/mmOpen.m4", Some(1), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x78aeadb9, 0xd4314dbd, 0x5e0dde85, 0xb1aff360]])); + } + #[test] + fn test_pixar_5() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + + test_decoding("mov", "pixar-5", "assets/QT/FRARMYA1.M5", Some(6), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xe171c38d, 0xdfdc767a, 0x5594a451, 0xf6aa0748], + [0xee0bf633, 0xbd5411b0, 0x75a1ddff, 0xed3c929b], + [0x4b2069f3, 0x1532ead6, 0x2f836693, 0x868711a8], + [0x23e5764f, 0xc72b4827, 0x6baaa8ad, 0x0684228f], + [0xd6fa531e, 0xc51f4eae, 0xbf4c421a, 0x403c96ed], + [0xafd66217, 0xdc4ec112, 0xdf206f66, 0xba3d4415], + [0x6c529cc9, 0x81f1ed56, 0x80ababf2, 0x499dc64f]])); + } + #[test] + fn test_pixar_a() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + misc_register_all_decoders(&mut dec_reg); + + test_decoding("mov", "pixar-a", "assets/QT/frtkew2.ma", Some(100), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x4222e315, 0x9a964380, 0x581f2ed0, 0xc7a22942], + [0x4222e315, 0x9a964380, 0x581f2ed0, 0xc7a22942], + [0x1becb994, 0x49236ec9, 0xcfe33239, 0x43cc501e]])); + } +} diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 1e4049b..edc038a 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -216,6 +216,16 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(audio; "qdesign-music2", "QDesign Music v2"), desc!(audio; "qualcomm-purevoice", "Qualcomm PureVoice"), + desc!(video; "pixar-0", "Pixar Codec 0"), + desc!(video; "pixar-1", "Pixar Codec 1"), + desc!(video; "pixar-2", "Pixar Codec 2"), + desc!(video; "pixar-3", "Pixar Codec 3"), + desc!(video; "pixar-4", "Pixar Codec 4"), + desc!(video; "pixar-5", "Pixar Codec 5"), + desc!(video; "pixar-6", "Pixar Codec 6"), + desc!(video; "pixar-7", "Pixar Codec 7"), + desc!(video; "pixar-a", "Pixar Codec A"), + desc!(video-ll; "arm_rawvideo", "Acorn Replay Movie raw video formats"), desc!(audio; "arm_rawaudio", "Acorn Replay Movie raw audio formats"), desc!(video; "movinglines", "Acorn Moving Lines"), @@ -486,6 +496,16 @@ static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"mp4v", "mpeg4asp"), (b"avc1", "h264"), + (b"pix0", "pixar-0"), + (b"pix1", "pixar-1"), + (b"pix2", "pixar-2"), + (b"pix3", "pixar-3"), + (b"pix4", "pixar-4"), + (b"pix5", "pixar-5"), + (b"pix6", "pixar-6"), + (b"pix7", "pixar-7"), + (b"pixA", "pixar-a"), + (b"j420", "rawvideo"), (b"yuv2", "qt-yuv2"), (b"yuv4", "qt-yuv4"),