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"]
decoder_rawvideo = ["decoders"]
decoder_escape = ["decoders"]
decoder_euclid = ["decoders"]
+decoder_iotasound = ["decoders"]
decoder_rawaudio = ["decoders"]
--- /dev/null
+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]));
+ }
+}
#[cfg(feature="decoder_euclid")]
mod euclid;
+#[cfg(feature="decoder_iotasound")]
+mod iotasound;
#[cfg(feature="decoder_rawaudio")]
mod rawaudio;
#[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 },
#[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 },
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 {
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();
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)?;
},
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),
}
}
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"),