symphonia-format-riff-0.5.4/.cargo_vcs_info.json0000644000000001630000000000100152630ustar { "git": { "sha1": "d3b7742fa73674b70d9ab80cc5f8384cc653df3a" }, "path_in_vcs": "symphonia-format-riff" }symphonia-format-riff-0.5.4/Cargo.toml0000644000000024340000000000100132640ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.53" name = "symphonia-format-riff" version = "0.5.4" authors = [ "Philip Deljanov ", "dedobbin ", ] description = "Pure Rust RIFF demuxer (a part of project Symphonia)." homepage = "https://github.com/pdeljanov/Symphonia" readme = "README.md" keywords = [ "audio", "media", "demuxer", "aiff", "wav", ] categories = [ "multimedia", "multimedia::audio", "multimedia::encoding", ] license = "MPL-2.0" repository = "https://github.com/pdeljanov/Symphonia" [dependencies.extended] version = "0.1.0" [dependencies.log] version = "0.4" [dependencies.symphonia-core] version = "0.5.4" [dependencies.symphonia-metadata] version = "0.5.4" [features] aiff = [] default = [ "aiff", "wav", ] wav = [] symphonia-format-riff-0.5.4/Cargo.toml.orig000064400000000000000000000014251046102023000167440ustar 00000000000000[package] name = "symphonia-format-riff" version = "0.5.4" 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 ", "dedobbin "] license = "MPL-2.0" readme = "README.md" categories = ["multimedia", "multimedia::audio", "multimedia::encoding"] keywords = ["audio", "media", "demuxer", "aiff", "wav"] edition = "2018" rust-version = "1.53" [features] default = ["aiff", "wav"] aiff = [] wav = [] [dependencies] extended = "0.1.0" log = "0.4" symphonia-core = { version = "0.5.4", path = "../symphonia-core" } symphonia-metadata = { version = "0.5.4", path = "../symphonia-metadata" } symphonia-format-riff-0.5.4/README.md000064400000000000000000000020221046102023000153260ustar 00000000000000# Symphonia RIFF (AIFF, AVI, WAVE) Demuxer [![Docs](https://docs.rs/symphonia-format-riff/badge.svg)](https://docs.rs/symphonia-format-riff) AIFF/AVI/WAVE demuxer for Project Symphonia. **Note:** This crate is part of Symphonia. Please use the [`symphonia`](https://crates.io/crates/symphonia) crate instead of this one directly. ## Support This crate supports demuxing media containers based off the Resource Interchange File Format (RIFF). Specific format support may be enabled or disabled using feature flags. However, by default, all formats are enabled. | Format | Feature Flag | Default | |--------|--------------|---------| | AIFF | `aiff` | Yes | | WAVE | `wav` | Yes | ## License Symphonia is provided under the MPL v2.0 license. Please refer to the LICENSE file for more details. ## Contributing Symphonia is a free and open-source project that welcomes contributions! To get started, please read our [Contribution Guidelines](https://github.com/pdeljanov/Symphonia/tree/master/CONTRIBUTING.md). symphonia-format-riff-0.5.4/src/aiff/chunks.rs000064400000000000000000000266711046102023000174240ustar 00000000000000// 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 symphonia_core::codecs::{ CODEC_TYPE_PCM_ALAW, CODEC_TYPE_PCM_F32BE, CODEC_TYPE_PCM_F64BE, CODEC_TYPE_PCM_MULAW, CODEC_TYPE_PCM_S16BE, CODEC_TYPE_PCM_S16LE, 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::common::{ try_channel_count_to_mask, ChunkParser, FormatALaw, FormatData, FormatIeeeFloat, FormatMuLaw, FormatPcm, PacketInfo, ParseChunk, ParseChunkTag, }; use extended::Extended; /// `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"), }; let channels = try_channel_count_to_mask(n_channels)?; Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } fn read_alaw_pcm_fmt(n_channels: u16) -> Result { let channels = try_channel_count_to_mask(n_channels)?; Ok(FormatData::ALaw(FormatALaw { codec: CODEC_TYPE_PCM_ALAW, channels })) } fn read_mulaw_pcm_fmt(n_channels: u16) -> Result { let channels = try_channel_count_to_mask(n_channels)?; 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 = try_channel_count_to_mask(n_channels)?; Ok(FormatData::IeeeFloat(FormatIeeeFloat { channels, codec })) } fn read_sowt_fmt(bits_per_sample: u16, n_channels: u16) -> Result { let codec = match bits_per_sample { 16 => CODEC_TYPE_PCM_S16LE, _ => return decode_error("aiff: bits per sample for sowt must be 16 bits"), }; let channels = try_channel_count_to_mask(n_channels)?; Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } fn read_twos_fmt(bits_per_sample: u16, n_channels: u16) -> Result { let codec = match bits_per_sample { 16 => CODEC_TYPE_PCM_S16BE, _ => return decode_error("aiff: bits per sample for twos must be 16 bits"), }; let channels = try_channel_count_to_mask(n_channels)?; Ok(FormatData::Pcm(FormatPcm { bits_per_sample, channels, codec })) } 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::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; 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)) } FormatData::Extensible(_) => { unsupported_error("aiff: packet info not implemented for format Extensible") } FormatData::Adpcm(_) => { unsupported_error("aiff: packet info not implemented for format Adpcm") } } } } 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; 10]; 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; 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 { //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)?; 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)?; } 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)?; 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)?; } FormatData::Extensible(_) => { writeln!(f, "\tformat_data: Extensible DISPLAY UNSUPPORTED {{")?; } FormatData::Adpcm(_) => { writeln!(f, "\tformat_data: Adpcm DISPLAY UNSUPPORTED {{")?; } }; writeln!(f, "\t}}")?; writeln!(f, "}}") } } 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]; 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()?; // Ignore pascal string containing compression_name let str_len = source.read_byte()?; source.ignore_bytes(str_len as u64)?; // 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" | 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), b"sowt" | b"SOWT" => CommonChunk::read_sowt_fmt(sample_size as u16, n_channels as u16), b"twos" | b"TWOS" => CommonChunk::read_twos_fmt(sample_size as u16, n_channels as u16), _ => return unsupported_error("aifc: Compression type not implemented"), }; 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 }) } } /// `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, } } } symphonia-format-riff-0.5.4/src/aiff/mod.rs000064400000000000000000000213731046102023000167020ustar 00000000000000// 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::io::{Seek, SeekFrom}; use symphonia_core::codecs::CodecParameters; use symphonia_core::errors::{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; use crate::common::{ append_data_params, append_format_params, next_packet, ByteOrder, ChunksReader, PacketInfo, }; 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"; /// Audio Interchange File Format (AIFF) format reader. /// /// `AiffReader` implements a demuxer for the AIFF container format. pub struct AiffReader { reader: MediaSourceStream, tracks: Vec, cues: Vec, metadata: MetadataLog, packet_info: PacketInfo, data_start_pos: u64, data_end_pos: u64, } impl QueryDescriptor for AiffReader { 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"] ), ] } fn score(_context: &[u8]) -> u8 { 255 } } impl FormatReader for AiffReader { fn try_new(mut source: MediaSourceStream, _options: &FormatOptions) -> Result { // The FORM marker should be present. let marker = source.read_quad_bytes()?; if marker != AIFF_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). // 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()?; 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. let 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. // 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 sound chunk"); } match chunk.unwrap() { RiffAiffChunks::Common(common) => { let common = match riff_form { AIFF_RIFF_FORM => common.parse_aiff(&mut source)?, AIFC_RIFF_FORM => common.parse_aifc(&mut source)?, _ => return unsupported_error("aiff: riff form is not supported"), }; // 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_format_params( &mut codec_params, &common.format_data, common.sample_rate, ); } 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_data_params(&mut codec_params, data.len as u64, &packet_info); // Add a new track using the collected codec parameters. return Ok(AiffReader { 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 { 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); // 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 } } symphonia-format-riff-0.5.4/src/common.rs000064400000000000000000000324541046102023000165100ustar 00000000000000// 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, Error, Result}; use symphonia_core::formats::prelude::*; use symphonia_core::io::{MediaSourceStream, ReadBytes}; use log::{debug, 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 } } pub fn fix_channel_mask(mut channel_mask: u32, n_channels: u16) -> u32 { let channel_diff = n_channels as i32 - channel_mask.count_ones() as i32; if channel_diff != 0 { info!("Channel mask not set correctly, channel positions may be incorrect!"); } // Check that the number of ones in the channel mask match the number of channels. if channel_diff > 0 { // Too few ones in mask so add extra ones above the most significant one let shift = 32 - (!channel_mask).leading_ones(); channel_mask |= ((1 << channel_diff) - 1) << shift; } else { // Too many ones in mask so remove the most significant extra ones while channel_mask.count_ones() != n_channels as u32 { let highest_one = 31 - (!channel_mask).leading_ones(); channel_mask &= !(1 << highest_one); } } channel_mask } #[test] fn test_fix_channel_mask() { // Too few assert_eq!(fix_channel_mask(0, 9), 0b111111111); assert_eq!(fix_channel_mask(0b101000, 5), 0b111101000); // Too many assert_eq!(fix_channel_mask(0b1111111, 0), 0); assert_eq!(fix_channel_mask(0b101110111010, 5), 0b10111010); assert_eq!(fix_channel_mask(0xFFFFFFFF, 8), 0b11111111); } pub fn try_channel_count_to_mask(count: u16) -> Result { (1..=32) .contains(&count) .then(|| Channels::from_bits(((1u64 << count) - 1) as u32)) .flatten() .ok_or(Error::DecodeError("riff: invalid channel count")) } #[test] fn test_try_channel_count_to_mask() { assert!(try_channel_count_to_mask(0).is_err()); for i in 1..27 { assert!(try_channel_count_to_mask(i).is_ok()); } for i in 27..u16::MAX { assert!(try_channel_count_to_mask(i).is_err()); } } /// `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()?; 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) { 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"); } } // 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], pub 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: &[Track], 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); } } symphonia-format-riff-0.5.4/src/lib.rs000064400000000000000000000014061046102023000157570ustar 00000000000000// 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)] mod common; #[cfg(feature = "aiff")] mod aiff; #[cfg(feature = "wav")] mod wave; #[cfg(feature = "aiff")] pub use aiff::AiffReader; #[cfg(feature = "wav")] pub use wave::WavReader; symphonia-format-riff-0.5.4/src/wave/chunks.rs000064400000000000000000000555331046102023000174600ustar 00000000000000// 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::CodecParameters; 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::{MediaSourceStream, ReadBytes}; use symphonia_core::meta::{MetadataBuilder, MetadataRevision, Tag}; use symphonia_metadata::riff; use crate::common::{ fix_channel_mask, try_channel_count_to_mask, ByteOrder, ChunkParser, ChunksReader, FormatALaw, FormatAdpcm, FormatData, FormatExtensible, FormatIeeeFloat, FormatMuLaw, FormatPcm, 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", ) } }; let channels = try_channel_count_to_mask(n_channels)?; 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)?; let channels = try_channel_count_to_mask(n_channels)?; 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. match len { 16 => (), 18 => { let extra_size = reader.read_u16()?; if extra_size != 0 { return decode_error("wav: extra data not expected for fmt_ieee chunk"); } } 40 => { // WAVEFORMATEXTENSIBLE is used for formats having more than two channels // or higher sample resolutions than allowed by WAVEFORMATEX but for now // we just ignore it let _ = reader.ignore_bytes(40 - 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"), }; let channels = try_channel_count_to_mask(n_channels)?; 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 = fix_channel_mask(reader.read_u32()?, n_channels); // 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 = try_channel_count_to_mask(n_channels)?; 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 = try_channel_count_to_mask(n_channels)?; 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()) } symphonia-format-riff-0.5.4/src/wave/mod.rs000064400000000000000000000221661046102023000167400ustar 00000000000000// 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::io::{Seek, SeekFrom}; use symphonia_core::codecs::CodecParameters; use symphonia_core::errors::{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}; use crate::common::{ append_data_params, append_format_params, next_packet, ByteOrder, ChunksReader, PacketInfo, }; mod chunks; use chunks::*; /// 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 "wave". const WAVE_RIFF_FORM: [u8; 4] = *b"WAVE"; /// Waveform Audio File Format (WAV) format reader. /// /// `WavReader` implements a demuxer for the WAVE container format. 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, }); } } } } 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 } }