]> git.nihav.org Git - nihav.git/commitdiff
QT CD Video codec
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 19 Apr 2026 17:00:34 +0000 (19:00 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sun, 19 Apr 2026 17:00:34 +0000 (19:00 +0200)
nihav-qt/Cargo.toml
nihav-qt/src/codecs/cdvideo.rs [new file with mode: 0644]
nihav-qt/src/codecs/mod.rs
nihav-registry/src/register.rs

index c80a168e2895b55fdd92a8554a5163f35da386cc..f30b16d46f3b0bbc050384cd1b1adde300f35620 100644 (file)
@@ -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 (file)
index 0000000..477ab40
--- /dev/null
@@ -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<u8>,
+    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<NAFrameRef> {
+        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<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+    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]]));
+    }
+}
index b75a4c01602e967e78ede1281cb7ddc13b3ed25d..4685d2b83b5d831801ab59a4d4eee3cadc015431 100644 (file)
@@ -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")]
index 81182bdc28ee41a667851b419359212f95c65f5b..2d0a1be4cde169693cfbcaf0c7c903effb755983 100644 (file)
@@ -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"),