From b9c74ddc74de7ea802b6e5c22d380e821f7cdff9 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 5 Mar 2023 19:07:03 +0100 Subject: [PATCH 01/27] setup of riff module, support for PCM aiff --- Cargo.toml | 1 + symphonia-format-riff/Cargo.toml | 19 ++ symphonia-format-riff/src/chunks.rs | 299 ++++++++++++++++++++++++ symphonia-format-riff/src/lib.rs | 347 ++++++++++++++++++++++++++++ symphonia/Cargo.toml | 7 + symphonia/src/lib.rs | 5 + 6 files changed, 678 insertions(+) create mode 100644 symphonia-format-riff/Cargo.toml create mode 100644 symphonia-format-riff/src/chunks.rs create mode 100644 symphonia-format-riff/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index bdc8480c..82f88425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "symphonia-format-isomp4", "symphonia-format-mkv", "symphonia-format-ogg", + "symphonia-format-riff", "symphonia-format-wav", "symphonia-metadata", "symphonia-play", diff --git a/symphonia-format-riff/Cargo.toml b/symphonia-format-riff/Cargo.toml new file mode 100644 index 00000000..a27f9d03 --- /dev/null +++ b/symphonia-format-riff/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "symphonia-format-riff" +version = "0.5.2" +description = "Pure Rust RIFF demuxer (a part of project Symphonia)." +homepage = "https://github.com/pdeljanov/Symphonia" +repository = "https://github.com/pdeljanov/Symphonia" +authors = ["Philip Deljanov "] +license = "MPL-2.0" +readme = "README.md" +categories = ["multimedia", "multimedia::audio", "multimedia::encoding"] +keywords = ["audio", "media", "demuxer", "aiff", "riff"] +edition = "2018" +rust-version = "1.53" + +[dependencies] +extended = "0.1.0" +log = "0.4" +symphonia-core = { version = "0.5.2", path = "../symphonia-core" } +symphonia-metadata = { version = "0.5.2", path = "../symphonia-metadata" } diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs new file mode 100644 index 00000000..3c5f4aae --- /dev/null +++ b/symphonia-format-riff/src/chunks.rs @@ -0,0 +1,299 @@ +// Symphonia +// Copyright (c) 2019-2023 The Project Symphonia Developers. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::fmt; +use std::marker::PhantomData; + +use symphonia_core::audio::Channels; +use symphonia_core::codecs::CodecType; +use symphonia_core::codecs::{ + CODEC_TYPE_PCM_S8, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, +}; +use symphonia_core::errors::{decode_error, unsupported_error, Result}; +use symphonia_core::io::ReadBytes; + +use log::info; + +use extended::Extended; + +use crate::PacketInfo; + +pub trait ParseChunkTag: Sized { + fn parse_tag(tag: [u8; 4], len: u32) -> Option; +} + +enum NullChunks {} + +impl ParseChunkTag for NullChunks { + fn parse_tag(_tag: [u8; 4], _len: u32) -> Option { + None + } +} + +/// `ChunksReader` reads chunks from a `ByteStream`. It is generic across a type, usually an enum, +/// implementing the `ParseChunkTag` trait. When a new chunk is encountered in the stream, +/// `parse_tag` on T is called to return an object capable of parsing/reading that chunk or `None`. +/// This makes reading the actual chunk data lazy in that the chunk is not read until the object is +/// consumed. +pub struct ChunksReader { + len: u32, + consumed: u32, + phantom: PhantomData, +} + +impl ChunksReader { + pub fn new(len: u32) -> Self { + ChunksReader { len, consumed: 0, phantom: PhantomData } + } + + pub fn next(&mut self, reader: &mut B) -> Result> { + // Loop until a chunk is recognized and returned, or the end of stream is reached. + loop { + // Align to the next 2-byte boundary if not currently aligned. + if self.consumed & 0x1 == 1 { + reader.read_u8()?; + self.consumed += 1; + } + + // Check if there are enough bytes for another chunk, if not, there are no more chunks. + if self.consumed + 8 > self.len { + return Ok(None); + } + + // Read tag and len, the chunk header. + let tag = reader.read_quad_bytes()?; + let len = reader.read_be_u32()?; + self.consumed += 8; + + // Check if the ChunkReader has enough unread bytes to fully read the chunk. + // + // Warning: the formulation of this conditional is critical because len is untrusted + // input, it may overflow when if added to anything. + if self.len - self.consumed < len { + // When ffmpeg encodes to stdout the riff (parent) and data chunk lengths are + // (2^32)-1 since the size can't be known ahead of time. + if !(self.len == len && len == u32::MAX) { + return decode_error("riff: chunk length exceeds parent (list) chunk length"); + } + } + + // The length of the chunk has been validated, so "consume" the chunk. + self.consumed = self.consumed.saturating_add(len); + + match T::parse_tag(tag, len) { + Some(chunk) => return Ok(Some(chunk)), + None => { + // As per the RIFF spec, unknown chunks are to be ignored. + info!( + "ignoring unknown chunk: tag={}, len={}.", + String::from_utf8_lossy(&tag), + len + ); + + reader.ignore_bytes(u64::from(len))? + } + } + } + } + + #[allow(dead_code)] + pub fn finish(&mut self, reader: &mut B) -> Result<()> { + // If data is remaining in this chunk, skip it. + if self.consumed < self.len { + let remaining = self.len - self.consumed; + reader.ignore_bytes(u64::from(remaining))?; + self.consumed += remaining; + } + + // Pad the chunk to the next 2-byte boundary. + if self.len & 0x1 == 1 { + reader.read_u8()?; + } + + Ok(()) + } +} + +/// Common trait implemented for all chunks that are parsed by a `ChunkParser`. +pub trait ParseChunk: Sized { + fn parse(reader: &mut B, tag: [u8; 4], len: u32) -> Result; +} + +/// `ChunkParser` is a utility struct for unifying the parsing of chunks. +pub struct ChunkParser { + tag: [u8; 4], + len: u32, + phantom: PhantomData

, +} + +impl ChunkParser

{ + fn new(tag: [u8; 4], len: u32) -> Self { + ChunkParser { tag, len, phantom: PhantomData } + } + + pub fn parse(&self, reader: &mut B) -> Result

{ + P::parse(reader, self.tag, self.len) + } +} + +pub enum FormatData { + Pcm(FormatPcm), +} + +pub struct FormatPcm { + /// The number of bits per sample. In the PCM format, this is always a multiple of 8-bits. + pub bits_per_sample: u16, + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +/// `CommonChunk` is a required AIFF chunk, containing metadata. +pub struct CommonChunk { + /// The number of channels. + pub n_channels: i16, + /// The number of audio frames. + pub n_sample_frames: u32, + /// The sample size in bits. + pub sample_size: i16, + /// The sample rate in Hz. + pub sample_rate: u32, + /// Extra data associated with the format block conditional upon the format tag. + pub format_data: FormatData, +} + +impl CommonChunk { + fn read_pcm_fmt( + bits_per_sample: u16, + n_channels: u16, + ) -> Result { + // Bits per sample for PCM is both the encoded sample width, and the actual sample width. + // Strictly, this must either be 8 or 16 bits, but there is no reason why 24 and 32 bits + // can't be supported. Since these files do exist, allow for 8/16/24/32-bit samples, but + // error if not a multiple of 8 or greater than 32-bits. + // + // It is possible though for AIFF to have a sample size not divisible by 8. + // Data is left justified, with the remaining bits zeroed. Currently not supported. + // + // Select the appropriate codec using bits per sample. Samples are always interleaved and + // little-endian encoded for the PCM format. + let codec = match bits_per_sample { + 8 => CODEC_TYPE_PCM_S8, + 16 => CODEC_TYPE_PCM_S16BE, + 24 => CODEC_TYPE_PCM_S24BE, + 32 => CODEC_TYPE_PCM_S32BE, + _ => { + return decode_error( + "aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits", + ) + } + }; + + // The PCM format only supports 1 or 2 channels, for mono and stereo channel layouts, + // respectively. + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("aiff: channel layout is not stereo or mono for fmt_pcm"), + }; + + Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) + + } + + pub(crate) fn packet_info(&self) -> Result { + match &self.format_data { + FormatData::Pcm(_) => { + let block_align = self.n_channels * self.sample_size / 8; + Ok(PacketInfo::without_blocks(block_align as u16)) + } + } + } +} + +impl ParseChunk for CommonChunk { + fn parse(reader: &mut B, _tag: [u8; 4], _: u32) -> Result { + let n_channels = reader.read_be_i16()?; + let n_sample_frames = reader.read_be_u32()?; + let sample_size = reader.read_be_i16()?; + + let mut sample_rate: [u8; 10] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let _res = reader.read_buf(sample_rate.as_mut()); + let sample_rate = Extended::from_be_bytes(sample_rate); + let sample_rate = sample_rate.to_f64() as u32; + + let format_data = Self::read_pcm_fmt(sample_size as u16, n_channels as u16); + + let format_data = match format_data{ + Ok(data) => data, + Err(e) => return Err(e), + }; + Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data }) + } +} + +impl fmt::Display for CommonChunk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "CommonChunk {{")?; + writeln!(f, "\tn_channels: {},", self.n_channels)?; + writeln!(f, "\tsample_rate: {} Hz,", self.sample_rate)?; + + match self.format_data { + FormatData::Pcm(ref pcm) => { + writeln!(f, "\tformat_data: Pcm {{")?; + writeln!(f, "\t\tbits_per_sample: {},", pcm.bits_per_sample)?; + writeln!(f, "\t\tchannels: {},", pcm.channels)?; + writeln!(f, "\t\tcodec: {},", pcm.codec)?; + } + }; + + writeln!(f, "\t}}")?; + writeln!(f, "}}") + } +} + +/// `SoundChunk` is a required AIFF chunk, containing the audio data. +pub struct SoundChunk { + pub len: u32, + pub offset: u32, + pub block_size: u32, +} + +impl ParseChunk for SoundChunk { + fn parse(reader: &mut B, _: [u8; 4], len: u32) -> Result { + let offset = reader.read_be_u32()?; + let block_size = reader.read_be_u32()?; + + if offset != 0 || block_size != 0{ + return unsupported_error("riff: No support for AIFF block-aligned data"); + } + + Ok(SoundChunk { len, offset, block_size }) + } +} + +pub enum RiffAiffChunks { + Common(ChunkParser), + Sound(ChunkParser), +} + +macro_rules! parser { + ($class:expr, $result:ty, $tag:expr, $len:expr) => { + Some($class(ChunkParser::<$result>::new($tag, $len))) + }; +} + +impl ParseChunkTag for RiffAiffChunks { + fn parse_tag(tag: [u8; 4], len: u32) -> Option { + match &tag { + b"COMM" => parser!(RiffAiffChunks::Common, CommonChunk, tag, len), + b"SSND" => parser!(RiffAiffChunks::Sound, SoundChunk, tag, len), + _ => None, + } + } +} \ No newline at end of file diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs new file mode 100644 index 00000000..35fe84c9 --- /dev/null +++ b/symphonia-format-riff/src/lib.rs @@ -0,0 +1,347 @@ +// Symphonia +// Copyright (c) 2019-2022 The Project Symphonia Developers. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![warn(rust_2018_idioms)] +#![forbid(unsafe_code)] +// The following lints are allowed in all Symphonia crates. Please see clippy.toml for their +// justification. +#![allow(clippy::comparison_chain)] +#![allow(clippy::excessive_precision)] +#![allow(clippy::identity_op)] +#![allow(clippy::manual_range_contains)] + +use std::io::{Seek, SeekFrom}; + +use symphonia_core::codecs::CodecParameters; +use symphonia_core::errors::{decode_error, end_of_stream_error, seek_error, unsupported_error}; +use symphonia_core::errors::{Result, SeekErrorKind}; +use symphonia_core::formats::prelude::*; +use symphonia_core::io::*; +use symphonia_core::meta::{Metadata, MetadataLog}; +use symphonia_core::probe::{Descriptor, Instantiate, QueryDescriptor}; +use symphonia_core::support_format; + +use log::{debug, error}; + +mod chunks; + +use chunks::*; + +/// Aiff is actually a RIFF stream, with a "FORM" ASCII stream marker. +const AIFF_STREAM_MARKER: [u8; 4] = *b"FORM"; + +/// A possible RIFF form is "aiff". +const AIFF_RIFF_FORM: [u8; 4] = *b"AIFF"; +/// A possible RIFF form is "aifc", using compressed data. +const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; + +/// The maximum number of frames that will be in a packet. +/// Took this from symphonia-format-wav, but I don't know if it's correct. +const AIFF_MAX_FRAMES_PER_PACKET: u64 = 1152; + +pub(crate) struct PacketInfo { + block_size: u64, + frames_per_block: u64, + max_blocks_per_packet: u64, +} + +impl PacketInfo { + #[allow(dead_code)] + fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { + if frames_per_block == 0 { + return decode_error("riff: frames per block is 0"); + } + Ok(Self { + block_size: u64::from(block_size), + frames_per_block, + max_blocks_per_packet: frames_per_block.max(AIFF_MAX_FRAMES_PER_PACKET) + / frames_per_block, + }) + } + + fn without_blocks(frame_len: u16) -> Self { + Self { + block_size: u64::from(frame_len), + frames_per_block: 1, + max_blocks_per_packet: AIFF_MAX_FRAMES_PER_PACKET, + } + } + + fn is_empty(&self) -> bool { + self.block_size == 0 + } + + fn get_max_frames_per_packet(&self) -> u64 { + self.max_blocks_per_packet * self.frames_per_block + } + + fn get_frames(&self, data_len: u64) -> u64 { + data_len / self.block_size * self.frames_per_block + } + + fn get_actual_ts(&self, ts: u64) -> u64 { + let max_frames_per_packet = self.get_max_frames_per_packet(); + ts / max_frames_per_packet * max_frames_per_packet + } +} + +pub struct RiffReader { + reader: MediaSourceStream, + tracks: Vec, + cues: Vec, + metadata: MetadataLog, + packet_info: PacketInfo, + data_start_pos: u64, + data_end_pos: u64, +} + +impl QueryDescriptor for RiffReader { + fn query() -> &'static [Descriptor] { + &[ + // AIFF RIFF form + support_format!( + "riff", + " Resource Interchange File Format", + &["aiff", "aif", "aifc"], + &["audio/aiff", "audio/x-aiff", " sound/aiff", "audio/x-pn-aiff"], + &[b"FORM"] // TODO: In v0.6 this should also support wave ("RIFF") and avi + ), + ] + } + + fn score(_context: &[u8]) -> u8 { + 255 + } +} + +impl FormatReader for RiffReader { + fn try_new(mut source: MediaSourceStream, _options: &FormatOptions) -> Result { + // The FORM marker should be present. + // TODO: in v0.6 this should also support wave and avi + let marker = source.read_quad_bytes()?; + if marker != AIFF_STREAM_MARKER { + return unsupported_error("riff: missing riff stream marker"); + } + + // File is basically one RIFF chunk, with the actual meta and audio data as sub-chunks (called local chunks). + // Therefore, the header was the chunk ID, and the next 4 bytes is the length of the RIFF + // chunk. + let riff_len = source.read_be_u32()?; + let riff_form = source.read_quad_bytes()?; + + // TODO: in v0.6 this should also support wave and avi + if riff_form == AIFF_RIFF_FORM { + debug!("riff form is aiff"); + } else if riff_form == AIFC_RIFF_FORM { + return unsupported_error("riff: No support for aifc files"); + } else { + error!("riff form is not supported ({})", String::from_utf8_lossy(&riff_form)); + return unsupported_error("riff: riff form is not supported"); + } + + let mut riff_chunks = ChunksReader::::new(riff_len); + + let mut codec_params = CodecParameters::new(); + //TODO: Chunks such as marker contain metadata, get it. + let metadata: MetadataLog = Default::default(); + let mut packet_info = PacketInfo::without_blocks(0); + + // TODO: in v0.6 this should also support wave and avi + loop { + let chunk = riff_chunks.next(&mut source)?; + + // The last chunk should always be a data chunk, if it is not, then the stream is + // unsupported. + // TODO: According to the spec additional chunks can be added after the sound data chunk. In fact any order can be possible. + if chunk.is_none() { + return unsupported_error("riff: missing data chunk"); + } + + match chunk.unwrap() { + RiffAiffChunks::Common(common) => { + let common = common.parse(&mut source)?; + + // The Format chunk contains the block_align field and possible additional information + // to handle packetization and seeking. + packet_info = common.packet_info()?; + codec_params + .with_max_frames_per_packet(packet_info.get_max_frames_per_packet()) + .with_frames_per_block(packet_info.frames_per_block); + + // Append Format chunk fields to codec parameters. + append_common_params(&mut codec_params, common); + } + RiffAiffChunks::Sound(dat) => { + let data = dat.parse(&mut source)?; + + // Record the bounds of the data chunk. + let data_start_pos = source.pos(); + let data_end_pos = data_start_pos + u64::from(data.len); + + // Append Sound chunk fields to codec parameters. + append_sound_params(&mut codec_params, &data, &packet_info); + + // Add a new track using the collected codec parameters. + return Ok(RiffReader { + reader: source, + tracks: vec![Track::new(0, codec_params)], + cues: Vec::new(), + metadata, + packet_info, + data_start_pos, + data_end_pos, + }); + } + } + } + } + + fn next_packet(&mut self) -> Result { + let pos = self.reader.pos(); + if self.tracks.is_empty() { + return decode_error("riff: no tracks"); + } + if self.packet_info.is_empty() { + return decode_error("riff: block size is 0"); + } + + // Determine the number of complete blocks remaining in the data chunk. + let num_blocks_left = if pos < self.data_end_pos { + (self.data_end_pos - pos) / self.packet_info.block_size + } + else { + 0 + }; + + if num_blocks_left == 0 { + return end_of_stream_error(); + } + + let blocks_per_packet = num_blocks_left.min(self.packet_info.max_blocks_per_packet); + + let dur = blocks_per_packet * self.packet_info.frames_per_block; + let packet_len = blocks_per_packet * self.packet_info.block_size; + + // Copy the frames. + let packet_buf = self.reader.read_boxed_slice(packet_len as usize)?; + + // The packet timestamp is the position of the first byte of the first frame in the + // packet relative to the start of the data chunk divided by the length per frame. + let pts = self.packet_info.get_frames(pos - self.data_start_pos); + + Ok(Packet::new_from_boxed_slice(0, pts, dur, packet_buf)) + } + + fn metadata(&mut self) -> Metadata<'_> { + self.metadata.metadata() + } + + fn cues(&self) -> &[Cue] { + &self.cues + } + + fn tracks(&self) -> &[Track] { + &self.tracks + } + + fn seek(&mut self, _mode: SeekMode, to: SeekTo) -> Result { + if self.tracks.is_empty() || self.packet_info.is_empty() { + return seek_error(SeekErrorKind::Unseekable); + } + + let params = &self.tracks[0].codec_params; + + let ts = match to { + // Frame timestamp given. + SeekTo::TimeStamp { ts, .. } => ts, + // Time value given, calculate frame timestamp from sample rate. + SeekTo::Time { time, .. } => { + // Use the sample rate to calculate the frame timestamp. If sample rate is not + // known, the seek cannot be completed. + if let Some(sample_rate) = params.sample_rate { + TimeBase::new(1, sample_rate).calc_timestamp(time) + } + else { + return seek_error(SeekErrorKind::Unseekable); + } + } + }; + + // If the total number of frames in the track is known, verify the desired frame timestamp + // does not exceed it. + if let Some(n_frames) = params.n_frames { + if ts > n_frames { + return seek_error(SeekErrorKind::OutOfRange); + } + } + + debug!("seeking to frame_ts={}", ts); + + // RIFF is not internally packetized for PCM codecs. Packetization is simulated by trying to + // read a constant number of samples or blocks every call to next_packet. Therefore, a packet begins + // wherever the data stream is currently positioned. Since timestamps on packets should be + // determinstic, instead of seeking to the exact timestamp requested and starting the next + // packet there, seek to a packet boundary. In this way, packets will have have the same + // timestamps regardless if the stream was seeked or not. + let actual_ts = self.packet_info.get_actual_ts(ts); + + // Calculate the absolute byte offset of the desired audio frame. + let seek_pos = self.data_start_pos + (actual_ts * self.packet_info.block_size); + + // If the reader supports seeking we can seek directly to the frame's offset wherever it may + // be. + if self.reader.is_seekable() { + self.reader.seek(SeekFrom::Start(seek_pos))?; + } + // If the reader does not support seeking, we can only emulate forward seeks by consuming + // bytes. If the reader has to seek backwards, return an error. + else { + let current_pos = self.reader.pos(); + if seek_pos >= current_pos { + self.reader.ignore_bytes(seek_pos - current_pos)?; + } + else { + return seek_error(SeekErrorKind::ForwardOnly); + } + } + + debug!("seeked to packet_ts={} (delta={})", actual_ts, actual_ts as i64 - ts as i64); + + Ok(SeekedTo { track_id: 0, actual_ts, required_ts: ts }) + } + + fn into_inner(self: Box) -> MediaSourceStream { + self.reader + } +} + +fn append_common_params(codec_params: &mut CodecParameters, format: CommonChunk) { + codec_params + .with_sample_rate(format.sample_rate) + .with_time_base(TimeBase::new(1, format.sample_rate)); + + match format.format_data { + FormatData::Pcm(pcm) => { + codec_params + .for_codec(pcm.codec) + .with_bits_per_coded_sample(u32::from(pcm.bits_per_sample)) + .with_bits_per_sample(u32::from(pcm.bits_per_sample)) + .with_channels(pcm.channels); + } + } +} + +fn append_sound_params( + codec_params: &mut CodecParameters, + data: &SoundChunk, + packet_info: &PacketInfo, +) { + if !packet_info.is_empty() { + let n_frames = packet_info.get_frames(u64::from(data.len)); + codec_params.with_n_frames(n_frames); + } +} \ No newline at end of file diff --git a/symphonia/Cargo.toml b/symphonia/Cargo.toml index eb2b39db..143542e1 100644 --- a/symphonia/Cargo.toml +++ b/symphonia/Cargo.toml @@ -30,6 +30,7 @@ mp2 = ["symphonia-bundle-mp3/mp2"] mp3 = ["symphonia-bundle-mp3/mp3"] ogg = ["symphonia-format-ogg"] pcm = ["symphonia-codec-pcm"] +riff = ["symphonia-format-riff"] vorbis = ["symphonia-codec-vorbis"] wav = ["symphonia-format-wav"] @@ -54,6 +55,7 @@ all-formats = [ "isomp4", "mkv", "ogg", + "riff", "wav" ] @@ -111,6 +113,11 @@ version = "0.5.2" path = "../symphonia-codec-vorbis" optional = true +[dependencies.symphonia-format-riff] +version = "0.5.2" +path = "../symphonia-format-riff" +optional = true + [dependencies.symphonia-format-wav] version = "0.5.2" path = "../symphonia-format-wav" diff --git a/symphonia/src/lib.rs b/symphonia/src/lib.rs index af2ea2a0..b7cbbfb7 100644 --- a/symphonia/src/lib.rs +++ b/symphonia/src/lib.rs @@ -161,6 +161,8 @@ pub mod default { pub use symphonia_format_mkv::MkvReader; #[cfg(feature = "ogg")] pub use symphonia_format_ogg::OggReader; + #[cfg(feature = "riff")] + pub use symphonia_format_riff::RiffReader; #[cfg(feature = "wav")] pub use symphonia_format_wav::WavReader; @@ -259,6 +261,9 @@ pub mod default { #[cfg(any(feature = "mp1", feature = "mp2", feature = "mp3"))] probe.register_all::(); + #[cfg(feature = "riff")] + probe.register_all::(); + #[cfg(feature = "wav")] probe.register_all::(); From 2316cc4ae7937c27898d43b944a8d1403996877c Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 26 Mar 2023 20:18:55 +0200 Subject: [PATCH 02/27] CommonChunk parse now errors if reading samplerate data fails --- symphonia-format-riff/src/chunks.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 3c5f4aae..74b975be 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -223,7 +223,8 @@ impl ParseChunk for CommonChunk { let sample_size = reader.read_be_i16()?; let mut sample_rate: [u8; 10] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let _res = reader.read_buf(sample_rate.as_mut()); + let _res = reader.read_buf_exact(sample_rate.as_mut())?; + let sample_rate = Extended::from_be_bytes(sample_rate); let sample_rate = sample_rate.to_f64() as u32; From 0da1f5211a88c0a271620d7d9939191f3a396225 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 26 Mar 2023 20:19:49 +0200 Subject: [PATCH 03/27] CommonChunk::parse now uses shorthand for creating array --- symphonia-format-riff/src/chunks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 74b975be..453527f6 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -222,7 +222,7 @@ impl ParseChunk for CommonChunk { let n_sample_frames = reader.read_be_u32()?; let sample_size = reader.read_be_i16()?; - let mut sample_rate: [u8; 10] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let mut sample_rate: [u8; 10] = [0; 10]; let _res = reader.read_buf_exact(sample_rate.as_mut())?; let sample_rate = Extended::from_be_bytes(sample_rate); From e3bbe1e16dffd90cbbf69529e02496d921ba8f2d Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 26 Mar 2023 20:23:24 +0200 Subject: [PATCH 04/27] Added dedobbin as author of symphonia-format-riff --- symphonia-format-riff/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symphonia-format-riff/Cargo.toml b/symphonia-format-riff/Cargo.toml index a27f9d03..44937a57 100644 --- a/symphonia-format-riff/Cargo.toml +++ b/symphonia-format-riff/Cargo.toml @@ -4,7 +4,7 @@ version = "0.5.2" description = "Pure Rust RIFF demuxer (a part of project Symphonia)." homepage = "https://github.com/pdeljanov/Symphonia" repository = "https://github.com/pdeljanov/Symphonia" -authors = ["Philip Deljanov "] +authors = ["Philip Deljanov ", "dedobbin "] license = "MPL-2.0" readme = "README.md" categories = ["multimedia", "multimedia::audio", "multimedia::encoding"] From d09f4742e58d992f5d0def91baa6547a55908e31 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 26 Mar 2023 20:25:38 +0200 Subject: [PATCH 05/27] updated comment for AIFF_MAX_FRAMES_PER_PACKET --- symphonia-format-riff/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 35fe84c9..7df51806 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -40,7 +40,7 @@ const AIFF_RIFF_FORM: [u8; 4] = *b"AIFF"; const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; /// The maximum number of frames that will be in a packet. -/// Took this from symphonia-format-wav, but I don't know if it's correct. +/// Since there are no real packets in AIFF, this is arbitrary, used same value as MP3. const AIFF_MAX_FRAMES_PER_PACKET: u64 = 1152; pub(crate) struct PacketInfo { From 30c7bb933ead77e3fa2f9430b0460f49e3e44dbe Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sat, 22 Apr 2023 21:07:22 +0200 Subject: [PATCH 06/27] renamed riff reader to aiffreader, will make seperate readers for other riff formats --- symphonia-format-riff/src/lib.rs | 27 ++++++++++++--------------- symphonia/Cargo.toml | 4 ++-- symphonia/src/lib.rs | 8 ++++---- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 7df51806..04fe01d6 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -53,7 +53,7 @@ impl PacketInfo { #[allow(dead_code)] fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { if frames_per_block == 0 { - return decode_error("riff: frames per block is 0"); + return decode_error("aiff: frames per block is 0"); } Ok(Self { block_size: u64::from(block_size), @@ -89,7 +89,7 @@ impl PacketInfo { } } -pub struct RiffReader { +pub struct AiffReader { reader: MediaSourceStream, tracks: Vec, cues: Vec, @@ -99,7 +99,7 @@ pub struct RiffReader { data_end_pos: u64, } -impl QueryDescriptor for RiffReader { +impl QueryDescriptor for AiffReader { fn query() -> &'static [Descriptor] { &[ // AIFF RIFF form @@ -108,7 +108,7 @@ impl QueryDescriptor for RiffReader { " Resource Interchange File Format", &["aiff", "aif", "aifc"], &["audio/aiff", "audio/x-aiff", " sound/aiff", "audio/x-pn-aiff"], - &[b"FORM"] // TODO: In v0.6 this should also support wave ("RIFF") and avi + &[b"FORM"] ), ] } @@ -118,13 +118,12 @@ impl QueryDescriptor for RiffReader { } } -impl FormatReader for RiffReader { +impl FormatReader for AiffReader { fn try_new(mut source: MediaSourceStream, _options: &FormatOptions) -> Result { // The FORM marker should be present. - // TODO: in v0.6 this should also support wave and avi let marker = source.read_quad_bytes()?; if marker != AIFF_STREAM_MARKER { - return unsupported_error("riff: missing riff stream marker"); + return unsupported_error("aiff: missing riff stream marker"); } // File is basically one RIFF chunk, with the actual meta and audio data as sub-chunks (called local chunks). @@ -133,14 +132,13 @@ impl FormatReader for RiffReader { let riff_len = source.read_be_u32()?; let riff_form = source.read_quad_bytes()?; - // TODO: in v0.6 this should also support wave and avi if riff_form == AIFF_RIFF_FORM { debug!("riff form is aiff"); } else if riff_form == AIFC_RIFF_FORM { - return unsupported_error("riff: No support for aifc files"); + return unsupported_error("aiff: No support for aifc files"); } else { error!("riff form is not supported ({})", String::from_utf8_lossy(&riff_form)); - return unsupported_error("riff: riff form is not supported"); + return unsupported_error("aiff: riff form is not supported"); } let mut riff_chunks = ChunksReader::::new(riff_len); @@ -150,7 +148,6 @@ impl FormatReader for RiffReader { let metadata: MetadataLog = Default::default(); let mut packet_info = PacketInfo::without_blocks(0); - // TODO: in v0.6 this should also support wave and avi loop { let chunk = riff_chunks.next(&mut source)?; @@ -158,7 +155,7 @@ impl FormatReader for RiffReader { // unsupported. // TODO: According to the spec additional chunks can be added after the sound data chunk. In fact any order can be possible. if chunk.is_none() { - return unsupported_error("riff: missing data chunk"); + return unsupported_error("aiff: missing data chunk"); } match chunk.unwrap() { @@ -186,7 +183,7 @@ impl FormatReader for RiffReader { append_sound_params(&mut codec_params, &data, &packet_info); // Add a new track using the collected codec parameters. - return Ok(RiffReader { + return Ok(AiffReader { reader: source, tracks: vec![Track::new(0, codec_params)], cues: Vec::new(), @@ -203,10 +200,10 @@ impl FormatReader for RiffReader { fn next_packet(&mut self) -> Result { let pos = self.reader.pos(); if self.tracks.is_empty() { - return decode_error("riff: no tracks"); + return decode_error("aiff: no tracks"); } if self.packet_info.is_empty() { - return decode_error("riff: block size is 0"); + return decode_error("aiff: block size is 0"); } // Determine the number of complete blocks remaining in the data chunk. diff --git a/symphonia/Cargo.toml b/symphonia/Cargo.toml index 143542e1..8c73637d 100644 --- a/symphonia/Cargo.toml +++ b/symphonia/Cargo.toml @@ -30,7 +30,7 @@ mp2 = ["symphonia-bundle-mp3/mp2"] mp3 = ["symphonia-bundle-mp3/mp3"] ogg = ["symphonia-format-ogg"] pcm = ["symphonia-codec-pcm"] -riff = ["symphonia-format-riff"] +aiff = ["symphonia-format-riff"] vorbis = ["symphonia-codec-vorbis"] wav = ["symphonia-format-wav"] @@ -55,7 +55,7 @@ all-formats = [ "isomp4", "mkv", "ogg", - "riff", + "aiff", "wav" ] diff --git a/symphonia/src/lib.rs b/symphonia/src/lib.rs index b7cbbfb7..610f8c8a 100644 --- a/symphonia/src/lib.rs +++ b/symphonia/src/lib.rs @@ -161,8 +161,8 @@ pub mod default { pub use symphonia_format_mkv::MkvReader; #[cfg(feature = "ogg")] pub use symphonia_format_ogg::OggReader; - #[cfg(feature = "riff")] - pub use symphonia_format_riff::RiffReader; + #[cfg(feature = "aiff")] + pub use symphonia_format_riff::AiffReader; #[cfg(feature = "wav")] pub use symphonia_format_wav::WavReader; @@ -261,8 +261,8 @@ pub mod default { #[cfg(any(feature = "mp1", feature = "mp2", feature = "mp3"))] probe.register_all::(); - #[cfg(feature = "riff")] - probe.register_all::(); + #[cfg(feature = "aiff")] + probe.register_all::(); #[cfg(feature = "wav")] probe.register_all::(); From 5ad5a2bd8ceeb2bd72575bbaa89894d24a95b51c Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 23 Apr 2023 21:34:57 +0200 Subject: [PATCH 07/27] moved reusable riff logic from aiff to its own file --- symphonia-format-riff/src/chunks.rs | 146 +----------- symphonia-format-riff/src/lib.rs | 123 +--------- symphonia-format-riff/src/riff.rs | 349 ++++++++++++++++++++++++++++ 3 files changed, 367 insertions(+), 251 deletions(-) create mode 100644 symphonia-format-riff/src/riff.rs diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 453527f6..0dfdd2a3 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -6,152 +6,19 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; -use std::marker::PhantomData; + +use crate::{PacketInfo, FormatData, FormatPcm, ParseChunkTag, ParseChunk, ChunkParser}; use symphonia_core::audio::Channels; -use symphonia_core::codecs::CodecType; use symphonia_core::codecs::{ CODEC_TYPE_PCM_S8, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; -use log::info; use extended::Extended; -use crate::PacketInfo; - -pub trait ParseChunkTag: Sized { - fn parse_tag(tag: [u8; 4], len: u32) -> Option; -} - -enum NullChunks {} - -impl ParseChunkTag for NullChunks { - fn parse_tag(_tag: [u8; 4], _len: u32) -> Option { - None - } -} - -/// `ChunksReader` reads chunks from a `ByteStream`. It is generic across a type, usually an enum, -/// implementing the `ParseChunkTag` trait. When a new chunk is encountered in the stream, -/// `parse_tag` on T is called to return an object capable of parsing/reading that chunk or `None`. -/// This makes reading the actual chunk data lazy in that the chunk is not read until the object is -/// consumed. -pub struct ChunksReader { - len: u32, - consumed: u32, - phantom: PhantomData, -} - -impl ChunksReader { - pub fn new(len: u32) -> Self { - ChunksReader { len, consumed: 0, phantom: PhantomData } - } - - pub fn next(&mut self, reader: &mut B) -> Result> { - // Loop until a chunk is recognized and returned, or the end of stream is reached. - loop { - // Align to the next 2-byte boundary if not currently aligned. - if self.consumed & 0x1 == 1 { - reader.read_u8()?; - self.consumed += 1; - } - - // Check if there are enough bytes for another chunk, if not, there are no more chunks. - if self.consumed + 8 > self.len { - return Ok(None); - } - - // Read tag and len, the chunk header. - let tag = reader.read_quad_bytes()?; - let len = reader.read_be_u32()?; - self.consumed += 8; - - // Check if the ChunkReader has enough unread bytes to fully read the chunk. - // - // Warning: the formulation of this conditional is critical because len is untrusted - // input, it may overflow when if added to anything. - if self.len - self.consumed < len { - // When ffmpeg encodes to stdout the riff (parent) and data chunk lengths are - // (2^32)-1 since the size can't be known ahead of time. - if !(self.len == len && len == u32::MAX) { - return decode_error("riff: chunk length exceeds parent (list) chunk length"); - } - } - - // The length of the chunk has been validated, so "consume" the chunk. - self.consumed = self.consumed.saturating_add(len); - - match T::parse_tag(tag, len) { - Some(chunk) => return Ok(Some(chunk)), - None => { - // As per the RIFF spec, unknown chunks are to be ignored. - info!( - "ignoring unknown chunk: tag={}, len={}.", - String::from_utf8_lossy(&tag), - len - ); - - reader.ignore_bytes(u64::from(len))? - } - } - } - } - - #[allow(dead_code)] - pub fn finish(&mut self, reader: &mut B) -> Result<()> { - // If data is remaining in this chunk, skip it. - if self.consumed < self.len { - let remaining = self.len - self.consumed; - reader.ignore_bytes(u64::from(remaining))?; - self.consumed += remaining; - } - - // Pad the chunk to the next 2-byte boundary. - if self.len & 0x1 == 1 { - reader.read_u8()?; - } - - Ok(()) - } -} - -/// Common trait implemented for all chunks that are parsed by a `ChunkParser`. -pub trait ParseChunk: Sized { - fn parse(reader: &mut B, tag: [u8; 4], len: u32) -> Result; -} - -/// `ChunkParser` is a utility struct for unifying the parsing of chunks. -pub struct ChunkParser { - tag: [u8; 4], - len: u32, - phantom: PhantomData

, -} - -impl ChunkParser

{ - fn new(tag: [u8; 4], len: u32) -> Self { - ChunkParser { tag, len, phantom: PhantomData } - } - - pub fn parse(&self, reader: &mut B) -> Result

{ - P::parse(reader, self.tag, self.len) - } -} - -pub enum FormatData { - Pcm(FormatPcm), -} - -pub struct FormatPcm { - /// The number of bits per sample. In the PCM format, this is always a multiple of 8-bits. - pub bits_per_sample: u16, - /// Channel bitmask. - pub channels: Channels, - /// Codec type. - pub codec: CodecType, -} /// `CommonChunk` is a required AIFF chunk, containing metadata. pub struct CommonChunk { @@ -212,6 +79,7 @@ impl CommonChunk { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } + _=> todo!("other formats") } } } @@ -250,7 +118,13 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tbits_per_sample: {},", pcm.bits_per_sample)?; writeln!(f, "\t\tchannels: {},", pcm.channels)?; writeln!(f, "\t\tcodec: {},", pcm.codec)?; - } + }, + //TODO: this is not optimal + _ => { + //TODO: this is not optimal.. + writeln!(f, "\tdisplay not implemented for format")?; + } + }; writeln!(f, "\t}}")?; diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 04fe01d6..310fe4cc 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -17,7 +17,7 @@ use std::io::{Seek, SeekFrom}; use symphonia_core::codecs::CodecParameters; -use symphonia_core::errors::{decode_error, end_of_stream_error, seek_error, unsupported_error}; +use symphonia_core::errors::{seek_error, unsupported_error}; use symphonia_core::errors::{Result, SeekErrorKind}; use symphonia_core::formats::prelude::*; use symphonia_core::io::*; @@ -28,9 +28,11 @@ use symphonia_core::support_format; use log::{debug, error}; mod chunks; - use chunks::*; +mod riff; +use riff::*; + /// Aiff is actually a RIFF stream, with a "FORM" ASCII stream marker. const AIFF_STREAM_MARKER: [u8; 4] = *b"FORM"; @@ -39,56 +41,6 @@ const AIFF_RIFF_FORM: [u8; 4] = *b"AIFF"; /// A possible RIFF form is "aifc", using compressed data. const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; -/// The maximum number of frames that will be in a packet. -/// Since there are no real packets in AIFF, this is arbitrary, used same value as MP3. -const AIFF_MAX_FRAMES_PER_PACKET: u64 = 1152; - -pub(crate) struct PacketInfo { - block_size: u64, - frames_per_block: u64, - max_blocks_per_packet: u64, -} - -impl PacketInfo { - #[allow(dead_code)] - fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { - if frames_per_block == 0 { - return decode_error("aiff: frames per block is 0"); - } - Ok(Self { - block_size: u64::from(block_size), - frames_per_block, - max_blocks_per_packet: frames_per_block.max(AIFF_MAX_FRAMES_PER_PACKET) - / frames_per_block, - }) - } - - fn without_blocks(frame_len: u16) -> Self { - Self { - block_size: u64::from(frame_len), - frames_per_block: 1, - max_blocks_per_packet: AIFF_MAX_FRAMES_PER_PACKET, - } - } - - fn is_empty(&self) -> bool { - self.block_size == 0 - } - - fn get_max_frames_per_packet(&self) -> u64 { - self.max_blocks_per_packet * self.frames_per_block - } - - fn get_frames(&self, data_len: u64) -> u64 { - data_len / self.block_size * self.frames_per_block - } - - fn get_actual_ts(&self, ts: u64) -> u64 { - let max_frames_per_packet = self.get_max_frames_per_packet(); - ts / max_frames_per_packet * max_frames_per_packet - } -} - pub struct AiffReader { reader: MediaSourceStream, tracks: Vec, @@ -141,7 +93,7 @@ impl FormatReader for AiffReader { return unsupported_error("aiff: riff form is not supported"); } - let mut riff_chunks = ChunksReader::::new(riff_len); + let mut riff_chunks = ChunksReader::::new(riff_len, ByteOrder::BigEndian); let mut codec_params = CodecParameters::new(); //TODO: Chunks such as marker contain metadata, get it. @@ -170,7 +122,7 @@ impl FormatReader for AiffReader { .with_frames_per_block(packet_info.frames_per_block); // Append Format chunk fields to codec parameters. - append_common_params(&mut codec_params, common); + append_format_params(&mut codec_params, &common.format_data, common.sample_rate); } RiffAiffChunks::Sound(dat) => { let data = dat.parse(&mut source)?; @@ -180,7 +132,7 @@ impl FormatReader for AiffReader { let data_end_pos = data_start_pos + u64::from(data.len); // Append Sound chunk fields to codec parameters. - append_sound_params(&mut codec_params, &data, &packet_info); + append_data_params(&mut codec_params, data.len as u64, &packet_info); // Add a new track using the collected codec parameters. return Ok(AiffReader { @@ -198,39 +150,7 @@ impl FormatReader for AiffReader { } fn next_packet(&mut self) -> Result { - let pos = self.reader.pos(); - if self.tracks.is_empty() { - return decode_error("aiff: no tracks"); - } - if self.packet_info.is_empty() { - return decode_error("aiff: block size is 0"); - } - - // Determine the number of complete blocks remaining in the data chunk. - let num_blocks_left = if pos < self.data_end_pos { - (self.data_end_pos - pos) / self.packet_info.block_size - } - else { - 0 - }; - - if num_blocks_left == 0 { - return end_of_stream_error(); - } - - let blocks_per_packet = num_blocks_left.min(self.packet_info.max_blocks_per_packet); - - let dur = blocks_per_packet * self.packet_info.frames_per_block; - let packet_len = blocks_per_packet * self.packet_info.block_size; - - // Copy the frames. - let packet_buf = self.reader.read_boxed_slice(packet_len as usize)?; - - // The packet timestamp is the position of the first byte of the first frame in the - // packet relative to the start of the data chunk divided by the length per frame. - let pts = self.packet_info.get_frames(pos - self.data_start_pos); - - Ok(Packet::new_from_boxed_slice(0, pts, dur, packet_buf)) + next_packet(&mut self.reader, &self.packet_info, &self.tracks, self.data_start_pos, self.data_end_pos) } fn metadata(&mut self) -> Metadata<'_> { @@ -314,31 +234,4 @@ impl FormatReader for AiffReader { fn into_inner(self: Box) -> MediaSourceStream { self.reader } -} - -fn append_common_params(codec_params: &mut CodecParameters, format: CommonChunk) { - codec_params - .with_sample_rate(format.sample_rate) - .with_time_base(TimeBase::new(1, format.sample_rate)); - - match format.format_data { - FormatData::Pcm(pcm) => { - codec_params - .for_codec(pcm.codec) - .with_bits_per_coded_sample(u32::from(pcm.bits_per_sample)) - .with_bits_per_sample(u32::from(pcm.bits_per_sample)) - .with_channels(pcm.channels); - } - } -} - -fn append_sound_params( - codec_params: &mut CodecParameters, - data: &SoundChunk, - packet_info: &PacketInfo, -) { - if !packet_info.is_empty() { - let n_frames = packet_info.get_frames(u64::from(data.len)); - codec_params.with_n_frames(n_frames); - } } \ No newline at end of file diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs new file mode 100644 index 00000000..6bc4d6d9 --- /dev/null +++ b/symphonia-format-riff/src/riff.rs @@ -0,0 +1,349 @@ +// Symphonia +// Copyright (c) 2019-2022 The Project Symphonia Developers. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/// `PacketInfo` helps to simulate packetization over a number of blocks of data. +/// In case the codec is blockless the block size equals one full audio frame in bytes. + +use std::marker::PhantomData; + +use symphonia_core::audio::Channels; +use symphonia_core::codecs::CodecParameters; +use symphonia_core::codecs::CodecType; +use symphonia_core::errors::{decode_error, end_of_stream_error, Result}; +use symphonia_core::formats::prelude::*; +use symphonia_core::io::{MediaSourceStream, ReadBytes}; + +use log::info; + +pub enum ByteOrder { + LittleEndian, + BigEndian, +} + +/// The maximum number of frames that will be in a packet. +/// Since there are no real packets in AIFF, this is arbitrary, used same value as MP3. +const MAX_FRAMES_PER_PACKET: u64 = 1152; + +/// `ParseChunkTag` implements `parse_tag` to map between the 4-byte chunk identifier and the +/// enumeration +pub trait ParseChunkTag: Sized { + fn parse_tag(tag: [u8; 4], len: u32) -> Option; +} + +pub enum NullChunks {} + +impl ParseChunkTag for NullChunks { + fn parse_tag(_tag: [u8; 4], _len: u32) -> Option { + None + } +} + +/// `ChunksReader` reads chunks from a `ByteStream`. It is generic across a type, usually an enum, +/// implementing the `ParseChunkTag` trait. When a new chunk is encountered in the stream, +/// `parse_tag` on T is called to return an object capable of parsing/reading that chunk or `None`. +/// This makes reading the actual chunk data lazy in that the chunk is not read until the object is +/// consumed. +pub struct ChunksReader { + len: u32, + byte_order: ByteOrder, + consumed: u32, + phantom: PhantomData, +} + +impl ChunksReader { + pub fn new(len: u32, byte_order: ByteOrder) -> Self { + ChunksReader { len, byte_order, consumed: 0, phantom: PhantomData } + } + + pub fn next(&mut self, reader: &mut B) -> Result> { + // Loop until a chunk is recognized and returned, or the end of stream is reached. + loop { + // Align to the next 2-byte boundary if not currently aligned. + if self.consumed & 0x1 == 1 { + reader.read_u8()?; + self.consumed += 1; + } + + // Check if there are enough bytes for another chunk, if not, there are no more chunks. + if self.consumed + 8 > self.len { + return Ok(None); + } + + // Read tag and len, the chunk header. + let tag = reader.read_quad_bytes()?; + // TODO: this probably breaks on machine with big endian architecture + let len = match self.byte_order { + ByteOrder::LittleEndian => reader.read_u32()?, + ByteOrder::BigEndian => reader.read_be_u32()?, + }; + + self.consumed += 8; + + // Check if the ChunkReader has enough unread bytes to fully read the chunk. + // + // Warning: the formulation of this conditional is critical because len is untrusted + // input, it may overflow when if added to anything. + if self.len - self.consumed < len { + // When ffmpeg encodes wave to stdout the riff (parent) and data chunk lengths are + // (2^32)-1 since the size can't be known ahead of time. + if !(self.len == len && len == u32::MAX) { + return decode_error("riff: chunk length exceeds parent (list) chunk length"); + } + } + + // The length of the chunk has been validated, so "consume" the chunk. + self.consumed = self.consumed.saturating_add(len); + + match T::parse_tag(tag, len) { + Some(chunk) => return Ok(Some(chunk)), + None => { + // As per the RIFF spec, unknown chunks are to be ignored. + info!( + "ignoring unknown chunk: tag={}, len={}.", + String::from_utf8_lossy(&tag), + len + ); + + reader.ignore_bytes(u64::from(len))? + } + } + } + } + pub fn finish(&mut self, reader: &mut B) -> Result<()> { + // If data is remaining in this chunk, skip it. + if self.consumed < self.len { + let remaining = self.len - self.consumed; + reader.ignore_bytes(u64::from(remaining))?; + self.consumed += remaining; + } + + // Pad the chunk to the next 2-byte boundary. + if self.len & 0x1 == 1 { + reader.read_u8()?; + } + + Ok(()) + } +} + +/// Common trait implemented for all chunks that are parsed by a `ChunkParser`. +pub trait ParseChunk: Sized { + fn parse(reader: &mut B, tag: [u8; 4], len: u32) -> Result; +} + +/// `ChunkParser` is a utility struct for unifying the parsing of chunks. +pub struct ChunkParser { + tag: [u8; 4], + len: u32, + phantom: PhantomData

, +} + +impl ChunkParser

{ + pub fn new(tag: [u8; 4], len: u32) -> Self { + ChunkParser { tag, len, phantom: PhantomData } + } + + pub fn parse(&self, reader: &mut B) -> Result

{ + P::parse(reader, self.tag, self.len) + } +} + +pub enum FormatData { + Pcm(FormatPcm), + Adpcm(FormatAdpcm), + IeeeFloat(FormatIeeeFloat), + Extensible(FormatExtensible), + ALaw(FormatALaw), + MuLaw(FormatMuLaw), +} + +pub struct FormatPcm { + /// The number of bits per sample. In the PCM format, this is always a multiple of 8-bits. + pub bits_per_sample: u16, + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +pub struct FormatAdpcm { + /// The number of bits per sample. At the moment only 4bit is supported. + pub bits_per_sample: u16, + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +pub struct FormatIeeeFloat { + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +pub struct FormatExtensible { + /// The number of bits per sample as stored in the stream. This value is always a multiple of + /// 8-bits. + pub bits_per_sample: u16, + /// The number of bits per sample that are valid. This number is always less than the number + /// of bits per sample. + pub bits_per_coded_sample: u16, + /// Channel bitmask. + pub channels: Channels, + /// Globally unique identifier of the format. + pub sub_format_guid: [u8; 16], + /// Codec type. + pub codec: CodecType, +} + +pub struct FormatALaw { + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +pub struct FormatMuLaw { + /// Channel bitmask. + pub channels: Channels, + /// Codec type. + pub codec: CodecType, +} + +pub struct PacketInfo { + pub block_size: u64, + pub frames_per_block: u64, + pub max_blocks_per_packet: u64, +} + +impl PacketInfo { + pub fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { + if frames_per_block == 0 { + return decode_error("riff: frames per block is 0"); + } + Ok(Self { + block_size: u64::from(block_size), + frames_per_block, + max_blocks_per_packet: frames_per_block.max(MAX_FRAMES_PER_PACKET) + / frames_per_block, + }) + } + + pub fn without_blocks(frame_len: u16) -> Self { + Self { + block_size: u64::from(frame_len), + frames_per_block: 1, + max_blocks_per_packet: MAX_FRAMES_PER_PACKET, + } + } + + pub fn is_empty(&self) -> bool { + self.block_size == 0 + } + + pub fn get_max_frames_per_packet(&self) -> u64 { + self.max_blocks_per_packet * self.frames_per_block + } + + pub fn get_frames(&self, data_len: u64) -> u64 { + data_len / self.block_size * self.frames_per_block + } + + pub fn get_actual_ts(&self, ts: u64) -> u64 { + let max_frames_per_packet = self.get_max_frames_per_packet(); + ts / max_frames_per_packet * max_frames_per_packet + } +} + +pub fn next_packet( + reader: &mut MediaSourceStream, packet_info: &PacketInfo, tracks: &Vec, + data_start_pos: u64, data_end_pos: u64 + ) -> Result { + let pos = reader.pos(); + if tracks.is_empty() { + return decode_error("riff: no tracks"); + } + if packet_info.is_empty() { + return decode_error("riff: block size is 0"); + } + + // Determine the number of complete blocks remaining in the data chunk. + let num_blocks_left = if pos < data_end_pos { + (data_end_pos - pos) / packet_info.block_size + } + else { + 0 + }; + + if num_blocks_left == 0 { + return end_of_stream_error(); + } + + let blocks_per_packet = num_blocks_left.min(packet_info.max_blocks_per_packet); + + let dur = blocks_per_packet * packet_info.frames_per_block; + let packet_len = blocks_per_packet * packet_info.block_size; + + // Copy the frames. + let packet_buf = reader.read_boxed_slice(packet_len as usize)?; + + // The packet timestamp is the position of the first byte of the first frame in the + // packet relative to the start of the data chunk divided by the length per frame. + let pts = packet_info.get_frames(pos - data_start_pos); + + Ok(Packet::new_from_boxed_slice(0, pts, dur, packet_buf)) +} + +/// TODO: format here refers to format chunk in Wave terminology, but the data being handled here is generic - find a better name, or combine with append_data_params +pub fn append_format_params(codec_params: &mut CodecParameters, format_data: &FormatData, sample_rate: u32) { + codec_params + .with_sample_rate(sample_rate) + .with_time_base(TimeBase::new(1, sample_rate)); + + match format_data { + FormatData::Pcm(pcm) => { + codec_params + .for_codec(pcm.codec) + .with_bits_per_coded_sample(u32::from(pcm.bits_per_sample)) + .with_bits_per_sample(u32::from(pcm.bits_per_sample)) + .with_channels(pcm.channels); + } + FormatData::Adpcm(adpcm) => { + codec_params.for_codec(adpcm.codec).with_channels(adpcm.channels); + } + FormatData::IeeeFloat(ieee) => { + codec_params.for_codec(ieee.codec).with_channels(ieee.channels); + } + FormatData::Extensible(ext) => { + codec_params + .for_codec(ext.codec) + .with_bits_per_coded_sample(u32::from(ext.bits_per_coded_sample)) + .with_bits_per_sample(u32::from(ext.bits_per_sample)) + .with_channels(ext.channels); + } + FormatData::ALaw(alaw) => { + codec_params.for_codec(alaw.codec).with_channels(alaw.channels); + } + FormatData::MuLaw(mulaw) => { + codec_params.for_codec(mulaw.codec).with_channels(mulaw.channels); + } + } +} + +/// TODO: format here refers to format chunk in Wave terminology, but the data being handled here is generic - find a better name, or combine with append_data_params append_format_params +pub fn append_data_params( + codec_params: &mut CodecParameters, + data_len: u64, + packet_info: &PacketInfo, +) { + if !packet_info.is_empty() { + //let n_frames = packet_info.get_frames(u64::from(data.len)); + let n_frames = packet_info.get_frames(data_len); + codec_params.with_n_frames(n_frames); + } +} \ No newline at end of file From 9263e8b0c352e166f6c5e1ae2bce05a6461863e8 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 23 Apr 2023 21:35:36 +0200 Subject: [PATCH 08/27] allow deadcode for logic in aiff module that will/can be used by wav --- symphonia-format-riff/src/riff.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 6bc4d6d9..cd7e79db 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -19,6 +19,7 @@ use symphonia_core::io::{MediaSourceStream, ReadBytes}; use log::info; +#[allow(dead_code)] pub enum ByteOrder { LittleEndian, BigEndian, @@ -113,6 +114,7 @@ impl ChunksReader { } } } + #[allow(dead_code)] pub fn finish(&mut self, reader: &mut B) -> Result<()> { // If data is remaining in this chunk, skip it. if self.consumed < self.len { @@ -152,6 +154,7 @@ impl ChunkParser

{ } } +#[allow(dead_code)] pub enum FormatData { Pcm(FormatPcm), Adpcm(FormatAdpcm), @@ -222,6 +225,7 @@ pub struct PacketInfo { } impl PacketInfo { + #[allow(dead_code)] pub fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { if frames_per_block == 0 { return decode_error("riff: frames per block is 0"); From 5953c47a1694f459b4c823c9058edfaa87bef2e5 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Tue, 25 Apr 2023 19:26:43 +0200 Subject: [PATCH 09/27] used proper formatting --- symphonia-format-riff/src/chunks.rs | 49 +++++++++++------------------ symphonia-format-riff/src/lib.rs | 28 ++++++++++++----- symphonia-format-riff/src/riff.rs | 35 ++++++++++----------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 0dfdd2a3..8293b97d 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -7,19 +7,17 @@ use std::fmt; -use crate::{PacketInfo, FormatData, FormatPcm, ParseChunkTag, ParseChunk, ChunkParser}; +use crate::{ChunkParser, FormatData, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag}; use symphonia_core::audio::Channels; use symphonia_core::codecs::{ - CODEC_TYPE_PCM_S8, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, + CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; - use extended::Extended; - /// `CommonChunk` is a required AIFF chunk, containing metadata. pub struct CommonChunk { /// The number of channels. @@ -35,15 +33,12 @@ pub struct CommonChunk { } impl CommonChunk { - fn read_pcm_fmt( - bits_per_sample: u16, - n_channels: u16, - ) -> Result { - // Bits per sample for PCM is both the encoded sample width, and the actual sample width. + fn read_pcm_fmt(bits_per_sample: u16, n_channels: u16) -> Result { + // Bits per sample for PCM is both the encoded sample width, and the actual sample width. // Strictly, this must either be 8 or 16 bits, but there is no reason why 24 and 32 bits // can't be supported. Since these files do exist, allow for 8/16/24/32-bit samples, but // error if not a multiple of 8 or greater than 32-bits. - // + // // It is possible though for AIFF to have a sample size not divisible by 8. // Data is left justified, with the remaining bits zeroed. Currently not supported. // @@ -54,11 +49,7 @@ impl CommonChunk { 16 => CODEC_TYPE_PCM_S16BE, 24 => CODEC_TYPE_PCM_S24BE, 32 => CODEC_TYPE_PCM_S32BE, - _ => { - return decode_error( - "aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits", - ) - } + _ => return decode_error("aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits"), }; // The PCM format only supports 1 or 2 channels, for mono and stereo channel layouts, @@ -70,7 +61,6 @@ impl CommonChunk { }; Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) - } pub(crate) fn packet_info(&self) -> Result { @@ -79,7 +69,7 @@ impl CommonChunk { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } - _=> todo!("other formats") + _ => todo!("other formats"), } } } @@ -89,16 +79,16 @@ impl ParseChunk for CommonChunk { let n_channels = reader.read_be_i16()?; let n_sample_frames = reader.read_be_u32()?; let sample_size = reader.read_be_i16()?; - + let mut sample_rate: [u8; 10] = [0; 10]; let _res = reader.read_buf_exact(sample_rate.as_mut())?; - - let sample_rate = Extended::from_be_bytes(sample_rate); + + let sample_rate = Extended::from_be_bytes(sample_rate); let sample_rate = sample_rate.to_f64() as u32; - - let format_data = Self::read_pcm_fmt(sample_size as u16, n_channels as u16); - - let format_data = match format_data{ + + let format_data = Self::read_pcm_fmt(sample_size as u16, n_channels as u16); + + let format_data = match format_data { Ok(data) => data, Err(e) => return Err(e), }; @@ -118,13 +108,12 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tbits_per_sample: {},", pcm.bits_per_sample)?; writeln!(f, "\t\tchannels: {},", pcm.channels)?; writeln!(f, "\t\tcodec: {},", pcm.codec)?; - }, + } //TODO: this is not optimal - _ => { + _ => { //TODO: this is not optimal.. writeln!(f, "\tdisplay not implemented for format")?; - } - + } }; writeln!(f, "\t}}")?; @@ -144,7 +133,7 @@ impl ParseChunk for SoundChunk { let offset = reader.read_be_u32()?; let block_size = reader.read_be_u32()?; - if offset != 0 || block_size != 0{ + if offset != 0 || block_size != 0 { return unsupported_error("riff: No support for AIFF block-aligned data"); } @@ -171,4 +160,4 @@ impl ParseChunkTag for RiffAiffChunks { _ => None, } } -} \ No newline at end of file +} diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 310fe4cc..31721360 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -58,8 +58,8 @@ impl QueryDescriptor for AiffReader { support_format!( "riff", " Resource Interchange File Format", - &["aiff", "aif", "aifc"], - &["audio/aiff", "audio/x-aiff", " sound/aiff", "audio/x-pn-aiff"], + &["aiff", "aif", "aifc"], + &["audio/aiff", "audio/x-aiff", " sound/aiff", "audio/x-pn-aiff"], &[b"FORM"] ), ] @@ -86,9 +86,11 @@ impl FormatReader for AiffReader { if riff_form == AIFF_RIFF_FORM { debug!("riff form is aiff"); - } else if riff_form == AIFC_RIFF_FORM { + } + else if riff_form == AIFC_RIFF_FORM { return unsupported_error("aiff: No support for aifc files"); - } else { + } + else { error!("riff form is not supported ({})", String::from_utf8_lossy(&riff_form)); return unsupported_error("aiff: riff form is not supported"); } @@ -104,7 +106,7 @@ impl FormatReader for AiffReader { let chunk = riff_chunks.next(&mut source)?; // The last chunk should always be a data chunk, if it is not, then the stream is - // unsupported. + // unsupported. // TODO: According to the spec additional chunks can be added after the sound data chunk. In fact any order can be possible. if chunk.is_none() { return unsupported_error("aiff: missing data chunk"); @@ -122,7 +124,11 @@ impl FormatReader for AiffReader { .with_frames_per_block(packet_info.frames_per_block); // Append Format chunk fields to codec parameters. - append_format_params(&mut codec_params, &common.format_data, common.sample_rate); + append_format_params( + &mut codec_params, + &common.format_data, + common.sample_rate, + ); } RiffAiffChunks::Sound(dat) => { let data = dat.parse(&mut source)?; @@ -150,7 +156,13 @@ impl FormatReader for AiffReader { } fn next_packet(&mut self) -> Result { - next_packet(&mut self.reader, &self.packet_info, &self.tracks, self.data_start_pos, self.data_end_pos) + next_packet( + &mut self.reader, + &self.packet_info, + &self.tracks, + self.data_start_pos, + self.data_end_pos, + ) } fn metadata(&mut self) -> Metadata<'_> { @@ -234,4 +246,4 @@ impl FormatReader for AiffReader { fn into_inner(self: Box) -> MediaSourceStream { self.reader } -} \ No newline at end of file +} diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index cd7e79db..91b44c48 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -7,7 +7,6 @@ /// `PacketInfo` helps to simulate packetization over a number of blocks of data. /// In case the codec is blockless the block size equals one full audio frame in bytes. - use std::marker::PhantomData; use symphonia_core::audio::Channels; @@ -81,7 +80,7 @@ impl ChunksReader { ByteOrder::LittleEndian => reader.read_u32()?, ByteOrder::BigEndian => reader.read_be_u32()?, }; - + self.consumed += 8; // Check if the ChunkReader has enough unread bytes to fully read the chunk. @@ -233,8 +232,7 @@ impl PacketInfo { Ok(Self { block_size: u64::from(block_size), frames_per_block, - max_blocks_per_packet: frames_per_block.max(MAX_FRAMES_PER_PACKET) - / frames_per_block, + max_blocks_per_packet: frames_per_block.max(MAX_FRAMES_PER_PACKET) / frames_per_block, }) } @@ -265,9 +263,12 @@ impl PacketInfo { } pub fn next_packet( - reader: &mut MediaSourceStream, packet_info: &PacketInfo, tracks: &Vec, - data_start_pos: u64, data_end_pos: u64 - ) -> Result { + reader: &mut MediaSourceStream, + packet_info: &PacketInfo, + tracks: &Vec, + data_start_pos: u64, + data_end_pos: u64, +) -> Result { let pos = reader.pos(); if tracks.is_empty() { return decode_error("riff: no tracks"); @@ -277,12 +278,8 @@ pub fn next_packet( } // Determine the number of complete blocks remaining in the data chunk. - let num_blocks_left = if pos < data_end_pos { - (data_end_pos - pos) / packet_info.block_size - } - else { - 0 - }; + let num_blocks_left = + if pos < data_end_pos { (data_end_pos - pos) / packet_info.block_size } else { 0 }; if num_blocks_left == 0 { return end_of_stream_error(); @@ -304,10 +301,12 @@ pub fn next_packet( } /// TODO: format here refers to format chunk in Wave terminology, but the data being handled here is generic - find a better name, or combine with append_data_params -pub fn append_format_params(codec_params: &mut CodecParameters, format_data: &FormatData, sample_rate: u32) { - codec_params - .with_sample_rate(sample_rate) - .with_time_base(TimeBase::new(1, sample_rate)); +pub fn append_format_params( + codec_params: &mut CodecParameters, + format_data: &FormatData, + sample_rate: u32, +) { + codec_params.with_sample_rate(sample_rate).with_time_base(TimeBase::new(1, sample_rate)); match format_data { FormatData::Pcm(pcm) => { @@ -350,4 +349,4 @@ pub fn append_data_params( let n_frames = packet_info.get_frames(data_len); codec_params.with_n_frames(n_frames); } -} \ No newline at end of file +} From cfa85e5eb19cd209afbb2e63383d73468ec992f5 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Tue, 25 Apr 2023 19:37:13 +0200 Subject: [PATCH 10/27] replaced todo! with unsupported error + removed double comment --- symphonia-format-riff/src/chunks.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 8293b97d..e873fd29 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -63,13 +63,13 @@ impl CommonChunk { Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } - pub(crate) fn packet_info(&self) -> Result { + pub fn packet_info(&self) -> Result { match &self.format_data { FormatData::Pcm(_) => { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } - _ => todo!("other formats"), + _ => return unsupported_error("aiff: packet info not implemented for format"), } } } @@ -109,7 +109,6 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tchannels: {},", pcm.channels)?; writeln!(f, "\t\tcodec: {},", pcm.codec)?; } - //TODO: this is not optimal _ => { //TODO: this is not optimal.. writeln!(f, "\tdisplay not implemented for format")?; From 3684e06a69da0a14076525e8f376f416ea09b047 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Wed, 26 Apr 2023 21:56:13 +0200 Subject: [PATCH 11/27] aifc support --- symphonia-format-riff/src/chunks.rs | 63 ++++++++++++++++++++++++++++- symphonia-format-riff/src/lib.rs | 29 ++++++++----- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index e873fd29..1764c360 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -14,7 +14,7 @@ use symphonia_core::codecs::{ CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; -use symphonia_core::io::ReadBytes; +use symphonia_core::io::{ReadBytes, MediaSourceStream}; use extended::Extended; @@ -30,6 +30,10 @@ pub struct CommonChunk { pub sample_rate: u32, /// Extra data associated with the format block conditional upon the format tag. pub format_data: FormatData, + /// The compression ID, only exists for aifc + pub compression_type: [u8; 4], + /// The compression name, only exists for aifc + pub compression_name: String, } impl CommonChunk { @@ -92,7 +96,13 @@ impl ParseChunk for CommonChunk { Ok(data) => data, Err(e) => return Err(e), }; - Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data }) + + // These fields only exist for AIFC. + // AIFF files are threated as AIFC files with no compression + let compression_type = *b"NONE"; + let compression_name = "not compressed".to_string(); + + Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data, compression_type, compression_name }) } } @@ -120,6 +130,55 @@ impl fmt::Display for CommonChunk { } } +pub trait CommonChunkParser { + fn parse_aiff(self, source: &mut MediaSourceStream) -> Result; + fn parse_aifc(self, source: &mut MediaSourceStream) -> Result; +} + +impl CommonChunkParser for ChunkParser { + fn parse_aiff(self, source: &mut MediaSourceStream) -> Result { + self.parse(source) + } + fn parse_aifc(self, source: &mut MediaSourceStream) -> Result { + let n_channels = source.read_be_i16()?; + let n_sample_frames = source.read_be_u32()?; + let sample_size = source.read_be_i16()?; + + let mut sample_rate: [u8; 10] = [0; 10]; + let _res = source.read_buf_exact(sample_rate.as_mut())?; + + let sample_rate = Extended::from_be_bytes(sample_rate); + let sample_rate = sample_rate.to_f64() as u32; + + let compression_type = source.read_quad_bytes()?; + let str_len = source.read_byte()?; + println!("{}, {}", String::from_utf8_lossy(&compression_type) , str_len); + + let mut text_buf = vec![0u8; str_len as usize]; + source.read_buf_exact(&mut text_buf)?; + let compression_name : String = text_buf.iter().map(|&c| char::from(c)).collect(); + println!("{}", compression_name); + + // Pad byte is not reflected in the len + if str_len % 2 != 0{ + match source.ignore_bytes(1){ + Ok(_) => {}, + Err(_) => {return unsupported_error("aifc: Missing data..");} + } + } + + print!("Compression type: {}, Compression name: {}", String::from_utf8_lossy(&compression_type), compression_name); + todo!("Use different read depending on compression type"); + let format_data = CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16); + + let format_data = match format_data { + Ok(data) => data, + Err(e) => return Err(e), + }; + Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data, compression_type, compression_name }) + } +} + /// `SoundChunk` is a required AIFF chunk, containing the audio data. pub struct SoundChunk { pub len: u32, diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 31721360..c2b51843 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -41,6 +41,11 @@ const AIFF_RIFF_FORM: [u8; 4] = *b"AIFF"; /// A possible RIFF form is "aifc", using compressed data. const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; +enum AiffType { + Aiff, + Aifc, +} + pub struct AiffReader { reader: MediaSourceStream, tracks: Vec, @@ -84,16 +89,11 @@ impl FormatReader for AiffReader { let riff_len = source.read_be_u32()?; let riff_form = source.read_quad_bytes()?; - if riff_form == AIFF_RIFF_FORM { - debug!("riff form is aiff"); - } - else if riff_form == AIFC_RIFF_FORM { - return unsupported_error("aiff: No support for aifc files"); - } - else { - error!("riff form is not supported ({})", String::from_utf8_lossy(&riff_form)); - return unsupported_error("aiff: riff form is not supported"); - } + let riff_form = match riff_form { + AIFF_RIFF_FORM => AiffType::Aiff, + AIFC_RIFF_FORM => AiffType::Aifc, + _ => return unsupported_error("aiff: riff form is not supported"), + }; let mut riff_chunks = ChunksReader::::new(riff_len, ByteOrder::BigEndian); @@ -114,7 +114,14 @@ impl FormatReader for AiffReader { match chunk.unwrap() { RiffAiffChunks::Common(common) => { - let common = common.parse(&mut source)?; + let common = match riff_form{ + AiffType::Aiff => { + common.parse_aiff(&mut source)? + } + AiffType::Aifc => { + common.parse_aifc(&mut source)? + } + }; // The Format chunk contains the block_align field and possible additional information // to handle packetization and seeking. From 090b3328c1801f7c6f2a22748b1d8432013176f8 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Fri, 28 Apr 2023 22:07:35 +0200 Subject: [PATCH 12/27] added wav to riff - unexported --- .../src/{chunks.rs => aiff_chunks.rs} | 4 +- symphonia-format-riff/src/lib.rs | 233 ++++++- symphonia-format-riff/src/riff.rs | 4 - symphonia-format-riff/src/wav_chunks.rs | 635 ++++++++++++++++++ 4 files changed, 868 insertions(+), 8 deletions(-) rename symphonia-format-riff/src/{chunks.rs => aiff_chunks.rs} (100%) create mode 100644 symphonia-format-riff/src/wav_chunks.rs diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs similarity index 100% rename from symphonia-format-riff/src/chunks.rs rename to symphonia-format-riff/src/aiff_chunks.rs index e873fd29..99f24bd5 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -7,8 +7,6 @@ use std::fmt; -use crate::{ChunkParser, FormatData, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag}; - use symphonia_core::audio::Channels; use symphonia_core::codecs::{ CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, @@ -16,6 +14,8 @@ use symphonia_core::codecs::{ use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::ReadBytes; +use crate::{ChunkParser, FormatData, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag}; + use extended::Extended; /// `CommonChunk` is a required AIFF chunk, containing metadata. diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 31721360..303a5180 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -27,20 +27,28 @@ use symphonia_core::support_format; use log::{debug, error}; -mod chunks; -use chunks::*; +mod aiff_chunks; +use aiff_chunks::*; + +mod wav_chunks; +use wav_chunks::*; mod riff; use riff::*; /// Aiff is actually a RIFF stream, with a "FORM" ASCII stream marker. const AIFF_STREAM_MARKER: [u8; 4] = *b"FORM"; +/// WAVE is actually a RIFF stream, with a "RIFF" ASCII stream marker. +const WAVE_STREAM_MARKER: [u8; 4] = *b"RIFF"; /// A possible RIFF form is "aiff". const AIFF_RIFF_FORM: [u8; 4] = *b"AIFF"; /// A possible RIFF form is "aifc", using compressed data. const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; +/// A possible RIFF form is "wave". +const WAVE_RIFF_FORM: [u8; 4] = *b"WAVE"; + pub struct AiffReader { reader: MediaSourceStream, tracks: Vec, @@ -247,3 +255,224 @@ impl FormatReader for AiffReader { self.reader } } + +pub struct WavReader { + reader: MediaSourceStream, + tracks: Vec, + cues: Vec, + metadata: MetadataLog, + packet_info: PacketInfo, + data_start_pos: u64, + data_end_pos: u64, +} + +impl QueryDescriptor for WavReader { + fn query() -> &'static [Descriptor] { + &[ + // WAVE RIFF form + support_format!( + "wave", + "Waveform Audio File Format", + &["wav", "wave"], + &["audio/vnd.wave", "audio/x-wav", "audio/wav", "audio/wave"], + &[b"RIFF"] + ), + ] + } + + fn score(_context: &[u8]) -> u8 { + 255 + } +} + +impl FormatReader for WavReader { + fn try_new(mut source: MediaSourceStream, _options: &FormatOptions) -> Result { + // The RIFF marker should be present. + let marker = source.read_quad_bytes()?; + + if marker != WAVE_STREAM_MARKER { + return unsupported_error("wav: missing riff stream marker"); + } + + // A Wave file is one large RIFF chunk, with the actual meta and audio data as sub-chunks. + // Therefore, the header was the chunk ID, and the next 4 bytes is the length of the RIFF + // chunk. + let riff_len = source.read_u32()?; + let riff_form = source.read_quad_bytes()?; + + // The RIFF chunk contains WAVE data. + if riff_form != WAVE_RIFF_FORM { + error!("riff form is not wave ({})", String::from_utf8_lossy(&riff_form)); + + return unsupported_error("wav: riff form is not wave"); + } + + let mut riff_chunks = + ChunksReader::::new(riff_len, ByteOrder::LittleEndian); + + let mut codec_params = CodecParameters::new(); + let mut metadata: MetadataLog = Default::default(); + let mut packet_info = PacketInfo::without_blocks(0); + + loop { + let chunk = riff_chunks.next(&mut source)?; + + // The last chunk should always be a data chunk, if it is not, then the stream is + // unsupported. + if chunk.is_none() { + return unsupported_error("wav: missing data chunk"); + } + + match chunk.unwrap() { + RiffWaveChunks::Format(fmt) => { + let format = fmt.parse(&mut source)?; + + // The Format chunk contains the block_align field and possible additional information + // to handle packetization and seeking. + packet_info = format.packet_info()?; + codec_params + .with_max_frames_per_packet(packet_info.get_max_frames_per_packet()) + .with_frames_per_block(packet_info.frames_per_block); + + // Append Format chunk fields to codec parameters. + append_format_params( + &mut codec_params, + &format.format_data, + format.sample_rate, + ); + } + RiffWaveChunks::Fact(fct) => { + let fact = fct.parse(&mut source)?; + + // Append Fact chunk fields to codec parameters. + append_fact_params(&mut codec_params, &fact); + } + RiffWaveChunks::List(lst) => { + let list = lst.parse(&mut source)?; + + // Riff Lists can have many different forms, but WavReader only supports Info + // lists. + match &list.form { + b"INFO" => metadata.push(read_info_chunk(&mut source, list.len)?), + _ => list.skip(&mut source)?, + } + } + RiffWaveChunks::Data(dat) => { + let data = dat.parse(&mut source)?; + + // Record the bounds of the data chunk. + let data_start_pos = source.pos(); + let data_end_pos = data_start_pos + u64::from(data.len); + + // Append Data chunk fields to codec parameters. + append_data_params(&mut codec_params, data.len as u64, &packet_info); + + // Add a new track using the collected codec parameters. + return Ok(WavReader { + reader: source, + tracks: vec![Track::new(0, codec_params)], + cues: Vec::new(), + metadata, + packet_info, + data_start_pos, + data_end_pos, + }); + } + } + } + + // Chunks are processed until the Data chunk is found, or an error occurs. + } + + fn next_packet(&mut self) -> Result { + next_packet( + &mut self.reader, + &self.packet_info, + &self.tracks, + self.data_start_pos, + self.data_end_pos, + ) + } + + fn metadata(&mut self) -> Metadata<'_> { + self.metadata.metadata() + } + + fn cues(&self) -> &[Cue] { + &self.cues + } + + fn tracks(&self) -> &[Track] { + &self.tracks + } + + fn seek(&mut self, _mode: SeekMode, to: SeekTo) -> Result { + if self.tracks.is_empty() || self.packet_info.is_empty() { + return seek_error(SeekErrorKind::Unseekable); + } + + let params = &self.tracks[0].codec_params; + + let ts = match to { + // Frame timestamp given. + SeekTo::TimeStamp { ts, .. } => ts, + // Time value given, calculate frame timestamp from sample rate. + SeekTo::Time { time, .. } => { + // Use the sample rate to calculate the frame timestamp. If sample rate is not + // known, the seek cannot be completed. + if let Some(sample_rate) = params.sample_rate { + TimeBase::new(1, sample_rate).calc_timestamp(time) + } + else { + return seek_error(SeekErrorKind::Unseekable); + } + } + }; + + // If the total number of frames in the track is known, verify the desired frame timestamp + // does not exceed it. + if let Some(n_frames) = params.n_frames { + if ts > n_frames { + return seek_error(SeekErrorKind::OutOfRange); + } + } + + debug!("seeking to frame_ts={}", ts); + + // WAVE is not internally packetized for PCM codecs. Packetization is simulated by trying to + // read a constant number of samples or blocks every call to next_packet. Therefore, a packet begins + // wherever the data stream is currently positioned. Since timestamps on packets should be + // determinstic, instead of seeking to the exact timestamp requested and starting the next + // packet there, seek to a packet boundary. In this way, packets will have have the same + // timestamps regardless if the stream was seeked or not. + let actual_ts = self.packet_info.get_actual_ts(ts); + + // Calculate the absolute byte offset of the desired audio frame. + let seek_pos = self.data_start_pos + (actual_ts * self.packet_info.block_size); + + // If the reader supports seeking we can seek directly to the frame's offset wherever it may + // be. + if self.reader.is_seekable() { + self.reader.seek(SeekFrom::Start(seek_pos))?; + } + // If the reader does not support seeking, we can only emulate forward seeks by consuming + // bytes. If the reader has to seek backwards, return an error. + else { + let current_pos = self.reader.pos(); + if seek_pos >= current_pos { + self.reader.ignore_bytes(seek_pos - current_pos)?; + } + else { + return seek_error(SeekErrorKind::ForwardOnly); + } + } + + debug!("seeked to packet_ts={} (delta={})", actual_ts, actual_ts as i64 - ts as i64); + + Ok(SeekedTo { track_id: 0, actual_ts, required_ts: ts }) + } + + fn into_inner(self: Box) -> MediaSourceStream { + self.reader + } +} diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 91b44c48..e5eec84d 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -18,7 +18,6 @@ use symphonia_core::io::{MediaSourceStream, ReadBytes}; use log::info; -#[allow(dead_code)] pub enum ByteOrder { LittleEndian, BigEndian, @@ -113,7 +112,6 @@ impl ChunksReader { } } } - #[allow(dead_code)] pub fn finish(&mut self, reader: &mut B) -> Result<()> { // If data is remaining in this chunk, skip it. if self.consumed < self.len { @@ -153,7 +151,6 @@ impl ChunkParser

{ } } -#[allow(dead_code)] pub enum FormatData { Pcm(FormatPcm), Adpcm(FormatAdpcm), @@ -224,7 +221,6 @@ pub struct PacketInfo { } impl PacketInfo { - #[allow(dead_code)] pub fn with_blocks(block_size: u16, frames_per_block: u64) -> Result { if frames_per_block == 0 { return decode_error("riff: frames per block is 0"); diff --git a/symphonia-format-riff/src/wav_chunks.rs b/symphonia-format-riff/src/wav_chunks.rs new file mode 100644 index 00000000..16e7d84c --- /dev/null +++ b/symphonia-format-riff/src/wav_chunks.rs @@ -0,0 +1,635 @@ +// Symphonia +// Copyright (c) 2019-2022 The Project Symphonia Developers. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::fmt; + +use symphonia_core::audio::Channels; +use symphonia_core::codecs::CodecType; +use symphonia_core::codecs::{ + CODEC_TYPE_ADPCM_IMA_WAV, CODEC_TYPE_ADPCM_MS, CODEC_TYPE_PCM_ALAW, CODEC_TYPE_PCM_F32LE, + CODEC_TYPE_PCM_F64LE, CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16LE, CODEC_TYPE_PCM_S24LE, + CODEC_TYPE_PCM_S32LE, CODEC_TYPE_PCM_U8, +}; +use symphonia_core::errors::{decode_error, unsupported_error, Result}; +use symphonia_core::io::ReadBytes; +use symphonia_core::meta::{MetadataBuilder, MetadataRevision, Tag}; +use symphonia_metadata::riff; + +use crate::{ + ByteOrder, ChunkParser, ChunksReader, CodecParameters, FormatALaw, FormatAdpcm, FormatData, + FormatExtensible, FormatIeeeFloat, FormatMuLaw, FormatPcm, MediaSourceStream, NullChunks, + PacketInfo, ParseChunk, ParseChunkTag, +}; + +pub struct WaveFormatChunk { + /// The number of channels. + pub n_channels: u16, + /// The sample rate in Hz. For non-PCM formats, this value must be interpreted as per the + /// format's specifications. + pub sample_rate: u32, + /// The required average data rate required in bytes/second. For non-PCM formats, this value + /// must be interpreted as per the format's specifications. + pub avg_bytes_per_sec: u32, + /// The byte alignment of one audio frame. For PCM formats, this is equal to + /// `(n_channels * format_data.bits_per_sample) / 8`. For non-PCM formats, this value must be + /// interpreted as per the format's specifications. + pub block_align: u16, + /// Extra data associated with the format block conditional upon the format tag. + pub format_data: FormatData, +} + +impl WaveFormatChunk { + fn read_pcm_fmt( + reader: &mut B, + bits_per_sample: u16, + n_channels: u16, + len: u32, + ) -> Result { + // WaveFormat for a PCM format may be extended with an extra data length field followed by + // the extension data itself. Use the chunk length to determine if the format chunk is + // extended. + match len { + // Basic WavFormat struct, no extension. + 16 => (), + // WaveFormatEx with extension data length field present, but no extension data. + 18 => { + // Extension data length should be 0. + let _extension_len = reader.read_be_u16()?; + } + // WaveFormatEx with extension data length field present, and extension data. + 40 => { + // Extension data length should be either 0 or 22 (if valid data is present). + let _extension_len = reader.read_u16()?; + reader.ignore_bytes(22)?; + } + _ => return decode_error("wav: malformed fmt_pcm chunk"), + } + + // Bits per sample for PCM is both the encoded sample width, and the actual sample width. + // Strictly, this must either be 8 or 16 bits, but there is no reason why 24 and 32 bits + // can't be supported. Since these files do exist, allow for 8/16/24/32-bit samples, but + // error if not a multiple of 8 or greater than 32-bits. + // + // Select the appropriate codec using bits per sample. Samples are always interleaved and + // little-endian encoded for the PCM format. + let codec = match bits_per_sample { + 8 => CODEC_TYPE_PCM_U8, + 16 => CODEC_TYPE_PCM_S16LE, + 24 => CODEC_TYPE_PCM_S24LE, + 32 => CODEC_TYPE_PCM_S32LE, + _ => { + return decode_error( + "wav: bits per sample for fmt_pcm must be 8, 16, 24 or 32 bits", + ) + } + }; + + // The PCM format only supports 1 or 2 channels, for mono and stereo channel layouts, + // respectively. + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("wav: channel layout is not stereo or mono for fmt_pcm"), + }; + + Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) + } + + fn read_adpcm_fmt( + reader: &mut B, + bits_per_sample: u16, + n_channels: u16, + len: u32, + codec: CodecType, + ) -> Result { + if bits_per_sample != 4 { + return decode_error("wav: bits per sample for fmt_adpcm must be 4 bits"); + } + + // WaveFormatEx with extension data length field present and with atleast frames per block data. + if len < 20 { + return decode_error("wav: malformed fmt_adpcm chunk"); + } + + let extra_size = reader.read_u16()? as u64; + + match codec { + CODEC_TYPE_ADPCM_MS if extra_size < 32 => { + return decode_error("wav: malformed fmt_adpcm chunk"); + } + CODEC_TYPE_ADPCM_IMA_WAV if extra_size != 2 => { + return decode_error("wav: malformed fmt_adpcm chunk"); + } + _ => (), + } + reader.ignore_bytes(extra_size)?; + + // The ADPCM format only supports 1 or 2 channels, for mono and stereo channel layouts, + // respectively. + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("wav: channel layout is not stereo or mono for fmt_adpcm"), + }; + Ok(FormatData::Adpcm(FormatAdpcm { bits_per_sample, channels, codec })) + } + + fn read_ieee_fmt( + reader: &mut B, + bits_per_sample: u16, + n_channels: u16, + len: u32, + ) -> Result { + // WaveFormat for a IEEE format should not be extended, but it may still have an extra data + // length parameter. + if len == 18 { + let extra_size = reader.read_u16()?; + + if extra_size != 0 { + return decode_error("wav: extra data not expected for fmt_ieee chunk"); + } + } + else if len > 16 { + return decode_error("wav: malformed fmt_ieee chunk"); + } + + // Officially, only 32-bit floats are supported, but Symphonia can handle 64-bit floats. + // + // Select the appropriate codec using bits per sample. Samples are always interleaved and + // little-endian encoded for the IEEE Float format. + let codec = match bits_per_sample { + 32 => CODEC_TYPE_PCM_F32LE, + 64 => CODEC_TYPE_PCM_F64LE, + _ => return decode_error("wav: bits per sample for fmt_ieee must be 32 or 64 bits"), + }; + + // The IEEE format only supports 1 or 2 channels, for mono and stereo channel layouts, + // respectively. + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("wav: channel layout is not stereo or mono for fmt_ieee"), + }; + + Ok(FormatData::IeeeFloat(FormatIeeeFloat { channels, codec })) + } + + fn read_ext_fmt( + reader: &mut B, + bits_per_coded_sample: u16, + n_channels: u16, + len: u32, + ) -> Result { + // WaveFormat for the extensible format must be extended to 40 bytes in length. + if len < 40 { + return decode_error("wav: malformed fmt_ext chunk"); + } + + let extra_size = reader.read_u16()?; + + // The size of the extra data for the Extensible format is exactly 22 bytes. + if extra_size != 22 { + return decode_error("wav: extra data size not 22 bytes for fmt_ext chunk"); + } + + let bits_per_sample = reader.read_u16()?; + + // Bits per coded sample for extensible formats is the width per sample as stored in the + // stream. This must be a multiple of 8. + if (bits_per_coded_sample & 0x7) != 0 { + return decode_error("wav: bits per coded sample for fmt_ext must be a multiple of 8"); + } + + // Bits per sample indicates the number of valid bits in the encoded sample. The sample is + // encoded in a bits per coded sample width value, therefore the valid number of bits must + // be at most bits per coded sample long. + if bits_per_sample > bits_per_coded_sample { + return decode_error( + "wav: bits per sample must be <= bits per coded sample for fmt_ext", + ); + } + + let channel_mask = reader.read_u32()?; + + // The number of ones in the channel mask should match the number of channels. + if channel_mask.count_ones() != u32::from(n_channels) { + return decode_error("wav: channel mask mismatch with number of channels for fmt_ext"); + } + + // Try to map channels. + let channels = match Channels::from_bits(channel_mask) { + Some(channels) => channels, + _ => return unsupported_error("wav: too many channels in mask for fmt_ext"), + }; + + let mut sub_format_guid = [0u8; 16]; + reader.read_buf_exact(&mut sub_format_guid)?; + + // These GUIDs identifiy the format of the data chunks. These definitions can be found in + // ksmedia.h of the Microsoft Windows Platform SDK. + #[rustfmt::skip] + const KSDATAFORMAT_SUBTYPE_PCM: [u8; 16] = [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + ]; + // #[rustfmt::skip] + // const KSDATAFORMAT_SUBTYPE_ADPCM: [u8; 16] = [ + // 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + // 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + // ]; + #[rustfmt::skip] + const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: [u8; 16] = [ + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + ]; + #[rustfmt::skip] + const KSDATAFORMAT_SUBTYPE_ALAW: [u8; 16] = [ + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + ]; + #[rustfmt::skip] + const KSDATAFORMAT_SUBTYPE_MULAW: [u8; 16] = [ + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + ]; + + // Verify support based on the format GUID. + let codec = match sub_format_guid { + KSDATAFORMAT_SUBTYPE_PCM => { + // Only support up-to 32-bit integer samples. + if bits_per_coded_sample > 32 { + return decode_error( + "bits per sample for fmt_ext PCM sub-type must be <= 32 bits", + ); + } + + // Use bits per coded sample to select the codec to use. If bits per sample is less + // than the bits per coded sample, the codec will expand the sample during decode. + match bits_per_coded_sample { + 8 => CODEC_TYPE_PCM_U8, + 16 => CODEC_TYPE_PCM_S16LE, + 24 => CODEC_TYPE_PCM_S24LE, + 32 => CODEC_TYPE_PCM_S32LE, + _ => unreachable!(), + } + } + KSDATAFORMAT_SUBTYPE_IEEE_FLOAT => { + // IEEE floating formats do not support truncated sample widths. + if bits_per_sample != bits_per_coded_sample { + return decode_error( + "wav: bits per sample for fmt_ext IEEE sub-type must equal bits per coded sample" + ); + } + + // Select the appropriate codec based on the bits per coded sample. + match bits_per_coded_sample { + 32 => CODEC_TYPE_PCM_F32LE, + 64 => CODEC_TYPE_PCM_F64LE, + _ => { + return decode_error( + "wav: bits per sample for fmt_ext IEEE sub-type must be 32 or 64 bits", + ) + } + } + } + KSDATAFORMAT_SUBTYPE_ALAW => CODEC_TYPE_PCM_ALAW, + KSDATAFORMAT_SUBTYPE_MULAW => CODEC_TYPE_PCM_MULAW, + _ => return unsupported_error("wav: unsupported fmt_ext sub-type"), + }; + + Ok(FormatData::Extensible(FormatExtensible { + bits_per_sample, + bits_per_coded_sample, + channels, + sub_format_guid, + codec, + })) + } + + fn read_alaw_pcm_fmt( + reader: &mut B, + n_channels: u16, + len: u32, + ) -> Result { + if len != 18 { + return decode_error("wav: malformed fmt_alaw chunk"); + } + + let extra_size = reader.read_u16()?; + + if extra_size > 0 { + reader.ignore_bytes(u64::from(extra_size))?; + } + + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("wav: channel layout is not stereo or mono for fmt_alaw"), + }; + + Ok(FormatData::ALaw(FormatALaw { codec: CODEC_TYPE_PCM_ALAW, channels })) + } + + fn read_mulaw_pcm_fmt( + reader: &mut B, + n_channels: u16, + len: u32, + ) -> Result { + if len != 18 { + return decode_error("wav: malformed fmt_mulaw chunk"); + } + + let extra_size = reader.read_u16()?; + + if extra_size > 0 { + reader.ignore_bytes(u64::from(extra_size))?; + } + + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("wav: channel layout is not stereo or mono for fmt_mulaw"), + }; + + Ok(FormatData::MuLaw(FormatMuLaw { codec: CODEC_TYPE_PCM_MULAW, channels })) + } + + pub(crate) fn packet_info(&self) -> Result { + match self.format_data { + FormatData::Adpcm(FormatAdpcm { codec, bits_per_sample, .. }) + //| WaveFormatData::Extensible(WaveFormatExtensible { codec, bits_per_sample, .. }) + if codec == CODEC_TYPE_ADPCM_MS => + { + let frames_per_block = ((((self.block_align - (7 * self.n_channels)) * 8) + / (bits_per_sample * self.n_channels)) + + 2) as u64; + PacketInfo::with_blocks(self.block_align, frames_per_block) + } + FormatData::Adpcm(FormatAdpcm { codec, bits_per_sample, .. }) + if codec == CODEC_TYPE_ADPCM_IMA_WAV => + { + let frames_per_block = (((self.block_align - (4 * self.n_channels)) * 8) + / (bits_per_sample * self.n_channels) + + 1) as u64; + PacketInfo::with_blocks(self.block_align, frames_per_block) + } + _ => Ok(PacketInfo::without_blocks(self.block_align)), + } + } +} + +impl ParseChunk for WaveFormatChunk { + fn parse(reader: &mut B, _tag: [u8; 4], len: u32) -> Result { + // WaveFormat has a minimal length of 16 bytes. This may be extended with format specific + // data later. + if len < 16 { + return decode_error("wav: malformed fmt chunk"); + } + + let format = reader.read_u16()?; + let n_channels = reader.read_u16()?; + let sample_rate = reader.read_u32()?; + let avg_bytes_per_sec = reader.read_u32()?; + let block_align = reader.read_u16()?; + let bits_per_sample = reader.read_u16()?; + + // The definition of these format identifiers can be found in mmreg.h of the Microsoft + // Windows Platform SDK. + const WAVE_FORMAT_PCM: u16 = 0x0001; + const WAVE_FORMAT_ADPCM: u16 = 0x0002; + const WAVE_FORMAT_IEEE_FLOAT: u16 = 0x0003; + const WAVE_FORMAT_ALAW: u16 = 0x0006; + const WAVE_FORMAT_MULAW: u16 = 0x0007; + const WAVE_FORMAT_ADPCM_IMA: u16 = 0x0011; + const WAVE_FORMAT_EXTENSIBLE: u16 = 0xfffe; + + let format_data = match format { + // The PCM Wave Format + WAVE_FORMAT_PCM => Self::read_pcm_fmt(reader, bits_per_sample, n_channels, len), + // The Microsoft ADPCM Format + WAVE_FORMAT_ADPCM => { + Self::read_adpcm_fmt(reader, bits_per_sample, n_channels, len, CODEC_TYPE_ADPCM_MS) + } + // The IEEE Float Wave Format + WAVE_FORMAT_IEEE_FLOAT => Self::read_ieee_fmt(reader, bits_per_sample, n_channels, len), + // The Extensible Wave Format + WAVE_FORMAT_EXTENSIBLE => Self::read_ext_fmt(reader, bits_per_sample, n_channels, len), + // The Alaw Wave Format. + WAVE_FORMAT_ALAW => Self::read_alaw_pcm_fmt(reader, n_channels, len), + // The MuLaw Wave Format. + WAVE_FORMAT_MULAW => Self::read_mulaw_pcm_fmt(reader, n_channels, len), + // The IMA ADPCM Format + WAVE_FORMAT_ADPCM_IMA => Self::read_adpcm_fmt( + reader, + bits_per_sample, + n_channels, + len, + CODEC_TYPE_ADPCM_IMA_WAV, + ), + // Unsupported format. + _ => return unsupported_error("wav: unsupported wave format"), + }?; + + Ok(WaveFormatChunk { n_channels, sample_rate, avg_bytes_per_sec, block_align, format_data }) + } +} + +impl fmt::Display for WaveFormatChunk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "WaveFormatChunk {{")?; + writeln!(f, "\tn_channels: {},", self.n_channels)?; + writeln!(f, "\tsample_rate: {} Hz,", self.sample_rate)?; + writeln!(f, "\tavg_bytes_per_sec: {},", self.avg_bytes_per_sec)?; + writeln!(f, "\tblock_align: {},", self.block_align)?; + + match self.format_data { + FormatData::Pcm(ref pcm) => { + writeln!(f, "\tformat_data: Pcm {{")?; + writeln!(f, "\t\tbits_per_sample: {},", pcm.bits_per_sample)?; + writeln!(f, "\t\tchannels: {},", pcm.channels)?; + writeln!(f, "\t\tcodec: {},", pcm.codec)?; + } + FormatData::Adpcm(ref adpcm) => { + writeln!(f, "\tformat_data: Adpcm {{")?; + writeln!(f, "\t\tbits_per_sample: {},", adpcm.bits_per_sample)?; + writeln!(f, "\t\tchannels: {},", adpcm.channels)?; + writeln!(f, "\t\tcodec: {},", adpcm.codec)?; + } + FormatData::IeeeFloat(ref ieee) => { + writeln!(f, "\tformat_data: IeeeFloat {{")?; + writeln!(f, "\t\tchannels: {},", ieee.channels)?; + writeln!(f, "\t\tcodec: {},", ieee.codec)?; + } + FormatData::Extensible(ref ext) => { + writeln!(f, "\tformat_data: Extensible {{")?; + writeln!(f, "\t\tbits_per_sample: {},", ext.bits_per_sample)?; + writeln!(f, "\t\tbits_per_coded_sample: {},", ext.bits_per_coded_sample)?; + writeln!(f, "\t\tchannels: {},", ext.channels)?; + writeln!(f, "\t\tsub_format_guid: {:?},", &ext.sub_format_guid)?; + writeln!(f, "\t\tcodec: {},", ext.codec)?; + } + FormatData::ALaw(ref alaw) => { + writeln!(f, "\tformat_data: ALaw {{")?; + writeln!(f, "\t\tchannels: {},", alaw.channels)?; + writeln!(f, "\t\tcodec: {},", alaw.codec)?; + } + FormatData::MuLaw(ref mulaw) => { + writeln!(f, "\tformat_data: MuLaw {{")?; + writeln!(f, "\t\tchannels: {},", mulaw.channels)?; + writeln!(f, "\t\tcodec: {},", mulaw.codec)?; + } + }; + + writeln!(f, "\t}}")?; + writeln!(f, "}}") + } +} + +pub struct FactChunk { + pub n_frames: u32, +} + +impl ParseChunk for FactChunk { + fn parse(reader: &mut B, _tag: [u8; 4], len: u32) -> Result { + // A Fact chunk is exactly 4 bytes long, though there is some mystery as to whether there + // can be more fields in the chunk. + if len != 4 { + return decode_error("wav: malformed fact chunk"); + } + + Ok(FactChunk { n_frames: reader.read_u32()? }) + } +} + +impl fmt::Display for FactChunk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "FactChunk {{")?; + writeln!(f, "\tn_frames: {},", self.n_frames)?; + writeln!(f, "}}") + } +} + +pub struct ListChunk { + pub form: [u8; 4], + pub len: u32, +} + +impl ListChunk { + pub fn skip(&self, reader: &mut B) -> Result<()> { + ChunksReader::::new(self.len, ByteOrder::LittleEndian).finish(reader) + } +} + +impl ParseChunk for ListChunk { + fn parse(reader: &mut B, _tag: [u8; 4], len: u32) -> Result { + // A List chunk must contain atleast the list/form identifier. However, an empty list + // (len == 4) is permissible. + if len < 4 { + return decode_error("wav: malformed list chunk"); + } + + Ok(ListChunk { form: reader.read_quad_bytes()?, len: len - 4 }) + } +} + +impl fmt::Display for ListChunk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "ListChunk {{")?; + writeln!(f, "\tform: {},", String::from_utf8_lossy(&self.form))?; + writeln!(f, "\tlen: {},", self.len)?; + writeln!(f, "}}") + } +} + +pub struct InfoChunk { + pub tag: Tag, +} + +impl ParseChunk for InfoChunk { + fn parse(reader: &mut B, tag: [u8; 4], len: u32) -> Result { + // TODO: Apply limit. + let mut value_buf = vec![0u8; len as usize]; + reader.read_buf_exact(&mut value_buf)?; + + Ok(InfoChunk { tag: riff::parse(tag, &value_buf) }) + } +} + +pub struct DataChunk { + pub len: u32, +} + +impl ParseChunk for DataChunk { + fn parse(_: &mut B, _: [u8; 4], len: u32) -> Result { + Ok(DataChunk { len }) + } +} + +pub enum RiffWaveChunks { + Format(ChunkParser), + List(ChunkParser), + Fact(ChunkParser), + Data(ChunkParser), +} + +macro_rules! parser { + ($class:expr, $result:ty, $tag:expr, $len:expr) => { + Some($class(ChunkParser::<$result>::new($tag, $len))) + }; +} + +impl ParseChunkTag for RiffWaveChunks { + fn parse_tag(tag: [u8; 4], len: u32) -> Option { + match &tag { + b"fmt " => parser!(RiffWaveChunks::Format, WaveFormatChunk, tag, len), + b"LIST" => parser!(RiffWaveChunks::List, ListChunk, tag, len), + b"fact" => parser!(RiffWaveChunks::Fact, FactChunk, tag, len), + b"data" => parser!(RiffWaveChunks::Data, DataChunk, tag, len), + _ => None, + } + } +} + +pub enum RiffInfoListChunks { + Info(ChunkParser), +} + +impl ParseChunkTag for RiffInfoListChunks { + fn parse_tag(tag: [u8; 4], len: u32) -> Option { + // Right now it is assumed all list chunks are INFO chunks, but that's not really + // guaranteed. + // + // TODO: Actually validate that the chunk is an info chunk. + parser!(RiffInfoListChunks::Info, InfoChunk, tag, len) + } +} + +pub fn append_fact_params(codec_params: &mut CodecParameters, fact: &FactChunk) { + codec_params.with_n_frames(u64::from(fact.n_frames)); +} + +pub fn read_info_chunk(source: &mut MediaSourceStream, len: u32) -> Result { + let mut info_list = ChunksReader::::new(len, ByteOrder::LittleEndian); + + let mut metadata_builder = MetadataBuilder::new(); + + loop { + let chunk = info_list.next(source)?; + + if let Some(RiffInfoListChunks::Info(info)) = chunk { + let parsed_info = info.parse(source)?; + metadata_builder.add_tag(parsed_info.tag); + } + else { + break; + } + } + + info_list.finish(source)?; + + Ok(metadata_builder.metadata()) +} From 60d99c3cbb691e0b97cc335f5f7e513863c56dd4 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sat, 29 Apr 2023 21:08:02 +0200 Subject: [PATCH 13/27] changed implementation of reading aifc pascal string --- symphonia-format-riff/src/chunks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 1764c360..5da08b9d 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -154,9 +154,9 @@ impl CommonChunkParser for ChunkParser { let str_len = source.read_byte()?; println!("{}, {}", String::from_utf8_lossy(&compression_type) , str_len); - let mut text_buf = vec![0u8; str_len as usize]; - source.read_buf_exact(&mut text_buf)?; - let compression_name : String = text_buf.iter().map(|&c| char::from(c)).collect(); + let mut compression_name = vec![0; str_len as usize]; + source.read_buf(compression_name.as_mut())?; + let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); println!("{}", compression_name); // Pad byte is not reflected in the len From c278ced87d99d7127cd8813096071b9a6cf6d985 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sat, 29 Apr 2023 21:29:22 +0200 Subject: [PATCH 14/27] Can read aifc with no compression --- symphonia-format-riff/src/chunks.rs | 34 ++++++++++++++++++----------- symphonia-format-riff/src/riff.rs | 5 ++++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 5da08b9d..7af4f2b9 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -152,24 +152,32 @@ impl CommonChunkParser for ChunkParser { let compression_type = source.read_quad_bytes()?; let str_len = source.read_byte()?; - println!("{}, {}", String::from_utf8_lossy(&compression_type) , str_len); + //println!("{}, {}", String::from_utf8_lossy(&compression_type) , str_len); - let mut compression_name = vec![0; str_len as usize]; + let mut compression_name = vec![0; (str_len) as usize]; source.read_buf(compression_name.as_mut())?; let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); - println!("{}", compression_name); - + //println!("{}", compression_name); + + // According to the spec only uneven lengths are padded with a pad byte (not reflected in str_len), + // but i found only files with even lengths, and they included an additional empty byte... + source.ignore_bytes(1)?; // Pad byte is not reflected in the len - if str_len % 2 != 0{ - match source.ignore_bytes(1){ - Ok(_) => {}, - Err(_) => {return unsupported_error("aifc: Missing data..");} - } - } + // if str_len % 2 != 0{ + // match source.ignore_bytes(1){ + // Ok(_) => {}, + // Err(_) => {return unsupported_error("aifc: Missing data..");} + // } + // } + + println!("Compression type:{}, Compression name:{}, strlen:{}", String::from_utf8_lossy(&compression_type), compression_name, str_len); + + let format_data = match &compression_type { + b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), + _ => return unsupported_error("aifc: Compression type not implemented"), + }; - print!("Compression type: {}, Compression name: {}", String::from_utf8_lossy(&compression_type), compression_name); - todo!("Use different read depending on compression type"); - let format_data = CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16); + //let format_data = CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16); let format_data = match format_data { Ok(data) => data, diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 91b44c48..56c2950a 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -75,7 +75,10 @@ impl ChunksReader { // Read tag and len, the chunk header. let tag = reader.read_quad_bytes()?; - // TODO: this probably breaks on machine with big endian architecture + //let debug_str = String::from_utf8_lossy(&tag); + //println!("tag:{},{}", debug_str, debug_str.len()); + + // TODO: this could break on machine with big endian architecture, gotta think about it lol let len = match self.byte_order { ByteOrder::LittleEndian => reader.read_u32()?, ByteOrder::BigEndian => reader.read_be_u32()?, From 3ad8de8001c9e76aded409c7cbba31a4e1182bb3 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Tue, 2 May 2023 19:44:49 +0200 Subject: [PATCH 15/27] can play mulaw compressed aifc --- symphonia-format-riff/src/chunks.rs | 60 +++++++++++++++++++++-------- symphonia-format-riff/src/lib.rs | 12 ++---- symphonia-format-riff/src/riff.rs | 25 +++--------- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/symphonia-format-riff/src/chunks.rs b/symphonia-format-riff/src/chunks.rs index 7af4f2b9..0ed46ba7 100644 --- a/symphonia-format-riff/src/chunks.rs +++ b/symphonia-format-riff/src/chunks.rs @@ -7,14 +7,17 @@ use std::fmt; -use crate::{ChunkParser, FormatData, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag}; +use crate::{ + ChunkParser, FormatData, FormatMuLaw, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag, +}; use symphonia_core::audio::Channels; use symphonia_core::codecs::{ - CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, + CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, + CODEC_TYPE_PCM_S8, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; -use symphonia_core::io::{ReadBytes, MediaSourceStream}; +use symphonia_core::io::{MediaSourceStream, ReadBytes}; use extended::Extended; @@ -67,12 +70,27 @@ impl CommonChunk { Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } + fn read_mulaw_pcm_fmt(n_channels: u16) -> Result { + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("afc: channel layout is not stereo or mono for fmt_mulaw"), + }; + + Ok(FormatData::MuLaw(FormatMuLaw { codec: CODEC_TYPE_PCM_MULAW, channels })) + } + pub fn packet_info(&self) -> Result { match &self.format_data { FormatData::Pcm(_) => { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } + FormatData::MuLaw(_) => { + // In mu-law encoding, each audio sample is represented by an 8-bit value that has been compressed + let block_align = self.n_channels; + Ok(PacketInfo::without_blocks(block_align as u16)) + } _ => return unsupported_error("aiff: packet info not implemented for format"), } } @@ -102,7 +120,15 @@ impl ParseChunk for CommonChunk { let compression_type = *b"NONE"; let compression_name = "not compressed".to_string(); - Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data, compression_type, compression_name }) + Ok(CommonChunk { + n_channels, + n_sample_frames, + sample_size, + sample_rate, + format_data, + compression_type, + compression_name, + }) } } @@ -152,14 +178,13 @@ impl CommonChunkParser for ChunkParser { let compression_type = source.read_quad_bytes()?; let str_len = source.read_byte()?; - //println!("{}, {}", String::from_utf8_lossy(&compression_type) , str_len); - + let mut compression_name = vec![0; (str_len) as usize]; - source.read_buf(compression_name.as_mut())?; + source.read_buf_exact(compression_name.as_mut())?; let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); - //println!("{}", compression_name); - - // According to the spec only uneven lengths are padded with a pad byte (not reflected in str_len), + //println!("Compression {}, {}", String::from_utf8_lossy(&compression_type), compression_name); + + // According to the spec only uneven lengths are padded with a pad byte (not reflected in str_len), // but i found only files with even lengths, and they included an additional empty byte... source.ignore_bytes(1)?; // Pad byte is not reflected in the len @@ -170,20 +195,25 @@ impl CommonChunkParser for ChunkParser { // } // } - println!("Compression type:{}, Compression name:{}, strlen:{}", String::from_utf8_lossy(&compression_type), compression_name, str_len); - let format_data = match &compression_type { b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), + b"ulaw" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), _ => return unsupported_error("aifc: Compression type not implemented"), }; - //let format_data = CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16); - let format_data = match format_data { Ok(data) => data, Err(e) => return Err(e), }; - Ok(CommonChunk { n_channels, n_sample_frames, sample_size, sample_rate, format_data, compression_type, compression_name }) + Ok(CommonChunk { + n_channels, + n_sample_frames, + sample_size, + sample_rate, + format_data, + compression_type, + compression_name, + }) } } diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index c2b51843..b327e17c 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -25,7 +25,7 @@ use symphonia_core::meta::{Metadata, MetadataLog}; use symphonia_core::probe::{Descriptor, Instantiate, QueryDescriptor}; use symphonia_core::support_format; -use log::{debug, error}; +use log::debug; mod chunks; use chunks::*; @@ -114,13 +114,9 @@ impl FormatReader for AiffReader { match chunk.unwrap() { RiffAiffChunks::Common(common) => { - let common = match riff_form{ - AiffType::Aiff => { - common.parse_aiff(&mut source)? - } - AiffType::Aifc => { - common.parse_aifc(&mut source)? - } + let common = match riff_form { + AiffType::Aiff => common.parse_aiff(&mut source)?, + AiffType::Aifc => common.parse_aifc(&mut source)?, }; // The Format chunk contains the block_align field and possible additional information diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 56c2950a..ce21c927 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -75,9 +75,7 @@ impl ChunksReader { // Read tag and len, the chunk header. let tag = reader.read_quad_bytes()?; - //let debug_str = String::from_utf8_lossy(&tag); - //println!("tag:{},{}", debug_str, debug_str.len()); - + // TODO: this could break on machine with big endian architecture, gotta think about it lol let len = match self.byte_order { ByteOrder::LittleEndian => reader.read_u32()?, @@ -142,7 +140,7 @@ pub trait ParseChunk: Sized { /// `ChunkParser` is a utility struct for unifying the parsing of chunks. pub struct ChunkParser { tag: [u8; 4], - len: u32, + pub len: u32, phantom: PhantomData

, } @@ -319,25 +317,12 @@ pub fn append_format_params( .with_bits_per_sample(u32::from(pcm.bits_per_sample)) .with_channels(pcm.channels); } - FormatData::Adpcm(adpcm) => { - codec_params.for_codec(adpcm.codec).with_channels(adpcm.channels); - } - FormatData::IeeeFloat(ieee) => { - codec_params.for_codec(ieee.codec).with_channels(ieee.channels); - } - FormatData::Extensible(ext) => { - codec_params - .for_codec(ext.codec) - .with_bits_per_coded_sample(u32::from(ext.bits_per_coded_sample)) - .with_bits_per_sample(u32::from(ext.bits_per_sample)) - .with_channels(ext.channels); - } - FormatData::ALaw(alaw) => { - codec_params.for_codec(alaw.codec).with_channels(alaw.channels); - } FormatData::MuLaw(mulaw) => { codec_params.for_codec(mulaw.codec).with_channels(mulaw.channels); } + _ => { + unimplemented!("riff: format not supported"); + } } } From 14a8fda121aa6d5c993ce63db224a28dc20dbf39 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Wed, 3 May 2023 18:04:41 +0200 Subject: [PATCH 16/27] can play float aiff --- symphonia-format-riff/src/aiff_chunks.rs | 46 +++++++++++++++++------- symphonia-format-riff/src/lib.rs | 2 +- symphonia-format-riff/src/riff.rs | 10 +++++- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index ed3a58db..9130fb4e 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -9,13 +9,16 @@ use std::fmt; use symphonia_core::audio::Channels; use symphonia_core::codecs::{ - CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, - CODEC_TYPE_PCM_S8, + CODEC_TYPE_PCM_F32BE, CODEC_TYPE_PCM_F64BE, CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16BE, + CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::{MediaSourceStream, ReadBytes}; -use crate::{ChunkParser, FormatData, FormatPcm, FormatMuLaw, PacketInfo, ParseChunk, ParseChunkTag}; +use crate::{ + ChunkParser, FormatData, FormatIeeeFloat, FormatMuLaw, FormatPcm, PacketInfo, ParseChunk, + ParseChunkTag, +}; use extended::Extended; @@ -78,6 +81,24 @@ impl CommonChunk { Ok(FormatData::MuLaw(FormatMuLaw { codec: CODEC_TYPE_PCM_MULAW, channels })) } + fn read_ieee_fmt(bits_per_sample: u16, n_channels: u16) -> Result { + // Select the appropriate codec using bits per sample. Samples are always interleaved and + // little-endian encoded for the IEEE Float format. + let codec = match bits_per_sample { + 32 => CODEC_TYPE_PCM_F32BE, + 64 => CODEC_TYPE_PCM_F64BE, + _ => return decode_error("aifc: bits per sample for fmt_ieee must be 32 or 64 bits"), + }; + + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("aifc: channel layout is not stereo or mono for fmt_ieee"), + }; + + Ok(FormatData::IeeeFloat(FormatIeeeFloat { channels, codec })) + } + pub fn packet_info(&self) -> Result { match &self.format_data { FormatData::Pcm(_) => { @@ -89,6 +110,10 @@ impl CommonChunk { let block_align = self.n_channels; Ok(PacketInfo::without_blocks(block_align as u16)) } + FormatData::IeeeFloat(_) => { + let block_align = self.n_channels * self.sample_size / 8; + Ok(PacketInfo::without_blocks(block_align as u16)) + } _ => return unsupported_error("aiff: packet info not implemented for format"), } } @@ -182,20 +207,15 @@ impl CommonChunkParser for ChunkParser { let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); //println!("Compression {}, {}", String::from_utf8_lossy(&compression_type), compression_name); - // According to the spec only uneven lengths are padded with a pad byte (not reflected in str_len), - // but i found only files with even lengths, and they included an additional empty byte... - source.ignore_bytes(1)?; - // Pad byte is not reflected in the len - // if str_len % 2 != 0{ - // match source.ignore_bytes(1){ - // Ok(_) => {}, - // Err(_) => {return unsupported_error("aifc: Missing data..");} - // } - // } + // Total number of bytes in pascal string must be even, since len is excluded from our var, we add 1 + if (str_len + 1) % 2 != 0 { + source.ignore_bytes(1)?; + } let format_data = match &compression_type { b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), b"ulaw" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), + b"fl32" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), _ => return unsupported_error("aifc: Compression type not implemented"), }; diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 1a94c741..116a8d73 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -49,7 +49,7 @@ const AIFC_RIFF_FORM: [u8; 4] = *b"AIFC"; /// A possible RIFF form is "wave". const WAVE_RIFF_FORM: [u8; 4] = *b"WAVE"; -enum AiffType{ +enum AiffType { Aiff, Aifc, } diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 998b8cb2..7fbc037a 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -16,7 +16,7 @@ use symphonia_core::errors::{decode_error, end_of_stream_error, Result}; use symphonia_core::formats::prelude::*; use symphonia_core::io::{MediaSourceStream, ReadBytes}; -use log::info; +use log::{debug, info}; pub enum ByteOrder { LittleEndian, @@ -74,6 +74,7 @@ impl ChunksReader { // Read tag and len, the chunk header. let tag = reader.read_quad_bytes()?; + //println!( "tag: {}", String::from_utf8_lossy(&tag)); // TODO: this could break on machine with big endian architecture, gotta think about it lol let len = match self.byte_order { @@ -91,6 +92,10 @@ impl ChunksReader { // When ffmpeg encodes wave to stdout the riff (parent) and data chunk lengths are // (2^32)-1 since the size can't be known ahead of time. if !(self.len == len && len == u32::MAX) { + debug!( + "chunk length of {} exceeds parent (list) chunk length", + String::from_utf8_lossy(&tag) + ); return decode_error("riff: chunk length exceeds parent (list) chunk length"); } } @@ -316,6 +321,9 @@ pub fn append_format_params( FormatData::MuLaw(mulaw) => { codec_params.for_codec(mulaw.codec).with_channels(mulaw.channels); } + FormatData::IeeeFloat(ieee) => { + codec_params.for_codec(ieee.codec).with_channels(ieee.channels); + } _ => { unimplemented!("riff: format not supported"); } From db01e59249a2e13843c3ba8cc0fefafe3f2352de Mon Sep 17 00:00:00 2001 From: dedobbin Date: Wed, 3 May 2023 18:17:20 +0200 Subject: [PATCH 17/27] can read ieee float 64bit aifc --- symphonia-format-riff/src/aiff_chunks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 9130fb4e..3932b1e9 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -216,6 +216,7 @@ impl CommonChunkParser for ChunkParser { b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), b"ulaw" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), b"fl32" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), + b"fl64" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), _ => return unsupported_error("aifc: Compression type not implemented"), }; From 771fbb8e7d2db2dcc5ac55b5f7773acafb0ccf76 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Thu, 4 May 2023 17:55:09 +0200 Subject: [PATCH 18/27] implement missing formats for aiff CommonChunk fmt::Display --- symphonia-format-riff/src/aiff_chunks.rs | 14 ++++++++++++-- symphonia-format-riff/src/riff.rs | 1 - 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 3932b1e9..521ac47a 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -168,8 +168,18 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tchannels: {},", pcm.channels)?; writeln!(f, "\t\tcodec: {},", pcm.codec)?; } + FormatData::MuLaw(ref mulaw) => { + writeln!(f, "\tformat_data: MuLaw {{")?; + writeln!(f, "\t\tchannels: {},", mulaw.channels)?; + writeln!(f, "\t\tcodec: {},", mulaw.codec)?; + } + FormatData::IeeeFloat(ref ieee) => { + writeln!(f, "\tformat_data: IeeeFloat {{")?; + writeln!(f, "\t\tchannels: {},", ieee.channels)?; + writeln!(f, "\t\tcodec: {},", ieee.codec)?; + } _ => { - //TODO: this is not optimal.. + //TODO: this is not optimal, but since no other aiff formats are support it should never be reached anyway.. writeln!(f, "\tdisplay not implemented for format")?; } }; @@ -205,7 +215,6 @@ impl CommonChunkParser for ChunkParser { let mut compression_name = vec![0; (str_len) as usize]; source.read_buf_exact(compression_name.as_mut())?; let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); - //println!("Compression {}, {}", String::from_utf8_lossy(&compression_type), compression_name); // Total number of bytes in pascal string must be even, since len is excluded from our var, we add 1 if (str_len + 1) % 2 != 0 { @@ -224,6 +233,7 @@ impl CommonChunkParser for ChunkParser { Ok(data) => data, Err(e) => return Err(e), }; + Ok(CommonChunk { n_channels, n_sample_frames, diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index 7fbc037a..b32d5365 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -74,7 +74,6 @@ impl ChunksReader { // Read tag and len, the chunk header. let tag = reader.read_quad_bytes()?; - //println!( "tag: {}", String::from_utf8_lossy(&tag)); // TODO: this could break on machine with big endian architecture, gotta think about it lol let len = match self.byte_order { From 50a5b91bc88e7edb411104881654d4efe4a30e7a Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 7 May 2023 19:18:41 +0200 Subject: [PATCH 19/27] can read alaw aifc --- symphonia-format-riff/src/aiff_chunks.rs | 47 +++++++++++++++++++----- symphonia-format-riff/src/riff.rs | 10 ++++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 521ac47a..aa17068a 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -9,15 +9,15 @@ use std::fmt; use symphonia_core::audio::Channels; use symphonia_core::codecs::{ - CODEC_TYPE_PCM_F32BE, CODEC_TYPE_PCM_F64BE, CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16BE, - CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, + CODEC_TYPE_PCM_ALAW, CODEC_TYPE_PCM_F32BE, CODEC_TYPE_PCM_F64BE, CODEC_TYPE_PCM_MULAW, + CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S24BE, CODEC_TYPE_PCM_S32BE, CODEC_TYPE_PCM_S8, }; use symphonia_core::errors::{decode_error, unsupported_error, Result}; use symphonia_core::io::{MediaSourceStream, ReadBytes}; use crate::{ - ChunkParser, FormatData, FormatIeeeFloat, FormatMuLaw, FormatPcm, PacketInfo, ParseChunk, - ParseChunkTag, + ChunkParser, FormatALaw, FormatData, FormatIeeeFloat, FormatMuLaw, FormatPcm, PacketInfo, + ParseChunk, ParseChunkTag, }; use extended::Extended; @@ -71,6 +71,16 @@ impl CommonChunk { Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } + fn read_alaw_pcm_fmt(n_channels: u16) -> Result { + let channels = match n_channels { + 1 => Channels::FRONT_LEFT, + 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT, + _ => return decode_error("aifc: channel layout is not stereo or mono for fmt_alaw"), + }; + + Ok(FormatData::ALaw(FormatALaw { codec: CODEC_TYPE_PCM_ALAW, channels })) + } + fn read_mulaw_pcm_fmt(n_channels: u16) -> Result { let channels = match n_channels { 1 => Channels::FRONT_LEFT, @@ -105,6 +115,11 @@ impl CommonChunk { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } + FormatData::ALaw(_) => { + // In a-law encoding, each audio sample is represented by an 8-bit value that has been compressed + let block_align = self.n_channels; + Ok(PacketInfo::without_blocks(block_align as u16)) + } FormatData::MuLaw(_) => { // In mu-law encoding, each audio sample is represented by an 8-bit value that has been compressed let block_align = self.n_channels; @@ -114,7 +129,12 @@ impl CommonChunk { let block_align = self.n_channels * self.sample_size / 8; Ok(PacketInfo::without_blocks(block_align as u16)) } - _ => return unsupported_error("aiff: packet info not implemented for format"), + FormatData::Extensible(_) => { + return unsupported_error("aiff: packet info not implemented for format Extensible") + } + FormatData::Adpcm(_) => { + return unsupported_error("aiff: packet info not implemented for format Adpcm") + } } } } @@ -156,6 +176,7 @@ impl ParseChunk for CommonChunk { } impl fmt::Display for CommonChunk { + //TODO: perhaps place this in riff.rs to share with wave etc fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "CommonChunk {{")?; writeln!(f, "\tn_channels: {},", self.n_channels)?; @@ -168,6 +189,11 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tchannels: {},", pcm.channels)?; writeln!(f, "\t\tcodec: {},", pcm.codec)?; } + FormatData::ALaw(ref alaw) => { + writeln!(f, "\tformat_data: MuLaw {{")?; + writeln!(f, "\t\tchannels: {},", alaw.channels)?; + writeln!(f, "\t\tcodec: {},", alaw.codec)?; + } FormatData::MuLaw(ref mulaw) => { writeln!(f, "\tformat_data: MuLaw {{")?; writeln!(f, "\t\tchannels: {},", mulaw.channels)?; @@ -178,9 +204,11 @@ impl fmt::Display for CommonChunk { writeln!(f, "\t\tchannels: {},", ieee.channels)?; writeln!(f, "\t\tcodec: {},", ieee.codec)?; } - _ => { - //TODO: this is not optimal, but since no other aiff formats are support it should never be reached anyway.. - writeln!(f, "\tdisplay not implemented for format")?; + FormatData::Extensible(_) => { + writeln!(f, "\tformat_data: Extensible DISPLAY UNSUPPORTED {{")?; + } + FormatData::Adpcm(_) => { + writeln!(f, "\tformat_data: Adpcm DISPLAY UNSUPPORTED {{")?; } }; @@ -215,7 +243,7 @@ impl CommonChunkParser for ChunkParser { let mut compression_name = vec![0; (str_len) as usize]; source.read_buf_exact(compression_name.as_mut())?; let compression_name = String::from_utf8_lossy(&compression_name).into_owned(); - + // Total number of bytes in pascal string must be even, since len is excluded from our var, we add 1 if (str_len + 1) % 2 != 0 { source.ignore_bytes(1)?; @@ -223,6 +251,7 @@ impl CommonChunkParser for ChunkParser { let format_data = match &compression_type { b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), + b"alaw" => CommonChunk::read_alaw_pcm_fmt(n_channels as u16), b"ulaw" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), b"fl32" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), b"fl64" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), diff --git a/symphonia-format-riff/src/riff.rs b/symphonia-format-riff/src/riff.rs index b32d5365..8cacb4cc 100644 --- a/symphonia-format-riff/src/riff.rs +++ b/symphonia-format-riff/src/riff.rs @@ -317,14 +317,20 @@ pub fn append_format_params( .with_bits_per_sample(u32::from(pcm.bits_per_sample)) .with_channels(pcm.channels); } + FormatData::ALaw(alaw) => { + codec_params.for_codec(alaw.codec).with_channels(alaw.channels); + } FormatData::MuLaw(mulaw) => { codec_params.for_codec(mulaw.codec).with_channels(mulaw.channels); } FormatData::IeeeFloat(ieee) => { codec_params.for_codec(ieee.codec).with_channels(ieee.channels); } - _ => { - unimplemented!("riff: format not supported"); + FormatData::Extensible(_) => { + unimplemented!("riff: format Extensible not supported"); + } + FormatData::Adpcm(_) => { + unimplemented!("riff: format Adpcm not supported"); } } } From ae9db6036c231c95ddd268320a848ee2d5d3bff4 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 7 May 2023 20:23:07 +0200 Subject: [PATCH 20/27] aiff: support for capitalized compression_type --- symphonia-format-riff/src/aiff_chunks.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 627edba4..28c355a3 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -250,11 +250,10 @@ impl CommonChunkParser for ChunkParser { } let format_data = match &compression_type { - b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), - b"alaw" => CommonChunk::read_alaw_pcm_fmt(n_channels as u16), - b"ulaw" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), - b"fl32" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), - b"fl64" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), + b"none" | b"NONE" => CommonChunk::read_pcm_fmt(sample_size as u16, n_channels as u16), + b"alaw" | b"ALAW" => CommonChunk::read_alaw_pcm_fmt(n_channels as u16), + b"ulaw" | b"ULAW" => CommonChunk::read_mulaw_pcm_fmt(n_channels as u16), + b"fl32" | b"fl64" => CommonChunk::read_ieee_fmt(sample_size as u16, n_channels as u16), _ => return unsupported_error("aifc: Compression type not implemented"), }; From cffad2dbfae363fa7035f60cd3d775d9fe424d83 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 7 May 2023 20:24:24 +0200 Subject: [PATCH 21/27] aiff: correctly refer to sound chunk --- symphonia-format-riff/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symphonia-format-riff/src/lib.rs b/symphonia-format-riff/src/lib.rs index 116a8d73..9bbdf3d2 100644 --- a/symphonia-format-riff/src/lib.rs +++ b/symphonia-format-riff/src/lib.rs @@ -117,7 +117,7 @@ impl FormatReader for AiffReader { // unsupported. // TODO: According to the spec additional chunks can be added after the sound data chunk. In fact any order can be possible. if chunk.is_none() { - return unsupported_error("aiff: missing data chunk"); + return unsupported_error("aiff: missing sound chunk"); } match chunk.unwrap() { From c17a6d76979e089e7018ccb4540a99c8c6ebb5a4 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sat, 13 May 2023 20:03:15 +0200 Subject: [PATCH 22/27] support for riff with samplesize non divisible by 8 --- symphonia-codec-pcm/src/lib.rs | 56 +++++++++++++++++++++--- symphonia-format-riff/src/aiff_chunks.rs | 8 ++-- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/symphonia-codec-pcm/src/lib.rs b/symphonia-codec-pcm/src/lib.rs index a240689d..c4b59947 100644 --- a/symphonia-codec-pcm/src/lib.rs +++ b/symphonia-codec-pcm/src/lib.rs @@ -117,6 +117,25 @@ macro_rules! read_pcm_signed { }; } +macro_rules! read_pcm_signed_be { + ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => { + // Get buffer of the correct sample format. + match $buf { + GenericAudioBuffer::$fmt(ref mut buf) => { + // Read samples. + let shift = $width - $coded_width; + buf.fill(|audio_planes, idx| -> Result<()> { + for plane in audio_planes.planes() { + plane[idx] = ($read >> shift).into_sample(); + } + Ok(()) + }) + } + _ => unreachable!(), + } + }; +} + macro_rules! read_pcm_unsigned { ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => { // Get buffer of the correct sample format. @@ -136,6 +155,25 @@ macro_rules! read_pcm_unsigned { }; } +macro_rules! read_pcm_unsigned_be { + ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => { + // Get buffer of the correct sample format. + match $buf { + GenericAudioBuffer::$fmt(ref mut buf) => { + // Read samples. + let shift = $width - $coded_width; + buf.fill(|audio_planes, idx| -> Result<()> { + for plane in audio_planes.planes() { + plane[idx] = ($read >> shift).into_sample(); + } + Ok(()) + }) + } + _ => unreachable!(), + } + }; +} + macro_rules! read_pcm_floating { ($buf:expr, $fmt:tt, $read:expr) => { // Get buffer of the correct sample format. @@ -259,19 +297,19 @@ impl PcmDecoder { read_pcm_signed!(self.buf, S32, reader.read_i32()?, 32, self.coded_width) } CODEC_TYPE_PCM_S32BE => { - read_pcm_signed!(self.buf, S32, reader.read_be_i32()?, 32, self.coded_width) + read_pcm_signed_be!(self.buf, S32, reader.read_be_i32()?, 32, self.coded_width) } CODEC_TYPE_PCM_S24LE => { read_pcm_signed!(self.buf, S24, reader.read_i24()? << 8, 24, self.coded_width) } CODEC_TYPE_PCM_S24BE => { - read_pcm_signed!(self.buf, S24, reader.read_be_i24()? << 8, 24, self.coded_width) + read_pcm_signed_be!(self.buf, S24, reader.read_be_i24()? << 8, 24, self.coded_width) } CODEC_TYPE_PCM_S16LE => { read_pcm_signed!(self.buf, S16, reader.read_i16()?, 16, self.coded_width) } CODEC_TYPE_PCM_S16BE => { - read_pcm_signed!(self.buf, S16, reader.read_be_i16()?, 16, self.coded_width) + read_pcm_signed_be!(self.buf, S16, reader.read_be_i16()?, 16, self.coded_width) } CODEC_TYPE_PCM_S8 => { read_pcm_signed!(self.buf, S8, reader.read_i8()?, 8, self.coded_width) @@ -280,19 +318,25 @@ impl PcmDecoder { read_pcm_unsigned!(self.buf, U32, reader.read_u32()?, 32, self.coded_width) } CODEC_TYPE_PCM_U32BE => { - read_pcm_unsigned!(self.buf, U32, reader.read_be_u32()?, 32, self.coded_width) + read_pcm_unsigned_be!(self.buf, U32, reader.read_be_u32()?, 32, self.coded_width) } CODEC_TYPE_PCM_U24LE => { read_pcm_unsigned!(self.buf, U24, reader.read_u24()? << 8, 24, self.coded_width) } CODEC_TYPE_PCM_U24BE => { - read_pcm_unsigned!(self.buf, U24, reader.read_be_u24()? << 8, 24, self.coded_width) + read_pcm_unsigned_be!( + self.buf, + U24, + reader.read_be_u24()? << 8, + 24, + self.coded_width + ) } CODEC_TYPE_PCM_U16LE => { read_pcm_unsigned!(self.buf, U16, reader.read_u16()?, 16, self.coded_width) } CODEC_TYPE_PCM_U16BE => { - read_pcm_unsigned!(self.buf, U16, reader.read_be_u16()?, 16, self.coded_width) + read_pcm_unsigned_be!(self.buf, U16, reader.read_be_u16()?, 16, self.coded_width) } CODEC_TYPE_PCM_U8 => { read_pcm_unsigned!(self.buf, U8, reader.read_u8()?, 8, self.coded_width) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 28c355a3..4c5f0c72 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -53,10 +53,10 @@ impl CommonChunk { // Select the appropriate codec using bits per sample. Samples are always interleaved and // little-endian encoded for the PCM format. let codec = match bits_per_sample { - 8 => CODEC_TYPE_PCM_S8, - 16 => CODEC_TYPE_PCM_S16BE, - 24 => CODEC_TYPE_PCM_S24BE, - 32 => CODEC_TYPE_PCM_S32BE, + x if x <= 8 => CODEC_TYPE_PCM_S8, + x if x > 8 && x <= 16 => CODEC_TYPE_PCM_S16BE, + x if x > 16 && x <= 24 => CODEC_TYPE_PCM_S24BE, + x if x > 24 && x <= 32 => CODEC_TYPE_PCM_S32BE, _ => return decode_error("aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits"), }; From bc2ca53c7de7d18cb2e94fde9a78fa31fde76da2 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Fri, 16 Jun 2023 21:05:19 +0200 Subject: [PATCH 23/27] correctly set bits in read_pcm_unsigned_be and read_pcm_signed_be --- symphonia-codec-pcm/src/lib.rs | 14 ++++++++------ symphonia-format-riff/src/aiff_chunks.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/symphonia-codec-pcm/src/lib.rs b/symphonia-codec-pcm/src/lib.rs index ac8177a0..14e72e94 100644 --- a/symphonia-codec-pcm/src/lib.rs +++ b/symphonia-codec-pcm/src/lib.rs @@ -39,6 +39,8 @@ use symphonia_core::io::ReadBytes; use symphonia_core::sample::{i24, u24, SampleFormat}; use symphonia_core::units::Duration; +use std::mem; + macro_rules! impl_generic_audio_buffer_func { ($generic:expr, $buf:ident, $expr:expr) => { match $generic { @@ -122,11 +124,11 @@ macro_rules! read_pcm_signed_be { // Get buffer of the correct sample format. match $buf { GenericAudioBuffer::$fmt(ref mut buf) => { - // Read samples. - let shift = $width - $coded_width; + let sample_size = mem::size_of_val(&$read) * 8; + let mask = !0 << (sample_size as u32 - $coded_width); buf.fill(|audio_planes, idx| -> Result<()> { for plane in audio_planes.planes() { - plane[idx] = ($read >> shift).into_sample(); + plane[idx] = ($read & mask).into_sample(); } Ok(()) }) @@ -160,11 +162,11 @@ macro_rules! read_pcm_unsigned_be { // Get buffer of the correct sample format. match $buf { GenericAudioBuffer::$fmt(ref mut buf) => { - // Read samples. - let shift = $width - $coded_width; + let sample_size = mem::size_of_val(&$read) * 8; + let mask = !0 << (sample_size as u32 - $coded_width); buf.fill(|audio_planes, idx| -> Result<()> { for plane in audio_planes.planes() { - plane[idx] = ($read >> shift).into_sample(); + plane[idx] = ($read & mask).into_sample(); } Ok(()) }) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index 4c5f0c72..5e6d1ab0 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -48,7 +48,7 @@ impl CommonChunk { // error if not a multiple of 8 or greater than 32-bits. // // It is possible though for AIFF to have a sample size not divisible by 8. - // Data is left justified, with the remaining bits zeroed. Currently not supported. + // Data is left justified, with the remaining bits zeroed. // // Select the appropriate codec using bits per sample. Samples are always interleaved and // little-endian encoded for the PCM format. From 0dbd5869df838261a9dabcda44baa2661655f632 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Fri, 16 Jun 2023 21:52:51 +0200 Subject: [PATCH 24/27] aiff: CommonChunk uses range in match statement --- symphonia-format-riff/src/aiff_chunks.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/symphonia-format-riff/src/aiff_chunks.rs b/symphonia-format-riff/src/aiff_chunks.rs index ea70c46d..2949d2f1 100644 --- a/symphonia-format-riff/src/aiff_chunks.rs +++ b/symphonia-format-riff/src/aiff_chunks.rs @@ -49,10 +49,10 @@ impl CommonChunk { // Select the appropriate codec using bits per sample. Samples are always interleaved and // little-endian encoded for the PCM format. let codec = match bits_per_sample { - x if x <= 8 => CODEC_TYPE_PCM_S8, - x if x > 8 && x <= 16 => CODEC_TYPE_PCM_S16BE, - x if x > 16 && x <= 24 => CODEC_TYPE_PCM_S24BE, - x if x > 24 && x <= 32 => CODEC_TYPE_PCM_S32BE, + 1..=8 => CODEC_TYPE_PCM_S8, + 9..=16 => CODEC_TYPE_PCM_S16BE, + 17..=24 => CODEC_TYPE_PCM_S24BE, + 25..=32 => CODEC_TYPE_PCM_S32BE, _ => return decode_error("aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits"), }; From ebb5ea4c0f922e4308c0fb07e3c136bbb37a0492 Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 23 Jul 2023 19:06:42 +0200 Subject: [PATCH 25/27] corrected error message regarding aiff bits per sample support --- symphonia-format-riff/src/aiff/chunks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symphonia-format-riff/src/aiff/chunks.rs b/symphonia-format-riff/src/aiff/chunks.rs index 47a78a7f..55801f1e 100644 --- a/symphonia-format-riff/src/aiff/chunks.rs +++ b/symphonia-format-riff/src/aiff/chunks.rs @@ -53,7 +53,7 @@ impl CommonChunk { 9..=16 => CODEC_TYPE_PCM_S16BE, 17..=24 => CODEC_TYPE_PCM_S24BE, 25..=32 => CODEC_TYPE_PCM_S32BE, - _ => return decode_error("aiff: bits per sample for pcm must be 8, 16, 24 or 32 bits"), + _ => return decode_error("aiff: bits per sample unsupported for pcm"), }; let channels = try_channel_count_to_mask(n_channels)?; From 8c8b1c473e50ff979bcb4ff88ec354b1bde04a4b Mon Sep 17 00:00:00 2001 From: dedobbin Date: Sun, 23 Jul 2023 19:17:06 +0200 Subject: [PATCH 26/27] wav: support more sample sizes --- symphonia-codec-pcm/src/lib.rs | 24 ++++++++++++++++-------- symphonia-format-riff/src/wave/chunks.rs | 14 +++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/symphonia-codec-pcm/src/lib.rs b/symphonia-codec-pcm/src/lib.rs index 14e72e94..d86a2aaa 100644 --- a/symphonia-codec-pcm/src/lib.rs +++ b/symphonia-codec-pcm/src/lib.rs @@ -39,8 +39,6 @@ use symphonia_core::io::ReadBytes; use symphonia_core::sample::{i24, u24, SampleFormat}; use symphonia_core::units::Duration; -use std::mem; - macro_rules! impl_generic_audio_buffer_func { ($generic:expr, $buf:ident, $expr:expr) => { match $generic { @@ -124,11 +122,16 @@ macro_rules! read_pcm_signed_be { // Get buffer of the correct sample format. match $buf { GenericAudioBuffer::$fmt(ref mut buf) => { - let sample_size = mem::size_of_val(&$read) * 8; - let mask = !0 << (sample_size as u32 - $coded_width); buf.fill(|audio_planes, idx| -> Result<()> { for plane in audio_planes.planes() { - plane[idx] = ($read & mask).into_sample(); + // To properly read a a sample with a shorter coded width, + // it should be masked using (1 << ($width - $coded_width)) - 1, masking out rightmost bits. + // ffpmeg seems to not do this, at least in the case for 12bit aiff PCM. + // If the aiff file is offspec, by not setting padded out bits to 0, + // this will fail when verifying with ffmpeg + // To comply with, we will not mask the sample. + // It should not make a hearable difference anyway. + plane[idx] = ($read).into_sample(); } Ok(()) }) @@ -162,11 +165,16 @@ macro_rules! read_pcm_unsigned_be { // Get buffer of the correct sample format. match $buf { GenericAudioBuffer::$fmt(ref mut buf) => { - let sample_size = mem::size_of_val(&$read) * 8; - let mask = !0 << (sample_size as u32 - $coded_width); buf.fill(|audio_planes, idx| -> Result<()> { for plane in audio_planes.planes() { - plane[idx] = ($read & mask).into_sample(); + // To properly read a a sample with a shorter coded width, + // it should be masked using (1 << ($width - $coded_width)) - 1, masking out rightmost bits. + // ffpmeg seems to not do this, at least in the case for 12bit aiff PCM. + // If the aiff file is offspec, by not setting padded out bits to 0, + // this will fail when verifying with ffmpeg + // To comply with, we will not mask the sample. + // It should not make a hearable difference anyway. + plane[idx] = ($read).into_sample(); } Ok(()) }) diff --git a/symphonia-format-riff/src/wave/chunks.rs b/symphonia-format-riff/src/wave/chunks.rs index 7996e26f..0ec5374b 100644 --- a/symphonia-format-riff/src/wave/chunks.rs +++ b/symphonia-format-riff/src/wave/chunks.rs @@ -78,15 +78,11 @@ impl WaveFormatChunk { // Select the appropriate codec using bits per sample. Samples are always interleaved and // little-endian encoded for the PCM format. let codec = match bits_per_sample { - 8 => CODEC_TYPE_PCM_U8, - 16 => CODEC_TYPE_PCM_S16LE, - 24 => CODEC_TYPE_PCM_S24LE, - 32 => CODEC_TYPE_PCM_S32LE, - _ => { - return decode_error( - "wav: bits per sample for fmt_pcm must be 8, 16, 24 or 32 bits", - ) - } + 1..=8 => CODEC_TYPE_PCM_U8, + 9..=16 => CODEC_TYPE_PCM_S16LE, + 17..=24 => CODEC_TYPE_PCM_S24LE, + 25..=32 => CODEC_TYPE_PCM_S32LE, + _ => return decode_error("wav: bits per sample unsupported for pcm"), }; let channels = try_channel_count_to_mask(n_channels)?; From 5375843a75c9190f805b4259df6876150b7e2cbb Mon Sep 17 00:00:00 2001 From: dedobbin Date: Wed, 16 Aug 2023 19:59:12 +0200 Subject: [PATCH 27/27] codec-pcm: properly mask out right most bits when BE encoded file has codedwidth < sample width --- symphonia-codec-pcm/src/lib.rs | 54 ++++++++++++---------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/symphonia-codec-pcm/src/lib.rs b/symphonia-codec-pcm/src/lib.rs index d86a2aaa..fcd148b4 100644 --- a/symphonia-codec-pcm/src/lib.rs +++ b/symphonia-codec-pcm/src/lib.rs @@ -118,27 +118,18 @@ macro_rules! read_pcm_signed { } macro_rules! read_pcm_signed_be { - ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => { - // Get buffer of the correct sample format. + ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => {{ + let mask = !((1 << ($width - $coded_width)) - 1); match $buf { - GenericAudioBuffer::$fmt(ref mut buf) => { - buf.fill(|audio_planes, idx| -> Result<()> { - for plane in audio_planes.planes() { - // To properly read a a sample with a shorter coded width, - // it should be masked using (1 << ($width - $coded_width)) - 1, masking out rightmost bits. - // ffpmeg seems to not do this, at least in the case for 12bit aiff PCM. - // If the aiff file is offspec, by not setting padded out bits to 0, - // this will fail when verifying with ffmpeg - // To comply with, we will not mask the sample. - // It should not make a hearable difference anyway. - plane[idx] = ($read).into_sample(); - } - Ok(()) - }) - } + GenericAudioBuffer::$fmt(ref mut buf) => buf.fill(|audio_planes, idx| -> Result<()> { + for plane in audio_planes.planes() { + plane[idx] = ($read & mask).into_sample(); + } + Ok(()) + }), _ => unreachable!(), } - }; + }}; } macro_rules! read_pcm_unsigned { @@ -161,27 +152,18 @@ macro_rules! read_pcm_unsigned { } macro_rules! read_pcm_unsigned_be { - ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => { - // Get buffer of the correct sample format. + ($buf:expr, $fmt:tt, $read:expr, $width:expr, $coded_width:expr) => {{ + let mask = !((1 << ($width - $coded_width)) - 1); match $buf { - GenericAudioBuffer::$fmt(ref mut buf) => { - buf.fill(|audio_planes, idx| -> Result<()> { - for plane in audio_planes.planes() { - // To properly read a a sample with a shorter coded width, - // it should be masked using (1 << ($width - $coded_width)) - 1, masking out rightmost bits. - // ffpmeg seems to not do this, at least in the case for 12bit aiff PCM. - // If the aiff file is offspec, by not setting padded out bits to 0, - // this will fail when verifying with ffmpeg - // To comply with, we will not mask the sample. - // It should not make a hearable difference anyway. - plane[idx] = ($read).into_sample(); - } - Ok(()) - }) - } + GenericAudioBuffer::$fmt(ref mut buf) => buf.fill(|audio_planes, idx| -> Result<()> { + for plane in audio_planes.planes() { + plane[idx] = ($read & mask).into_sample(); + } + Ok(()) + }), _ => unreachable!(), } - }; + }}; } macro_rules! read_pcm_floating {