From: Kostya Shishkov Date: Fri, 5 Sep 2025 17:04:34 +0000 (+0200) Subject: add a semblance of support for Intel DVI ADPCM X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=84227adb89877182c6c6b4902af5967c8406c271;p=nihav.git add a semblance of support for Intel DVI ADPCM --- diff --git a/nihav-indeo/Cargo.toml b/nihav-indeo/Cargo.toml index 9ded2a7..49b9f73 100644 --- a/nihav-indeo/Cargo.toml +++ b/nihav-indeo/Cargo.toml @@ -19,9 +19,10 @@ default = ["all_decoders", "all_demuxers", "all_encoders"] all_decoders = ["all_video_decoders", "all_audio_decoders"] all_video_decoders = ["decoder_indeo2", "decoder_indeo3", "decoder_indeo4", "decoder_indeo5", "decoder_intel263", "decoder_yv92"] -all_audio_decoders = ["decoder_imc"] +all_audio_decoders = ["decoder_dvi_adpcm", "decoder_imc"] decoders = [] +decoder_dvi_adpcm = ["decoders"] decoder_imc = ["decoders"] decoder_indeo2 = ["decoders"] decoder_indeo3 = ["decoders"] diff --git a/nihav-indeo/src/codecs/dviadpcm.rs b/nihav-indeo/src/codecs/dviadpcm.rs new file mode 100644 index 0000000..16d6156 --- /dev/null +++ b/nihav-indeo/src/codecs/dviadpcm.rs @@ -0,0 +1,167 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; +use nihav_codec_support::codecs::imaadpcm::*; +use std::str::FromStr; + +struct DVIADPCMDecoder { + ainfo: NAAudioInfo, + chmap: NAChannelMap, + ch_state: [IMAState; 2], +} + +impl DVIADPCMDecoder { + fn new() -> Self { + Self { + ainfo: NAAudioInfo::new(0, 1, SND_S16P_FORMAT, 0), + chmap: NAChannelMap::new(), + ch_state: [IMAState::new(), IMAState::new()], + } + } +} + +fn expand_word(dst: &mut [i16], mut word: u16, state: &mut IMAState) { + for dsamp in dst.iter_mut().take(4) { + *dsamp = state.expand_sample((word >> 12) as u8); + word <<= 4; + } +} + +impl NADecoder for DVIADPCMDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { + let channels = ainfo.get_channels() as usize; + validate!(channels == 2 || channels == 1); + self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels as u8, SND_S16P_FORMAT, 1); + self.chmap = NAChannelMap::from_str(if channels == 1 { "C" } else { "L,R" }).unwrap(); + Ok(()) + } else { + Err(DecoderError::InvalidData) + } + } + fn decode(&mut self, _supp: &mut NADecoderSupport, pkt: &NAPacket) -> DecoderResult { + let info = pkt.get_stream().get_info(); + if let NACodecTypeInfo::Audio(_) = info.get_properties() { + let src = pkt.get_buffer(); + let channels = self.chmap.num_channels(); + validate!(src.len() >= 16); + let _frameno = read_u32le(&src)?; + let _time = read_u32le(&src[4..])?; + let size = read_u32le(&src[12..])? as usize; + validate!(src.len() == size + 16); + + if size == 0 { + let len = 22050 / 30; + let abuf = alloc_audio_buffer(self.ainfo, len, self.chmap.clone())?; + let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf); + frm.set_duration(Some(len as u64)); + frm.set_keyframe(false); + return Ok(frm.into_ref()); + } + + validate!(size > 24); + let abuf; + let nsamples; + if src[18] == 0xFF { + let size = read_u16le(&src[16..])? as usize; + validate!(size + 24 == src.len()); + let id = src[19]; + validate!((channels == 1 && id == 1) || (channels == 2 && id == 3)); + + const BLOCK_SAMPLES: usize = 61; + const BLOCK_SIZE: usize = 32; + nsamples = (src.len() - 24) / BLOCK_SIZE * BLOCK_SAMPLES; + abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?; + let mut adata = abuf.get_abuf_i16().unwrap(); + //let mut off = [adata.get_offset(0), adata.get_offset(1)]; + let dst = adata.get_data_mut().unwrap(); + + if channels == 1 { + for (dblk, block) in dst.chunks_exact_mut(BLOCK_SAMPLES).zip(src[24..].chunks_exact(BLOCK_SIZE)) { + expand_word(&mut dblk[..4], read_u16le(block).unwrap_or_default(), &mut self.ch_state[0]); + let init = read_u16le(&block[2..]).unwrap_or_default(); + let pred = (init as i16) & !0x7F; + let step = (init & 0x7F) as u8; + validate!(step <= IMA_MAX_STEP); + dblk[4] = pred; + self.ch_state[0].reset(pred, step); + for (quad, w) in dblk[5..].chunks_exact_mut(4).zip(block[4..].chunks_exact(2)) { + let word = read_u16le(w).unwrap_or_default(); + expand_word(quad, word, &mut self.ch_state[0]); + } + } + } else { + return Err(DecoderError::NotImplemented); + } + } else { // this is a guesswork but it's better than nothing + nsamples = (src.len() - 20) * 2 + 1; + abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?; + let mut adata = abuf.get_abuf_i16().unwrap(); + //let mut off = [adata.get_offset(0), adata.get_offset(1)]; + let dst = adata.get_data_mut().unwrap(); + + if channels == 1 { + let step = src[16]; + let pred = read_u16le(&src[18..]).unwrap_or_default() as i16; + validate!(step <= IMA_MAX_STEP); + dst[0] = pred; + self.ch_state[0].reset(pred, step); + for (pair, &b) in dst[1..].chunks_exact_mut(2).zip(src[20..].iter()) { + pair[0] = self.ch_state[0].expand_sample(b & 0xF); + pair[1] = self.ch_state[0].expand_sample(b >> 4); + } + } else { + return Err(DecoderError::NotImplemented); + } + } + + let mut frm = NAFrame::new_from_pkt(pkt, info.replace_info(NACodecTypeInfo::Audio(self.ainfo)), abuf); + frm.set_duration(Some(nsamples as u64)); + frm.set_keyframe(false); + Ok(frm.into_ref()) + } else { + Err(DecoderError::InvalidData) + } + } + fn flush(&mut self) { + } +} + +impl NAOptionHandler for DVIADPCMDecoder { + 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(DVIADPCMDecoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::RegisteredDecoders; + use nihav_core::demuxers::RegisteredDemuxers; + use nihav_codec_support::test::dec_video::*; + use crate::*; + #[test] + fn test_dvi_adpcm_old() { + let mut dmx_reg = RegisteredDemuxers::new(); + indeo_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + indeo_register_all_decoders(&mut dec_reg); + + // sample from IBM CD Showcase + test_decoding("dvi", "dvi-adpcm", "assets/Indeo/video.avs", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0xfd1f6ee5, 0xf9e2c670, 0xfa51bc92, 0x70a2e488])); + } + #[test] + fn test_dvi_adpcm_new() { + let mut dmx_reg = RegisteredDemuxers::new(); + indeo_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + indeo_register_all_decoders(&mut dec_reg); + + // sample from D\Vision PRO v2.1 + test_decoding("dvi", "dvi-adpcm", "assets/Indeo/AUDM400.AVS", None, &dmx_reg, &dec_reg, + ExpectedTestResult::MD5([0x8dc1e085, 0x1e51e54f, 0x562f9add, 0x275a9c6f])); + } +} diff --git a/nihav-indeo/src/codecs/mod.rs b/nihav-indeo/src/codecs/mod.rs index 4e764b0..b0e6c37 100644 --- a/nihav-indeo/src/codecs/mod.rs +++ b/nihav-indeo/src/codecs/mod.rs @@ -36,6 +36,9 @@ mod ivibr; #[allow(clippy::too_many_arguments)] mod ividsp; +#[cfg(feature="decoder_dvi_adpcm")] +mod dviadpcm; + #[cfg(feature="decoder_imc")] #[allow(clippy::excessive_precision)] mod imc; @@ -58,6 +61,8 @@ const INDEO_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_yv92")] DecoderInfo { name: "yv92", get_decoder: yv92::get_decoder }, +#[cfg(feature="decoder_dvi_adpcm")] + DecoderInfo { name: "dvi-adpcm", get_decoder: dviadpcm::get_decoder }, #[cfg(feature="decoder_imc")] DecoderInfo { name: "imc", get_decoder: imc::get_decoder_imc }, #[cfg(feature="decoder_imc")] diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index e19668f..894e475 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -166,6 +166,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(video-im; "yv92", "Intel Indeo YVU9 Compressed"), desc!(audio; "iac", "Intel Indeo audio"), desc!(audio; "imc", "Intel Music Coder"), + desc!(audio; "dvi-adpcm", "Intel DVI ADPCM"), desc!(video; "realvideo1", "Real Video 1"), desc!(video; "realvideo2", "Real Video 2", CODEC_CAP_REORDER),