]> git.nihav.org Git - nihav.git/commitdiff
IotaSound support (in TCA) master
authorKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 15 Apr 2025 16:09:38 +0000 (18:09 +0200)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Tue, 15 Apr 2025 16:09:38 +0000 (18:09 +0200)
nihav-acorn/Cargo.toml
nihav-acorn/src/codecs/iotasound.rs [new file with mode: 0644]
nihav-acorn/src/codecs/mod.rs
nihav-acorn/src/demuxers/tca.rs
nihav-registry/src/register.rs

index 56ac79d9fcfd5d755cdc3fad4bcb436eb23cb98b..8ff3187fd0bdcecf369e4f944eaf1250db6326c1 100644 (file)
@@ -15,7 +15,7 @@ default = ["all_decoders", "all_demuxers", "all_packetisers"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_supermovingblocks", "decoder_linepack", "decoder_escape", "decoder_rawvideo", "decoder_euclid"]
-all_audio_decoders = ["decoder_rawaudio"]
+all_audio_decoders = ["decoder_rawaudio", "decoder_iotasound"]
 decoders = []
 
 decoder_movinglines = ["decoders"]
@@ -26,6 +26,7 @@ decoder_linepack = ["decoders"]
 decoder_rawvideo = ["decoders"]
 decoder_escape = ["decoders"]
 decoder_euclid = ["decoders"]
+decoder_iotasound = ["decoders"]
 
 decoder_rawaudio = ["decoders"]
 
diff --git a/nihav-acorn/src/codecs/iotasound.rs b/nihav-acorn/src/codecs/iotasound.rs
new file mode 100644 (file)
index 0000000..2efc141
--- /dev/null
@@ -0,0 +1,92 @@
+use nihav_core::codecs::*;
+use nihav_codec_support::codecs::imaadpcm::*;
+use std::str::FromStr;
+
+struct IotaSoundDecoder {
+    ainfo:      NAAudioInfo,
+    chmap:      NAChannelMap,
+    ch_state:   [IMAState; 2],
+}
+
+impl IotaSoundDecoder {
+    fn new() -> Self {
+        Self {
+            ainfo:      NAAudioInfo::new(8000, 1, SND_S16P_FORMAT, 0),
+            chmap:      NAChannelMap::new(),
+            ch_state:   [IMAState::new(), IMAState::new()],
+        }
+    }
+}
+
+impl NADecoder for IotaSoundDecoder {
+    fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> {
+        if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() {
+            let channels = ainfo.get_channels();
+            validate!(channels == 1 || channels == 2);
+            self.ainfo = NAAudioInfo::new(ainfo.get_sample_rate(), channels, SND_S16_FORMAT, 2);
+            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<NAFrameRef> {
+        let info = pkt.get_stream().get_info();
+        if let NACodecTypeInfo::Audio(_) = info.get_properties() {
+            let pktbuf = pkt.get_buffer();
+            let channels = self.ainfo.get_channels();
+            let nsamples = pktbuf.len() * 2 / usize::from(channels);
+            let abuf = alloc_audio_buffer(self.ainfo, nsamples, self.chmap.clone())?;
+            let mut adata = abuf.get_abuf_i16().unwrap();
+            let dst = adata.get_data_mut().unwrap();
+            let idx2 = if channels == 1 { 0 } else { 1 };
+            for (dpair, &src) in dst.chunks_exact_mut(2).zip(pktbuf.iter()) {
+                dpair[0] = self.ch_state[0].expand_sample(src >> 4);
+                dpair[1] = self.ch_state[idx2].expand_sample(src & 0xF);
+            }
+
+            let mut frm = NAFrame::new_from_pkt(pkt, info, abuf);
+            frm.set_duration(Some(nsamples as u64));
+            frm.set_keyframe(false);
+            Ok(frm.into_ref())
+        } else {
+            Err(DecoderError::InvalidData)
+        }
+    }
+    fn flush(&mut self) {
+        self.ch_state[0].reset(0, 0);
+        self.ch_state[1].reset(0, 0);
+    }
+}
+
+impl NAOptionHandler for IotaSoundDecoder {
+    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(IotaSoundDecoder::new())
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::{RegisteredDecoders, RegisteredPacketisers};
+    use nihav_core::demuxers::RegisteredRawDemuxers;
+    use nihav_codec_support::test::dec_video::*;
+    use crate::*;
+    #[test]
+    fn test_iota_sound() {
+        let mut dmx_reg = RegisteredRawDemuxers::new();
+        acorn_register_all_raw_demuxers(&mut dmx_reg);
+        let mut pkt_reg = RegisteredPacketisers::new();
+        acorn_register_all_packetisers(&mut pkt_reg);
+        let mut dec_reg = RegisteredDecoders::new();
+        acorn_register_all_decoders(&mut dec_reg);
+
+        // sample from All About Planes
+        test_decoding_raw("armovie", "iota-sound", "assets/Acorn/wessex", None,
+                          &dmx_reg, &pkt_reg, &dec_reg,
+                          ExpectedTestResult::MD5([0x19509699, 0x0a2134c9, 0xef46040e, 0xa4ccd672]));
+    }
+}
index 46495319b965678895c490f747b287a87fc97a9f..1c77ecbca45642a1ce826f5cba017f6fabee4ee9 100644 (file)
@@ -40,6 +40,8 @@ mod escape;
 
 #[cfg(feature="decoder_euclid")]
 mod euclid;
+#[cfg(feature="decoder_iotasound")]
+mod iotasound;
 
 #[cfg(feature="decoder_rawaudio")]
 mod rawaudio;
@@ -70,6 +72,8 @@ const ACORN_CODECS: &[DecoderInfo] = &[
 
 #[cfg(feature="decoder_euclid")]
     DecoderInfo { name: "euclid", get_decoder: euclid::get_decoder },
+#[cfg(feature="decoder_iotasound")]
+    DecoderInfo { name: "iota-sound", get_decoder: iotasound::get_decoder },
 
 #[cfg(feature="decoder_rawaudio")]
     DecoderInfo { name: "arm_rawaudio", get_decoder: rawaudio::get_decoder },
@@ -114,6 +118,8 @@ const ACORN_PACKETISERS: &[PacketiserInfo] = &[
 
 #[cfg(feature="decoder_euclid")]
     PacketiserInfo { name: "euclid", get_packetiser: euclid::get_packetiser },
+#[cfg(feature="decoder_iotasound")]
+    PacketiserInfo { name: "iota-sound", get_packetiser: rawaudio::get_packetiser },
 
 #[cfg(feature="packetiser_cinepak")]
     PacketiserInfo { name: "cinepak", get_packetiser: wss_packetisers::get_packetiser_cinepak },
index 8f1890da9b3f23afb15504a8d62eb2d18f895553..e35e718cab046477efe06007af3c6bfc5013237c 100644 (file)
@@ -48,6 +48,12 @@ impl DemuxerCreator for TCADemuxerCreator {
 pub(crate) struct TCACoreDemuxer {
     data_end:   u64,
     frameno:    u64,
+    video_pos:  u64,
+    sound_pos:  u64,
+    sound_end:  u64,
+    asize:      u64,
+    sblk_len:   usize,
+    audio:      bool,
 }
 
 impl TCACoreDemuxer {
@@ -78,6 +84,9 @@ impl TCACoreDemuxer {
         validate!(width > 0 && height > 0);
         validate!((width | height) & 1 == 0);
 
+        let mut sound_start = 0;
+        let mut sound_end = 0;
+
         if is_acef {
             let data_start = src.tell();
 
@@ -112,6 +121,21 @@ impl TCACoreDemuxer {
                                       src.read_skip(size - 8)?;
                             }
                         },
+                        b"SOUN" => {
+                            sound_start = src.tell();
+                            let stag        = src.read_tag()?;
+                            validate!(&stag == b"WAV1" || &stag == b"WAV2");
+                            let ssize       = src.read_u32le()?;
+                            validate!(ssize as usize <= size - 8 && ssize > 0x1C);
+                            sound_end = sound_start + u64::from(ssize);
+                                              src.read_u32le()?; // usually 1
+                                              src.read_u32le()?; // usually 20
+                                              src.read_u32le()?; // actual sound size
+                                              src.read_u32le()?; // some number, usually in 20000..30000 range
+                                              src.read_u32le()?; // usually -1
+                            sound_start = src.tell();
+                                              src.read_skip(size - 0x1C)?;
+                        },
                         _ => {
                                       src.read_skip(size - 8)?;
                         },
@@ -133,24 +157,61 @@ impl TCACoreDemuxer {
             return Err(DemuxerError::MemoryError);
         }
 
+        if sound_end > 0 {
+            let aci = NACodecTypeInfo::Audio(NAAudioInfo::new(8000, 1, SND_S16_FORMAT, 1));
+            let ainfo = NACodecInfo::new("iota-sound", aci, None);
+            let ret = strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, 8000, 0));
+            if ret.is_none() {
+                return Err(DemuxerError::MemoryError);
+            }
+            self.sound_pos = sound_start;
+            self.sound_end = sound_end;
+            self.sblk_len = (8000 * u64::from(tb_num) / u64::from(tb_den)).max(1) as usize;
+        }
+
+        self.audio = false;
+        self.video_pos = src.tell();
+
         Ok(())
     }
 
     pub(crate) fn get_frame(&mut self, src: &mut ByteReader, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> {
-        if src.tell() >= self.data_end {
-            return Err(DemuxerError::EOF);
-        }
-        let fsize               = src.read_u32le()? as usize;
-        if fsize == 0 {
-            return Err(DemuxerError::EOF);
-        }
-        validate!((9..=1048576).contains(&fsize));
-        if let Some(stream) = strmgr.get_stream(0) {
-            let ts = stream.make_ts(Some(self.frameno), None, None);
-            self.frameno += 1;
-            src.read_packet(stream, ts, false, fsize - 4)
-        } else {
-            Err(DemuxerError::InvalidData)
+        let has_video = self.video_pos + 4 < self.data_end;
+        let has_audio = self.audio && self.sound_pos < self.sound_end;
+
+        match (has_video, has_audio) {
+            (true, false) => {
+                                      src.seek(SeekFrom::Start(self.video_pos))?;
+                let fsize           = src.read_u32le()? as usize;
+                if fsize == 0 {
+                    return Err(DemuxerError::EOF);
+                }
+                validate!((9..=1048576).contains(&fsize));
+                if let Some(stream) = strmgr.get_stream(0) {
+                    let ts = stream.make_ts(Some(self.frameno), None, None);
+                    self.frameno += 1;
+                    self.audio = true;
+                    self.video_pos += fsize as u64;
+                    src.read_packet(stream, ts, false, fsize - 4)
+                } else {
+                    Err(DemuxerError::InvalidData)
+                }
+            },
+            (_, true) => {
+                                      src.seek(SeekFrom::Start(self.sound_pos))?;
+                self.audio = false;
+                let cur_blk_len = self.sblk_len.min((self.sound_end - self.sound_pos) as usize);
+                if let Some(stream) = strmgr.get_stream(1) {
+                    let ts = stream.make_ts(Some(self.asize * 2), None, None);
+                    self.asize += cur_blk_len as u64;
+                    self.audio = !has_video;
+                    self.sound_pos += cur_blk_len as u64;
+                    src.read_packet(stream, ts, false, cur_blk_len)
+                } else {
+                    Err(DemuxerError::InvalidData)
+                }
+            },
+            (false, false) => Err(DemuxerError::EOF),
         }
     }
 
index eea28613ca4729d7e0d93e1e3e87de5174133886..10b2266e176d3d3f1843afa096922d6e49559296 100644 (file)
@@ -223,6 +223,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[
     desc!(video;    "escape130",            "Eidos Escape 130"),
     desc!(audio;    "escape-adpcm",         "Eidos Escape ADPCM"),
     desc!(video-llp; "euclid",              "Iota Euclid / The Complete Animation"),
+    desc!(audio;    "iota-sound",           "IotaSound"),
 
     desc!(video;    "truemotion1",   "TrueMotion 1"),
     desc!(video-im; "truemotionrt",  "TrueMotion RT"),