From: Kostya Shishkov Date: Wed, 16 Sep 2026 14:31:27 +0000 (+0200) Subject: 8BPS decoder support X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=70f7513fc527a658473c20e7e4c480350b5b6c90;p=nihav.git 8BPS decoder support --- diff --git a/nihav-commonfmt/src/muxers/mov/videotrack.rs b/nihav-commonfmt/src/muxers/mov/videotrack.rs index 9026cd9..7bbf9a0 100644 --- a/nihav-commonfmt/src/muxers/mov/videotrack.rs +++ b/nihav-commonfmt/src/muxers/mov/videotrack.rs @@ -97,6 +97,7 @@ impl TrackHandler for VideoTrackHandler { bw.write_u16be(1)?; // frame count let name: &[u8] = match &self.fcc { b"raw " | b"j420" => b"Raw video", + b"8BPS" => b"Planar RGB", b"jpeg" => b"Motion JPEG", b"mjpa" => b"Motion JPEG A", b"mjpb" => b"Motion JPEG B", diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index 1b3a486..12a9259 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -27,6 +27,7 @@ all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] all_video_decoders = [ + "decoder_8bps", "decoder_rle", "decoder_smc", "decoder_rpza", @@ -37,6 +38,7 @@ all_video_decoders = [ "decoder_rawvid", "decoder_cdvideo" ] +decoder_8bps = ["decoders"] decoder_rle = ["decoders"] decoder_smc = ["decoders"] decoder_rpza = ["decoders"] diff --git a/nihav-qt/src/codecs/eightbps.rs b/nihav-qt/src/codecs/eightbps.rs new file mode 100644 index 0000000..d526ff4 --- /dev/null +++ b/nihav-qt/src/codecs/eightbps.rs @@ -0,0 +1,187 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +pub const ARGB_FORMAT: NAPixelFormaton = NAPixelFormaton { + model: ColourModel::RGB(RGBSubmodel::RGB), components: 4, + comp_info: [ + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 0, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 1, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 2, next_elem: 4 }), + Some(NAPixelChromaton{ h_ss: 0, v_ss: 0, packed: true, depth: 8, shift: 0, comp_offs: 3, next_elem: 4 }), + None ], + elem_size: 4, be: false, alpha: true, palette: false }; + +#[derive(Default)] +struct PlanarRGBDecoder { + info: NACodecInfoRef, + width: usize, + height: usize, + depth: u8, +} + +struct Frame<'a> { + data: &'a mut [u8], + width: usize, + height: usize, + stride: usize, + step: usize, +} + +fn decode_plane(line_sizes: &[u8], src: &[u8], offset: usize, frm: &mut Frame, plane: usize) -> DecoderResult { + let mut read_pos = offset; + for (linesz, line) in line_sizes.chunks_exact(2) + .zip(frm.data.chunks_exact_mut(frm.stride).take(frm.height)) { + let size = usize::from(read_u16be(linesz).unwrap_or_default()); + validate!(read_pos + size <= src.len()); + let mut br = MemoryReader::new_read(&src[read_pos..][..size]); + read_pos += size; + + let mut pos = 0; + let mut off = plane; + while pos < frm.width { + let op = usize::from(br.read_byte()?); + if op < 0x80 { + let len = op + 1; + validate!(pos + len <= frm.width); + for _ in 0..len { + line[off] = br.read_byte()?; + off += frm.step + } + pos += len; + } else { + let len = 257 - op; + validate!(pos + len <= frm.width); + let pix = br.read_byte()?; + for _ in 0..len { + line[off] = pix; + off += frm.step + } + pos += len; + } + } + } + Ok(read_pos) +} + +impl NADecoder for PlanarRGBDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Video(vinfo) = info.get_properties() { + self.width = vinfo.get_width(); + self.height = vinfo.get_height(); + self.depth = vinfo.bits; + let fmt = match self.depth { + 8 => PAL8_FORMAT, + 24 => RGB24_FORMAT, + 32 => ARGB_FORMAT, + _ => return Err(DecoderError::InvalidData), + }; + let myinfo = NACodecTypeInfo::Video(NAVideoInfo::new(self.width, self.height, false, fmt)); + 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 planes = usize::from(self.depth / 8); + + let bufinfo = alloc_video_buffer(self.info.get_properties().get_video_info().unwrap(), 0)?; + let mut buf = bufinfo.get_vbuf().unwrap(); + let frm = NASimpleVideoFrame::from_video_buf(&mut buf).unwrap(); + + let (size_data, rle_data) = src.split_at(self.height * 2 * planes); + let mut pos = 0; + let mut fdata = Frame { + data: frm.data, + width: self.width, + height: self.height, + stride: frm.stride[0], + step: planes, + }; + for (planeno, line_sizes) in size_data.chunks_exact(self.height * 2).enumerate() { + pos = decode_plane(line_sizes, rle_data, pos, &mut fdata, planeno)?; + } + + if self.depth == 8 { + let paloff = frm.offset[1]; + let dpal = &mut frm.data[paloff..]; + for sd in pkt.side_data.iter() { + match *sd { + NASideData::Palette(_, ref pal) => { + for (dst, src) in dpal.chunks_mut(3).zip(pal.chunks(4)) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + break; + }, + _ => {}, + }; + } + } + + let mut frm = NAFrame::new_from_pkt(pkt, self.info.clone(), bufinfo); + frm.set_keyframe(true); + frm.set_frame_type(FrameType::I); + Ok(frm.into_ref()) + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for PlanarRGBDecoder { + 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::::default() +} + +#[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 from https://samples.mplayerhq.hu/V-codecs/8BPS-PlanarRGB/ + #[test] + fn test_8bps_pal() { + 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", "8bps", "assets/QT/full9ironswing.mov", Some(0), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xa0607989, 0xf75365ee, 0x96d9bed8, 0x49f92014]])); + } + #[test] + fn test_8bps_rgb24() { + 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", "8bps", "assets/QT/aletrek-8BPS.mov", Some(1), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0x4782f1e2, 0x88ef50e2, 0x3525a06e, 0xac5def8d]])); + } + #[test] + fn test_8bps_rgb32() { + 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", "8bps", "assets/QT/8bpsbgra.mov", Some(1), &dmx_reg, &dec_reg, + ExpectedTestResult::MD5Frames(vec![ + [0xa578ced2, 0x6e480a5c, 0x14b3a859, 0x465a1cdd], + [0xa2db454b, 0x8d4130c9, 0x743fb0a8, 0x63766105]])); + } +} diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index 7e2f485..2cd9975 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -9,6 +9,9 @@ macro_rules! validate { ($a:expr) => { if !$a { return Err(DecoderError::InvalidData); } }; } +#[cfg(feature="decoder_8bps")] +mod eightbps; + #[cfg(feature="decoder_qdraw")] mod qdraw; @@ -75,6 +78,8 @@ mod qdm2qmf; mod qdm2; const QT_CODECS: &[DecoderInfo] = &[ +#[cfg(feature="decoder_8bps")] + DecoderInfo { name: "8bps", get_decoder: eightbps::get_decoder }, #[cfg(feature="decoder_qdraw")] DecoderInfo { name: "qdraw", get_decoder: qdraw::get_decoder }, #[cfg(feature="decoder_rle")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index e6932ea..b0aa91a 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -198,6 +198,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(audio; "ms-adpcm", "MS ADPCM"), desc!(audio; "ima-adpcm-ms", "IMA ADPCM (MS variant)"), + desc!(video-ll; "8bps", "Planar RGB"), desc!(video; "qt-smc", "Apple Graphics"), desc!(video; "qt-rle", "Apple Animation"), desc!(video; "apple-video", "Apple video"), @@ -457,6 +458,7 @@ static WAV_CODEC_REGISTER: &[(u16, &str)] = &[ ]; static MOV_VIDEO_CODEC_REGISTER: &[(&[u8;4], &str)] = &[ + (b"8BPS", "8bps"), (b"cvid", "cinepak"), (b"jpeg", "jpeg"), (b"raw ", "rawvideo"),