From 41a3a050e538044e6d749693f9ba97d9b447a737 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sun, 5 May 2024 18:45:38 +0200 Subject: [PATCH] add raw audio support in Acorn Replay Movie format --- nihav-acorn/Cargo.toml | 5 +- nihav-acorn/src/codecs/mod.rs | 9 + nihav-acorn/src/codecs/rawaudio.rs | 287 ++++++++++++++++++++++++++++ nihav-acorn/src/demuxers/armovie.rs | 5 +- nihav-registry/src/register.rs | 1 + 5 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 nihav-acorn/src/codecs/rawaudio.rs diff --git a/nihav-acorn/Cargo.toml b/nihav-acorn/Cargo.toml index db93d87..3266be9 100644 --- a/nihav-acorn/Cargo.toml +++ b/nihav-acorn/Cargo.toml @@ -13,8 +13,9 @@ path = "../nihav-codec-support" [features] default = ["all_decoders", "all_demuxers"] -all_decoders = ["all_video_decoders"] +all_decoders = ["all_video_decoders", "all_audio_decoders"] all_video_decoders = ["decoder_movinglines", "decoder_movingblocks", "decoder_movingblockshq", "decoder_linepack", "decoder_rawvideo"] +all_audio_decoders = ["decoder_rawaudio"] decoders = [] decoder_movinglines = ["decoders"] @@ -23,6 +24,8 @@ decoder_movingblockshq = ["decoders"] decoder_linepack = ["decoders"] decoder_rawvideo = ["decoders"] +decoder_rawaudio = ["decoders"] + all_demuxers = ["demuxer_armovie"] demuxers = [] diff --git a/nihav-acorn/src/codecs/mod.rs b/nihav-acorn/src/codecs/mod.rs index bb1c5ad..28ce17f 100644 --- a/nihav-acorn/src/codecs/mod.rs +++ b/nihav-acorn/src/codecs/mod.rs @@ -33,6 +33,9 @@ mod rawvideo; #[cfg(feature="decoder_linepack")] mod linepack; +#[cfg(feature="decoder_rawaudio")] +mod rawaudio; + const ACORN_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_rawvideo")] DecoderInfo { name: "arm_rawvideo", get_decoder: rawvideo::get_decoder }, @@ -45,6 +48,9 @@ const ACORN_CODECS: &[DecoderInfo] = &[ #[cfg(feature="decoder_linepack")] DecoderInfo { name: "linepack", get_decoder: linepack::get_decoder }, + +#[cfg(feature="decoder_rawaudio")] + DecoderInfo { name: "arm_rawaudio", get_decoder: rawaudio::get_decoder }, ]; /// Registers all available codecs provided by this crate. @@ -66,6 +72,9 @@ const ACORN_PACKETISERS: &[PacketiserInfo] = &[ #[cfg(feature="decoder_linepack")] PacketiserInfo { name: "linepack", get_packetiser: linepack::get_packetiser }, + +#[cfg(feature="decoder_rawaudio")] + PacketiserInfo { name: "arm_rawaudio", get_packetiser: rawaudio::get_packetiser }, ]; /// Registers all available packetisers provided by this crate. diff --git a/nihav-acorn/src/codecs/rawaudio.rs b/nihav-acorn/src/codecs/rawaudio.rs new file mode 100644 index 0000000..5952013 --- /dev/null +++ b/nihav-acorn/src/codecs/rawaudio.rs @@ -0,0 +1,287 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +const fn chord_val(val: u8) -> u32 { + (val as u32) * 0x7FFF / 247 +} + +fn vidc_mu_law(val: u8) -> i16 { + const CHORDS: [u32; 9] = [chord_val(0), chord_val(1), chord_val(3), chord_val(7), + chord_val(15), chord_val(31), chord_val(63), chord_val(127), chord_val(247) ]; + + let chord = ((val >> 4) & 7) as usize; + let point = u32::from(val & 0xF); + + (CHORDS[chord] + point * (CHORDS[chord + 1] - CHORDS[chord]) / 8) as i16 +} + +#[derive(Default,Debug,Clone,Copy,PartialEq)] +enum CodecType { + S8, + #[default] + U8, + S16, + Logarithmic, +} + +#[derive(Default)] +struct RawDecoder { + info: NACodecInfoRef, + chmap: NAChannelMap, + blk_size: usize, + codec_type: CodecType, +} + +impl RawDecoder { + fn new() -> Self { Self::default() } +} + +impl NADecoder for RawDecoder { + fn init(&mut self, _supp: &mut NADecoderSupport, info: NACodecInfoRef) -> DecoderResult<()> { + if let NACodecTypeInfo::Audio(ainfo) = info.get_properties() { + let channels = ainfo.channels; + let srate = ainfo.sample_rate; + let bits = ainfo.format.bits; + validate!(info.get_extradata().is_some()); + + if let Some(edata) = info.get_extradata() { + let mut is_vidc = false; + let mut is_unsigned = false; + + for i in 0..edata.len() { + let head = &edata[i..]; + if (head.len() >= 4 && &head[..4] == b"VIDC") || (head.len() >= 11 && &head[..11] == b"exponential") { + is_vidc = true; + break; + } + if head.len() >= 8 && (&head[..8] == b"unsigned" || &head[..8] == b"UNSIGNED") { + is_unsigned = true; + } + } + self.codec_type = if is_vidc { + CodecType::Logarithmic + } else if bits == 8 { + if is_unsigned { + CodecType::U8 + } else { + CodecType::S8 + } + } else { + CodecType::S16 + }; + } else { + return Err(DecoderError::InvalidData); + } + + match channels { + 1 => self.chmap.add_channel(NAChannelType::C), + 2 => self.chmap.add_channels(&[NAChannelType::L, NAChannelType::R]), + _ => return Err(DecoderError::InvalidData), + } + + self.blk_size = match self.codec_type { + CodecType::U8 | CodecType::S8 | CodecType::Logarithmic => channels as usize, + CodecType::S16 => channels as usize * 2, + }; + + let fmt = match self.codec_type { + CodecType::U8 | CodecType::S8 => SND_U8_FORMAT, + CodecType::S16 | CodecType::Logarithmic => SND_S16_FORMAT, + }; + + let myinfo = NACodecTypeInfo::Audio(NAAudioInfo::new(srate, channels, fmt, 0)); + 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.is_empty() && (src.len() % self.blk_size) == 0); + + let bufinfo = alloc_audio_buffer(self.info.get_properties().get_audio_info().unwrap(), src.len() / self.blk_size, self.chmap.clone())?; + match self.codec_type { + CodecType::S8 => { + let mut buf = bufinfo.get_abuf_u8().unwrap(); + let dst = buf.get_data_mut().unwrap(); + for (dst, &src) in dst.iter_mut().zip(src.iter()) { + *dst = src ^ 0x80; + } + }, + CodecType::U8 => { + let mut buf = bufinfo.get_abuf_u8().unwrap(); + let dst = buf.get_data_mut().unwrap(); + dst[..src.len()].copy_from_slice(&src); + }, + CodecType::S16 => { + let mut buf = bufinfo.get_abuf_i16().unwrap(); + let dst = buf.get_data_mut().unwrap(); + + for (dst, src) in dst.iter_mut().zip(src.chunks_exact(2)) { + *dst = read_u16le(src)? as i16; + } + }, + CodecType::Logarithmic => { + let mut buf = bufinfo.get_abuf_i16().unwrap(); + let dst = buf.get_data_mut().unwrap(); + + for (dst, &val) in dst.iter_mut().zip(src.iter()) { + let sign = (val & 0x01) != 0; + *dst = vidc_mu_law(val >> 1); + if sign { + *dst = -*dst; + } + } + }, + } + + 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 RawDecoder { + 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(RawDecoder::new()) +} + +#[derive(Default)] +struct RawPacketiser { + stream: Option, + blk_size: usize, + asize: u64, + buf: Vec, +} + +impl RawPacketiser { + fn new() -> Self { Self::default() } +} + +impl NAPacketiser for RawPacketiser { + fn attach_stream(&mut self, stream: NAStreamRef) { + if let NACodecTypeInfo::Audio(ainfo) = stream.get_info().get_properties() { + self.blk_size = (ainfo.channels as usize) * (ainfo.format.bits as usize) / 8; + } + self.stream = Some(stream); + } + fn add_data(&mut self, src: &[u8]) -> bool { + self.buf.extend_from_slice(src); + self.buf.len() < (1 << 10) + } + fn parse_stream(&mut self, id: u32) -> DecoderResult { + if let Some(ref stream) = self.stream { + let mut stream = NAStream::clone(stream); + stream.id = id; + Ok(stream.into_ref()) + } else { + Err(DecoderError::MissingReference) + } + } + fn skip_junk(&mut self) -> DecoderResult { + Err(DecoderError::NotImplemented) + } + fn get_packet(&mut self, stream: NAStreamRef) -> DecoderResult> { + if self.blk_size == 0 { + return Err(DecoderError::MissingReference); + } + + if self.buf.len() < 2 { + return Ok(None); + } + + let data_len = self.buf.len(); + let mut data = Vec::new(); + std::mem::swap(&mut self.buf, &mut data); + + let ts = NATimeInfo::new(Some(self.asize), None, None, stream.tb_num, stream.tb_den); + self.asize += (data_len / self.blk_size) as u64; + + Ok(Some(NAPacket::new(stream, ts, true, data))) + } + fn reset(&mut self) { + self.buf.clear(); + } + fn bytes_left(&self) -> usize { self.buf.len() } +} + +pub fn get_packetiser() -> Box { + Box::new(RawPacketiser::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_format_s8() { + 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); + + // a sample from Acorn Replay Demonstration Disc 2 + test_decoding_raw("armovie", "arm_rawaudio", "assets/Acorn/CHEMSET2", Some(1), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5([0x167985bf, 0xd82c3fb0, 0x125ff24e, 0xa7408c57])); + } + + #[test] + fn test_format_u8() { + 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); + + // a sample from Cine Clips by Oregan Software Developments + test_decoding_raw("armovie", "arm_rawaudio", "assets/Acorn/COLOURPLUS", Some(1), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5([0x2f0dc76b, 0x208372ad, 0xa986fb0b, 0x1024dcc8])); + } + + #[test] + fn test_format_vidc() { + 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); + + // a sample from Cine Clips by Oregan Software Developments + test_decoding_raw("armovie", "arm_rawaudio", "assets/Acorn/CFC2", Some(1), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5([0x3c2a6e48, 0x6e511c72, 0xd30b5813, 0x42d98a71])); + } + + #[test] + fn test_format_s16() { + 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); + + // a sample from Cine Clips by Oregan Software Developments + test_decoding_raw("armovie", "arm_rawaudio", "assets/Acorn/SKYHIGH", Some(1), + &dmx_reg, &pkt_reg, &dec_reg, + ExpectedTestResult::MD5([0xd2003a1c, 0xfe38974f, 0xa1850a5b, 0xb4313217])); + } +} diff --git a/nihav-acorn/src/demuxers/armovie.rs b/nihav-acorn/src/demuxers/armovie.rs index 9b9d8ee..64d222f 100644 --- a/nihav-acorn/src/demuxers/armovie.rs +++ b/nihav-acorn/src/demuxers/armovie.rs @@ -296,9 +296,10 @@ impl<'a> RawDemuxCore<'a> for ARMovieDemuxer<'a> { for ((&id, &sratestr), (&chan, &fmt)) in sound_ids.iter().zip(srates.iter()) .zip(channels.iter().zip(sndformats.iter())) { let codec_id = parse_uint(id)?; - let codec_name = if codec_id == 1 { "pcm" } else { "unknown" }; + let codec_name = if codec_id == 1 { "arm_rawaudio" } else { "unknown" }; let channels = parse_uint(chan)?; validate!(channels > 0 && channels < 16); + let edata = fmt.to_owned(); let bits = parse_uint(fmt)?; let mut srate = parse_uint(sratestr)?; if srate > 0 && srate < 1000 { // probably in microseconds instead of Hertz @@ -308,7 +309,7 @@ impl<'a> RawDemuxCore<'a> for ARMovieDemuxer<'a> { let fmt = if bits == 8 { SND_U8_FORMAT } else { SND_S16_FORMAT }; let aci = NACodecTypeInfo::Audio(NAAudioInfo::new(srate, channels as u8, fmt, 0)); - let ainfo = NACodecInfo::new(codec_name, aci, None); + let ainfo = NACodecInfo::new(codec_name, aci, Some(edata)); let ret = strmgr.add_stream(NAStream::new(StreamType::Audio, stream_id, ainfo, 1, srate, 0)); if let Some(id) = ret { self.audio_ids.push(id); diff --git a/nihav-registry/src/register.rs b/nihav-registry/src/register.rs index 1672b7e..15a3f65 100644 --- a/nihav-registry/src/register.rs +++ b/nihav-registry/src/register.rs @@ -208,6 +208,7 @@ static CODEC_REGISTER: &[CodecDescription] = &[ desc!(audio; "qualcomm-purevoice", "Qualcomm PureVoice"), desc!(video-ll; "arm_rawvideo", "Acorn Replay Movie raw video formats"), + desc!(audio; "arm_rawaudio", "Acorn Replay Movie raw audio formats"), desc!(video; "movinglines", "Acorn Moving Lines"), desc!(video; "movingblocks", "Acorn Moving Blocks"), desc!(video; "movingblockshq", "Acorn Moving Blocks HQ"), -- 2.39.5