aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKostya Shishkov <kostya.shishkov@gmail.com>2024-05-05 18:45:38 +0200
committerKostya Shishkov <kostya.shishkov@gmail.com>2024-05-05 18:45:38 +0200
commit41a3a050e538044e6d749693f9ba97d9b447a737 (patch)
treef8426b05e15591fa4f3a573735505eb62b5bcd5c
parentb5c817869631085dd20a5edbd087d4549a42da13 (diff)
downloadnihav-41a3a050e538044e6d749693f9ba97d9b447a737.tar.gz
add raw audio support in Acorn Replay Movie format
-rw-r--r--nihav-acorn/Cargo.toml5
-rw-r--r--nihav-acorn/src/codecs/mod.rs9
-rw-r--r--nihav-acorn/src/codecs/rawaudio.rs287
-rw-r--r--nihav-acorn/src/demuxers/armovie.rs5
-rw-r--r--nihav-registry/src/register.rs1
5 files changed, 304 insertions, 3 deletions
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<NAFrameRef> {
+ 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<NAValue> { None }
+}
+
+pub fn get_decoder() -> Box<dyn NADecoder + Send> {
+ Box::new(RawDecoder::new())
+}
+
+#[derive(Default)]
+struct RawPacketiser {
+ stream: Option<NAStreamRef>,
+ blk_size: usize,
+ asize: u64,
+ buf: Vec<u8>,
+}
+
+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<NAStreamRef> {
+ 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<usize> {
+ Err(DecoderError::NotImplemented)
+ }
+ fn get_packet(&mut self, stream: NAStreamRef) -> DecoderResult<Option<NAPacket>> {
+ 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<dyn NAPacketiser + Send> {
+ 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"),