From: Kostya Shishkov Date: Sun, 19 Apr 2026 17:00:34 +0000 (+0200) Subject: QT CD Video codec X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=b74889e596ca403985437c02325d32f7efbb9673;p=nihav.git QT CD Video codec --- diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index c80a168..f30b16d 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -20,7 +20,7 @@ default = ["all_decoders", "all_demuxers", "all_encoders"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] -all_video_decoders = ["decoder_rle", "decoder_smc", "decoder_rpza", "decoder_qdraw", "decoder_svq1", "decoder_svq3", "decoder_rawvid"] +all_video_decoders = ["decoder_rle", "decoder_smc", "decoder_rpza", "decoder_qdraw", "decoder_svq1", "decoder_svq3", "decoder_rawvid", "decoder_cdvideo"] decoder_rle = ["decoders"] decoder_smc = ["decoders"] decoder_rpza = ["decoders"] @@ -28,6 +28,7 @@ decoder_qdraw = ["decoders"] decoder_svq1 = ["decoders"] decoder_svq3 = ["decoders"] decoder_rawvid = ["decoders"] +decoder_cdvideo = ["decoders"] all_audio_decoders = ["decoder_ima_adpcm_qt", "decoder_mace", "decoder_qcelp", "decoder_qdm", "decoder_qdm2", "decoder_alac"] decoder_ima_adpcm_qt = ["decoders"] diff --git a/nihav-qt/src/codecs/cdvideo.rs b/nihav-qt/src/codecs/cdvideo.rs new file mode 100644 index 0000000..477ab40 --- /dev/null +++ b/nihav-qt/src/codecs/cdvideo.rs @@ -0,0 +1,221 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use nihav_codec_support::codecs::HAMShuffler; + +#[derive(Debug,PartialEq)] +enum Mode { + None, + V4(u8), + V1(u8), + Skip(u8), +} + +struct CDVideoDecoder { + info: NACodecInfoRef, + hams: HAMShuffler, + width: usize, + height: usize, + cb: [[[u8; 3]; 4]; 256], +} + +impl CDVideoDecoder { + fn new() -> Self { + Self { + info: NACodecInfoRef::default(), + hams: HAMShuffler::default(), + width: 0, + height: 0, + cb: [[[0; 3]; 4]; 256], + } + } +} + +fn yuv2rgb(rgb: &mut [u8; 3], y: u8, u: u8, v: u8) { + let y = i16::from(y); + let red = y + i16::from(u as i8) * 2; + let blue = y + i16::from(v as i8) * 2; + let green = y * 2 - (red >> 1) - (blue >> 1); + *rgb = [red.clamp(0, 255) as u8, green.clamp(0, 255) as u8, blue.clamp(0, 255) as u8]; +} + +impl NADecoder for CDVideoDecoder { + 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, RGB24_FORMAT)); + self.info = NACodecInfo::new_ref(info.get_name(), myinfo, info.get_extradata()).into_ref(); + + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + #[allow(clippy::cognitive_complexity)] + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let src = pkt.get_buffer(); + validate!(src.len() > 0x630); + let mut br = MemoryReader::new_read(src.as_slice()); + + let hdr = br.read_u32be()?; + if hdr == 22222222 { + br.read_u16be()?; + let to_skip = br.read_u16be()?; + validate!(to_skip >= 8); + br.read_skip(usize::from(to_skip - 4))?; + } + let _size = br.read_u32be()?; + br.read_u32be()?; + br.read_u32be()?; + let is_yuv = br.read_u32be()? > 0; + let cb_size = br.read_u32be()? as usize; + if is_yuv { + validate!((cb_size % 6) == 0 && (6..=0x600).contains(&cb_size)); + let mut yuv = [0; 6]; + for cb in self.cb.iter_mut().take(cb_size / 6) { + br.read_buf(&mut yuv)?; + for (clr, &y) in cb.iter_mut().zip(yuv[2..].iter()) { + yuv2rgb(clr, y, yuv[0], yuv[1]); + } + } + } else { + validate!((cb_size % 12) == 0 && (3..=0xC00).contains(&cb_size)); + for cb in self.cb.iter_mut().take(cb_size / 12) { + for clr in cb.iter_mut() { + br.read_buf(clr)?; + } + } + } + if (cb_size & 3) != 0 { + br.read_skip(4 - (cb_size & 3))?; + } + br.read_u32be()?; // always 3? + br.read_u32be()?; // always 0? + let width = br.read_u32be()? as usize; + let height = br.read_u32be()? as usize; + validate!(width > 0 && width <= self.width && (width & 3) == 0); + validate!(height > 0 && height <= self.height && (height & 3) == 0); + br.read_u32be()?; + br.read_u32be()?; + + let mut is_intra = width == self.width && height == self.height; + 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 mut mode = Mode::None; + for strip in frm.data[frm.offset[0]..].chunks_exact_mut(stride * 4).take(height / 4) { + for x in (0..width).step_by(4) { + if mode == Mode::None { + let op = br.read_byte()?; + let len = (op & 0x1F) + 1; + mode = match op >> 5 { + 1 => Mode::V4(len), + 6 => Mode::V1(len), + 4 => Mode::Skip(len), + _ => return Err(DecoderError::InvalidData), + }; + } + mode = match mode { + Mode::V4(len) => { + for sblk in 0..4 { + let idx = usize::from(br.read_byte()?); + let entry = &self.cb[idx]; + let dst = &mut strip[(x + (sblk & 1) * 2) * 3 + (sblk >> 1) * 2 * stride..]; + dst[..3].copy_from_slice(&entry[0]); + dst[3..][..3].copy_from_slice(&entry[1]); + dst[stride..][..3].copy_from_slice(&entry[0]); + dst[stride + 3..][..3].copy_from_slice(&entry[1]); + } + if len > 1 { + Mode::V4(len - 1) + } else { + Mode::None + } + }, + Mode::V1(len) => { + let idx = usize::from(br.read_byte()?); + let entry = &self.cb[idx]; + //xxx: the original code actually averages data here + for sblk in 0..4 { + let dst = &mut strip[(x + (sblk & 1) * 2) * 3 + (sblk >> 1) * 2 * stride..]; + dst[..3].copy_from_slice(&entry[0]); + dst[3..][..3].copy_from_slice(&entry[1]); + dst[stride..][..3].copy_from_slice(&entry[0]); + dst[stride + 3..][..3].copy_from_slice(&entry[1]); + } + if len > 1 { + Mode::V1(len - 1) + } else { + Mode::None + } + }, + Mode::Skip(len) => { + is_intra = false; + if len > 1 { + Mode::Skip(len - 1) + } else { + Mode::None + } + }, + Mode::None => unreachable!(), + }; + } + } + let buftype = NABufferType::VideoPacked(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 CDVideoDecoder { + 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(CDVideoDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::qt_register_all_decoders; + use nihav_commonfmt::generic_register_all_demuxers; + // samples is one of the navigable movies from the original QuickTime distribution + #[test] + fn test_cdvideo() { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + qt_register_all_decoders(&mut dec_reg); + + test_decoding("mov-macbin", "qt-cdvideo", "assets/QT/Golden Gate", Some(6), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xad94ad4f, 0x1b8148e5, 0x699e35ce, 0x562f1106], + [0xdbb07011, 0x605ef892, 0x166ac124, 0xe05dccea], + [0xc010996c, 0x6339c885, 0x64c6383b, 0xe1e4b7fa], + [0x27efd57d, 0xe2d95363, 0xad6b05f2, 0x812666e7], + [0x00ec0cee, 0xd9ca3b54, 0xaafa5501, 0x07dc49ef], + [0xfe3d2e1e, 0x2e0e60c5, 0x2eca8456, 0xa81b66b8], + [0x1c8dfa16, 0x86b8f0be, 0x245e994c, 0x9002edfa]])); + } +} diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index b75a4c0..4685d2b 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -21,6 +21,9 @@ mod rpza; #[cfg(feature="decoder_smc")] mod smc; +#[cfg(feature="decoder_cdvideo")] +mod cdvideo; + #[cfg(feature="decoder_svq1")] mod svq1; #[cfg(feature="decoder_svq1")] @@ -75,6 +78,8 @@ const QT_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "apple-video", get_decoder: rpza::get_decoder }, #[cfg(feature="decoder_smc")] DecoderInfo { name: "qt-smc", get_decoder: smc::get_decoder }, +#[cfg(feature="decoder_cdvideo")] + DecoderInfo { name: "qt-cdvideo", get_decoder: cdvideo::get_decoder }, #[cfg(feature="decoder_svq1")] DecoderInfo { name: "sorenson-video", get_decoder: svq1::get_decoder }, #[cfg(feature="decoder_svq3")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 81182bd..2d0a1be 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -204,6 +204,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video; "qdraw", "Apple QuickDraw"), desc!(video; "sorenson-video", "Sorenson Video"), desc!(video; "sorenson-video3", "Sorenson Video 3", CODEC_CAP_REORDER), + desc!(video; "qt-cdvideo", "CD Video Codec"), desc!(video-ll; "qt-yuv2", "Raw YUV"), desc!(video-ll; "qt-yuv4", "libquicktime YUV4"), desc!(audio-ll; "alac", "Apple Lossless Audio Codec"), @@ -452,6 +453,7 @@ static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ (b"rle ", "qt-rle"), (b"rpza", "apple-video"), (b"qdrw", "qdraw"), + (b"cdvc", "qt-cdvideo"), (b"kpcd", "kodak-photocd"), //(b"mpeg", "mpeg-video"), (b"mjpa", "mjpeg-a"),