├── .gitignore ├── Cargo.toml ├── README.md ├── examples └── tool.rs ├── src ├── error.rs ├── enums.rs ├── chunks.rs └── lib.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | *swp 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "caf" 3 | version = "0.1.0" 4 | authors = ["est31 "] 5 | description = "Pure rust Core Audio Format container decoder" 6 | license = "MIT/Apache-2.0" 7 | keywords = ["coreaudio", "apple"] 8 | repository = "https://github.com/RustAudio/caf" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | byteorder = "1.0" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # caf 2 | 3 | Decoder of Apple's [Core Audio Format](https://en.wikipedia.org/wiki/Core_Audio_Format) container. No unsafe. Pure Rust. 4 | 5 | State of implementation: usable already, but not polished (yet). 6 | 7 | ## License 8 | 9 | Licensed under Apache 2 or MIT (at your option). For details, see the [LICENSE](LICENSE) file. 10 | 11 | All examples inside the `examples/` folder are licensed under the 12 | [CC-0](https://creativecommons.org/publicdomain/zero/1.0/) license. 13 | 14 | ### License of your contributions 15 | 16 | Unless you explicitly state otherwise, any contribution intentionally submitted for 17 | inclusion in the work by you, as defined in the Apache-2.0 license, 18 | shall be dual licensed / CC-0 licensed as above, without any additional terms or conditions. 19 | -------------------------------------------------------------------------------- /examples/tool.rs: -------------------------------------------------------------------------------- 1 | // CAF container decoder written in Rust 2 | // 3 | // This example file is licensed 4 | // under the CC-0 license: 5 | // https://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | extern crate caf; 8 | use std::fs::File; 9 | use caf::chunks::{CafChunk}; 10 | use caf::{CafChunkReader}; 11 | use std::env; 12 | 13 | fn main() { 14 | let file_path = env::args().nth(1).expect("No arg found. Please specify a file to open."); 15 | println!("Opening file: {}", file_path); 16 | let f_rdr = File::open(file_path).unwrap(); 17 | let mut rdr = CafChunkReader::new(f_rdr).unwrap(); 18 | // Dump the decoded packets. 19 | loop { 20 | let chunk = rdr.read_chunk().unwrap(); 21 | match chunk { 22 | CafChunk::AudioDataInMemory(..) => println!("Audio data in memory"), 23 | _ => println!("{:?}", chunk), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // CAF container decoder written in Rust 2 | // 3 | // Copyright (c) 2017 est31 4 | // and contributors. All rights reserved. 5 | // Licensed under MIT license, or Apache 2 license, 6 | // at your option. Please see the LICENSE file 7 | // attached to this source distribution for details. 8 | 9 | use std::string::FromUtf8Error; 10 | use std::io::{Error as IoError}; 11 | use std::error::Error; 12 | use std::fmt::Display; 13 | use ::ChunkType; 14 | 15 | #[derive(Debug)] 16 | pub enum CafError { 17 | Io(IoError), 18 | FromUtf8(FromUtf8Error), 19 | /// If the given stream doesn't start with a CAF header. 20 | NotCaf, 21 | /// If the chunk can't be decoded because its type is not supported 22 | UnsupportedChunkType(ChunkType), 23 | } 24 | 25 | impl From for CafError { 26 | fn from(io_err :IoError) -> Self { 27 | CafError::Io(io_err) 28 | } 29 | } 30 | 31 | impl From for CafError { 32 | fn from(utf8_err :FromUtf8Error) -> Self { 33 | CafError::FromUtf8(utf8_err) 34 | } 35 | } 36 | 37 | impl Error for CafError { 38 | fn description(&self) -> &str { 39 | use CafError::*; 40 | match self { 41 | &Io(_) => "IO error", 42 | &FromUtf8(_) => "Can't decode UTF-8", 43 | &NotCaf => "The given stream doesn't start with a CAF header", 44 | &UnsupportedChunkType(_) => "Encountered a chunk with an unsupported type", 45 | } 46 | } 47 | 48 | fn cause(&self) -> Option<&Error> { 49 | use CafError::*; 50 | match self { 51 | &Io(ref err) => Some(err as &Error), 52 | &FromUtf8(ref err) => Some(err as &Error), 53 | _ => None 54 | } 55 | } 56 | } 57 | 58 | impl Display for CafError { 59 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { 60 | use CafError::*; 61 | match *self { 62 | Io(ref err) => err.fmt(f), 63 | FromUtf8(ref err) => err.fmt(f), 64 | UnsupportedChunkType(_) | 65 | NotCaf => write!(f, "{}", self.description()), 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/enums.rs: -------------------------------------------------------------------------------- 1 | // CAF container decoder written in Rust 2 | // 3 | // Copyright (c) 2017 est31 4 | // and contributors. All rights reserved. 5 | // Licensed under MIT license, or Apache 2 license, 6 | // at your option. Please see the LICENSE file 7 | // attached to this source distribution for details. 8 | 9 | /*! 10 | Module with some (sparse) enums 11 | 12 | In muliple places, the spec provides lists of IDs, saying 13 | that the list is non exhaustive. 14 | */ 15 | 16 | /// Module containing the different specified chunk types 17 | /// 18 | /// Beware, the spec explicitly says that its list is non exhaustive. 19 | mod chunk_types { 20 | // The order is not random, its how it appears in the spec, bear this in mind. 21 | // The spec says that this list is not exhaustive, so we can't use an enum here. 22 | // Especially, users may add their own custom chunk types provided those are 23 | // outside of the reserved space of identifiers. 24 | 25 | pub const AUDIO_DESCRIPTION :u32 = 0x64_65_73_63; // "desc" 26 | pub const AUDIO_DATA :u32 = 0x64_61_74_61; // "data" 27 | pub const PACKET_TABLE :u32 = 0x70_61_6b_74; // "pakt" 28 | pub const CHANNEL_LAYOUT :u32 = 0x63_68_61_6e; // "chan" 29 | pub const MAGIC_COOKIE :u32 = 0x6b_75_6b_69; // "kuki" 30 | pub const STRINGS :u32 = 0x73_74_42_67; // "strg" 31 | pub const MARKER :u32 = 0x6d_61_72_6b; // "mark" 32 | pub const REGION :u32 = 0x72_65_67_6e; // "regn" 33 | pub const INSTRUMENT :u32 = 0x69_6e_73_74; // "inst" 34 | pub const MIDI :u32 = 0x6d_69_64_69; // "midi" 35 | pub const OVERVIEW :u32 = 0x6f_76_76_77; // "ovvw" 36 | pub const PEAK :u32 = 0x70_65_61_6b; // "peak" 37 | pub const EDIT_COMMENTS :u32 = 0x65_64_63_74; // "edct" 38 | pub const INFO :u32 = 0x69_6e_66_6f; // "info" 39 | pub const UNIQUE_MATERIAL_IDENTIFIER :u32 = 0x75_6d_69_64; // "umid" 40 | pub const USER_DEFINED :u32 = 0x75_75_69_64; // "uuid" 41 | pub const FREE :u32 = 0x66_72_65_65; // "free" 42 | } 43 | 44 | /// Possible chunk types defined by the spec 45 | /// 46 | /// The chunks in a CAF file after the CAF File Header form the 47 | /// uppermost layer of granularity. 48 | /// 49 | /// The spec explicitly says that the list is not exhaustive 50 | /// and that users may add their own unofficial chunk types 51 | /// from outside of the reserved range of chunks. 52 | /// Those chunk types are represented by the `Other` variant. 53 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 54 | pub enum ChunkType { 55 | /// mChunkType for the "Audio Description" chunk 56 | AudioDescription, 57 | /// mChunkType for the "Audio Data" chunk 58 | AudioData, 59 | /// mChunkType for the "Packet Table" chunk 60 | PacketTable, 61 | /// mChunkType for the "Channel Layout" chunk 62 | ChannelLayout, 63 | /// mChunkType for the "Magic Cookie" chunk 64 | MagicCookie, 65 | /// mChunkType for the "Strings" chunk 66 | Strings, 67 | /// mChunkType for the "Marker" chunk 68 | Marker, 69 | /// mChunkType for the "Region" chunk 70 | Region, 71 | /// mChunkType for the "Instrument" chunk 72 | Instrument, 73 | /// mChunkType for the "MIDI" chunk 74 | Midi, 75 | /// mChunkType for the "Overview" chunk 76 | Overview, 77 | /// mChunkType for the "Peak" chunk 78 | Peak, 79 | /// mChunkType for the "Edit Comments" chunk 80 | EditComments, 81 | /// mChunkType for the "Information" chunk 82 | Info, 83 | /// mChunkType for the "Unique Material Identifier" chunk 84 | UniqueMaterialIdentifier, 85 | /// mChunkType for the "User-Defined" chunk 86 | UserDefined, 87 | /// mChunkType for the "Free" chunk 88 | Free, 89 | /// Variant for all chunks that were not mentioned in this list. 90 | /// 91 | /// This includes both chunk types from the range of reserved 92 | /// chunk types that weren't mentioned, and those from outside 93 | /// the range of reserved ones. 94 | Other(u32), 95 | } 96 | 97 | impl From for ChunkType { 98 | fn from(v :u32) -> Self { 99 | use self::chunk_types::*; 100 | use self::ChunkType::*; 101 | match v { 102 | AUDIO_DESCRIPTION => AudioDescription, 103 | AUDIO_DATA => AudioData, 104 | PACKET_TABLE => PacketTable, 105 | CHANNEL_LAYOUT => ChannelLayout, 106 | MAGIC_COOKIE => MagicCookie, 107 | STRINGS => Strings, 108 | MARKER => Marker, 109 | REGION => Region, 110 | INSTRUMENT => Instrument, 111 | MIDI => Midi, 112 | OVERVIEW => Overview, 113 | PEAK => Peak, 114 | EDIT_COMMENTS => EditComments, 115 | INFO => Info, 116 | UNIQUE_MATERIAL_IDENTIFIER => UniqueMaterialIdentifier, 117 | USER_DEFINED => UserDefined, 118 | FREE => Free, 119 | _ => Other(v), 120 | } 121 | } 122 | } 123 | 124 | /// Module containing the different specified chunk types 125 | /// 126 | /// Beware, the spec explicitly says that its list is non exhaustive. 127 | mod format_types { 128 | // The order is not random, its how it appears in the spec, bear this in mind. 129 | // The spec says that this list is not exhaustive, so we can't use an enum here. 130 | 131 | 132 | pub const LINEAR_PCM :u32 = 0x6c_70_63_6d; // "lpcm" 133 | pub const APPLE_IMA4 :u32 = 0x69_6d_61_34; // "ima4" 134 | pub const MPEG4_AAC :u32 = 0x61_61_63_20; // "aac " 135 | pub const MACE3 :u32 = 0x4d_41_43_33; // "MAC3" 136 | pub const MACE6 :u32 = 0x4d_41_43_36; // "MAC6" 137 | pub const U_LAW :u32 = 0x75_6c_61_77; // "ulaw" 138 | pub const A_LAW :u32 = 0x61_6c_61_77; // "alaw" 139 | pub const MPEG_LAYER_1 :u32 = 0x2e_6d_70_31; // ".mp1" 140 | pub const MPEG_LAYER_2 :u32 = 0x2e_6d_70_32; // ".mp2" 141 | pub const MPEG_LAYER_3 :u32 = 0x2e_6d_70_33; // ".mp3" 142 | pub const AAPL_LOSSLESS :u32 = 0x61_6c_61_63; // "alac" 143 | } 144 | 145 | /// Payload format types defined by the spec 146 | /// 147 | /// Enum for all the possible `mFormatID` field contents 148 | /// defined by the spec. 149 | /// 150 | /// The spec explicitly says that the list is not exhaustive. 151 | #[derive(Debug, Clone, PartialEq, Eq)] 152 | pub enum FormatType { 153 | /// mFormatID for Linear PCM 154 | LinearPcm, 155 | /// mFormatID for IMA 4:1 ADPCM 156 | AppleIma4, 157 | /// mFormatID for MPEG-4 AAC 158 | Mpeg4Aac, 159 | /// mFormatID for MACE 3:1 160 | Mace3, 161 | /// mFormatID for MACE 6:1 162 | Mace6, 163 | /// mFormatID for uLaw 2:1 164 | Ulaw, 165 | /// mFormatID for aLaw 2:1 166 | Alaw, 167 | /// mFormatID for MPEG-1 168 | MpegLayer1, 169 | /// mFormatID for MPEG-{1,2} 170 | MpegLayer2, 171 | /// mFormatID for MPEG-{1,2,3} 172 | MpegLayer3, 173 | /// mFormatID for Apple Lossless 174 | AppleLossless, 175 | /// Variant for all formats that were not mentioned in this list. 176 | Other(u32), 177 | } 178 | 179 | impl From for FormatType { 180 | fn from(v :u32) -> Self { 181 | use self::format_types::*; 182 | use self::FormatType::*; 183 | match v { 184 | LINEAR_PCM => LinearPcm, 185 | APPLE_IMA4 => AppleIma4, 186 | MPEG4_AAC => Mpeg4Aac, 187 | MACE3 => Mace3, 188 | MACE6 => Mace6, 189 | U_LAW => Ulaw, 190 | A_LAW => Alaw, 191 | MPEG_LAYER_1 => MpegLayer1, 192 | MPEG_LAYER_2 => MpegLayer2, 193 | MPEG_LAYER_3 => MpegLayer3, 194 | AAPL_LOSSLESS => AppleLossless, 195 | _ => Other(v), 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/chunks.rs: -------------------------------------------------------------------------------- 1 | // CAF container decoder written in Rust 2 | // 3 | // Copyright (c) 2017 est31 4 | // and contributors. All rights reserved. 5 | // Licensed under MIT license, or Apache 2 license, 6 | // at your option. Please see the LICENSE file 7 | // attached to this source distribution for details. 8 | 9 | /*! 10 | CAF chunk decoding 11 | */ 12 | 13 | use ::CafError; 14 | // TODO once we drop compat for pre rust 1.15 replace this with "use ::Read;" 15 | use std::io::Read; 16 | // TODO once we drop compat for pre rust 1.15 replace this with "use ::IoError;" 17 | use std::io::Error as IoError; 18 | use ::ChunkType; 19 | use ::FormatType; 20 | 21 | /// A decoded CAF chunk header 22 | #[derive(Debug, Clone)] 23 | pub struct CafChunkHeader { 24 | pub ch_type :ChunkType, 25 | /// The size of the chunk's content (without the head) in bytes. 26 | /// 27 | /// -1 is a special value and means the chunk ends at the EOF. 28 | /// The spec only allows this case for the Audio Data chunk. 29 | /// Such a chunk is obviously last in the file. 30 | pub ch_size :i64, 31 | } 32 | 33 | /// An in-memory CAF chunk. 34 | /// 35 | /// The list represents the chunk types we can parse. 36 | #[derive(Debug, Clone)] 37 | pub enum CafChunk { 38 | Desc(AudioDescription), 39 | AudioDataInMemory(u32, Vec), 40 | PacketTable(PacketTable), 41 | ChanLayout(ChannelLayout), 42 | MagicCookie(Vec), 43 | // ... 44 | Info(Vec<(String, String)>), // TODO use a hash map 45 | // ... 46 | } 47 | 48 | impl CafChunk { 49 | pub fn get_type(&self) -> ChunkType { 50 | use ChunkType::*; 51 | match self { 52 | &CafChunk::Desc(..) => AudioDescription, 53 | &CafChunk::AudioDataInMemory(..) => AudioData, 54 | &CafChunk::PacketTable(..) => PacketTable, 55 | &CafChunk::ChanLayout(..) => ChannelLayout, 56 | &CafChunk::MagicCookie(..) => MagicCookie, 57 | &CafChunk::Info(..) => Info, 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | pub struct AudioDescription { 64 | pub sample_rate :f64, 65 | pub format_id :FormatType, 66 | pub format_flags :u32, 67 | pub bytes_per_packet :u32, 68 | pub frames_per_packet :u32, 69 | pub channels_per_frame :u32, 70 | pub bits_per_channel :u32, 71 | } 72 | 73 | 74 | #[derive(Debug, Clone)] 75 | pub struct PacketTable { 76 | pub num_valid_frames :i64, 77 | pub num_priming_frames :i32, 78 | pub num_remainder_frames :i32, 79 | pub lengths :Vec, 80 | } 81 | 82 | #[derive(Debug, Clone)] 83 | pub struct ChannelLayout { 84 | // TODO enrich this one and the one below with some meaning 85 | // e.g. we'll maybe need some other representation, like an enum? 86 | pub channel_layout_tag :u32, 87 | pub channel_bitmap :u32, 88 | pub channel_descriptions :Vec, 89 | } 90 | 91 | #[derive(Debug, Clone)] 92 | pub struct ChannelDescription { 93 | pub channel_label :u32, 94 | pub channel_flags :u32, 95 | pub coordinates :(f32, f32, f32), 96 | } 97 | 98 | /// Returns whether `decode_chunk` can decode chunks with the given type 99 | pub fn can_decode_chunk_type(chunk_type :ChunkType) -> bool { 100 | use ChunkType::*; 101 | match chunk_type { 102 | AudioDescription | 103 | AudioData | 104 | PacketTable | 105 | ChannelLayout | 106 | MagicCookie | 107 | Info 108 | => true, 109 | _ => false, 110 | } 111 | } 112 | 113 | /// Decodes an in-memory chunk given its type and content 114 | /// 115 | /// If the given chunk type is not not supported, the function will 116 | /// return `CafError::UnsupportedChunkType` in this case. 117 | pub fn decode_chunk(chunk_type :ChunkType, mut chunk_content :Vec) 118 | -> Result { 119 | use byteorder::BigEndian as Be; 120 | use byteorder::ReadBytesExt; 121 | use std::io::{Cursor, BufRead}; 122 | // ReaD with big endian order and Try 123 | macro_rules! rdt { 124 | ($rdr:ident, $func:ident) => { try!($rdr.$func::()) } 125 | } 126 | match chunk_type { 127 | ChunkType::AudioDescription => { 128 | let mut rdr = Cursor::new(&chunk_content); 129 | let sample_rate = rdr.read_f64::().unwrap(); 130 | Ok(CafChunk::Desc(AudioDescription { 131 | sample_rate : sample_rate, 132 | format_id : FormatType::from(rdr.read_u32::().unwrap()), 133 | format_flags : rdr.read_u32::().unwrap(), 134 | bytes_per_packet : rdt!(rdr,read_u32), 135 | frames_per_packet : rdt!(rdr,read_u32), 136 | channels_per_frame : rdt!(rdr,read_u32), 137 | bits_per_channel : rdt!(rdr,read_u32), 138 | })) 139 | }, 140 | ChunkType::AudioData => { 141 | let edit_count = { 142 | let mut rdr = Cursor::new(&chunk_content); 143 | rdr.read_u32::().unwrap() 144 | }; 145 | // Remove the value just read from the vec 146 | let new_chunk_content_len = chunk_content.len() - 4; 147 | for i in 0..new_chunk_content_len { 148 | chunk_content[i] = chunk_content[i + 4]; 149 | } 150 | chunk_content.truncate(new_chunk_content_len); 151 | Ok(CafChunk::AudioDataInMemory( 152 | edit_count, 153 | chunk_content 154 | )) 155 | }, 156 | ChunkType::PacketTable => { 157 | let mut rdr = Cursor::new(&chunk_content); 158 | let num_packets = rdt!(rdr, read_i64); 159 | Ok(CafChunk::PacketTable(PacketTable { 160 | num_valid_frames : rdt!(rdr, read_i64), 161 | num_priming_frames : rdt!(rdr, read_i32), 162 | num_remainder_frames : rdt!(rdr, read_i32), 163 | lengths : { 164 | let mut lengths = Vec::with_capacity(num_packets as usize); 165 | for _ in 0..num_packets { 166 | let b = try!(read_vlq(&mut rdr)); 167 | lengths.push(b); 168 | } 169 | lengths 170 | }, 171 | })) 172 | }, 173 | ChunkType::ChannelLayout => { 174 | let mut rdr = Cursor::new(&chunk_content); 175 | let channel_layout_tag = rdr.read_u32::().unwrap(); 176 | let channel_bitmap = rdr.read_u32::().unwrap(); 177 | let channel_descriptions_count = rdt!(rdr, read_u32); 178 | let mut descs = Vec::with_capacity(channel_descriptions_count as usize); 179 | for _ in 0..channel_descriptions_count { 180 | descs.push(ChannelDescription { 181 | channel_label : rdt!(rdr, read_u32), 182 | channel_flags : rdt!(rdr, read_u32), 183 | coordinates : (rdt!(rdr, read_f32), 184 | rdt!(rdr, read_f32), rdt!(rdr, read_f32)), 185 | }); 186 | } 187 | Ok(CafChunk::ChanLayout(ChannelLayout { 188 | channel_layout_tag : channel_layout_tag, 189 | channel_bitmap : channel_bitmap, 190 | channel_descriptions : descs, 191 | })) 192 | }, 193 | ChunkType::MagicCookie => Ok(CafChunk::MagicCookie( 194 | chunk_content 195 | )), 196 | // ... 197 | ChunkType::Info => { 198 | let mut rdr = Cursor::new(&chunk_content); 199 | let num_entries = rdt!(rdr, read_u32); 200 | let mut res = Vec::with_capacity(num_entries as usize); 201 | for _ in 0..num_entries { 202 | let mut key = Vec::new(); 203 | let mut val = Vec::new(); 204 | try!(rdr.read_until(0, &mut key)); 205 | try!(rdr.read_until(0, &mut val)); 206 | // Remove the trailing \0. Somehow neither 207 | // read_until nor from_utf8 does this for us. 208 | key.pop(); 209 | val.pop(); 210 | res.push((try!(String::from_utf8(key)), try!(String::from_utf8(val)))); 211 | } 212 | Ok(CafChunk::Info(res)) 213 | }, 214 | // ... 215 | _ => try!(Err(CafError::UnsupportedChunkType(chunk_type))), 216 | } 217 | } 218 | 219 | fn read_vlq(rdr :&mut T) -> Result { 220 | let mut res = 0; 221 | let mut buf = [0; 1]; 222 | // TODO ensure we don't exceed 64 bytes. 223 | loop { 224 | try!(rdr.read_exact(&mut buf)); 225 | let byte = buf[0]; 226 | res <<= 7; 227 | res |= (byte & 127) as u64; 228 | if byte & 128 == 0 { 229 | return Ok(res); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 est31 and contributors 2 | 3 | Licensed under MIT or Apache License 2.0, 4 | at your option. 5 | 6 | The full list of contributors can be obtained by looking 7 | at the VCS log (originally, this crate was git versioned, 8 | there you can do "git shortlog -sn" for this task). 9 | 10 | MIT License 11 | ----------- 12 | 13 | The MIT License (MIT) 14 | 15 | Copyright (c) 2017 est31 and contributors 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | 35 | 36 | 37 | Apache License, version 2.0 38 | --------------------------- 39 | Apache License 40 | Version 2.0, January 2004 41 | http://www.apache.org/licenses/ 42 | 43 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 44 | 45 | 1. Definitions. 46 | 47 | "License" shall mean the terms and conditions for use, reproduction, 48 | and distribution as defined by Sections 1 through 9 of this document. 49 | 50 | "Licensor" shall mean the copyright owner or entity authorized by 51 | the copyright owner that is granting the License. 52 | 53 | "Legal Entity" shall mean the union of the acting entity and all 54 | other entities that control, are controlled by, or are under common 55 | control with that entity. For the purposes of this definition, 56 | "control" means (i) the power, direct or indirect, to cause the 57 | direction or management of such entity, whether by contract or 58 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 59 | outstanding shares, or (iii) beneficial ownership of such entity. 60 | 61 | "You" (or "Your") shall mean an individual or Legal Entity 62 | exercising permissions granted by this License. 63 | 64 | "Source" form shall mean the preferred form for making modifications, 65 | including but not limited to software source code, documentation 66 | source, and configuration files. 67 | 68 | "Object" form shall mean any form resulting from mechanical 69 | transformation or translation of a Source form, including but 70 | not limited to compiled object code, generated documentation, 71 | and conversions to other media types. 72 | 73 | "Work" shall mean the work of authorship, whether in Source or 74 | Object form, made available under the License, as indicated by a 75 | copyright notice that is included in or attached to the work 76 | (an example is provided in the Appendix below). 77 | 78 | "Derivative Works" shall mean any work, whether in Source or Object 79 | form, that is based on (or derived from) the Work and for which the 80 | editorial revisions, annotations, elaborations, or other modifications 81 | represent, as a whole, an original work of authorship. For the purposes 82 | of this License, Derivative Works shall not include works that remain 83 | separable from, or merely link (or bind by name) to the interfaces of, 84 | the Work and Derivative Works thereof. 85 | 86 | "Contribution" shall mean any work of authorship, including 87 | the original version of the Work and any modifications or additions 88 | to that Work or Derivative Works thereof, that is intentionally 89 | submitted to Licensor for inclusion in the Work by the copyright owner 90 | or by an individual or Legal Entity authorized to submit on behalf of 91 | the copyright owner. For the purposes of this definition, "submitted" 92 | means any form of electronic, verbal, or written communication sent 93 | to the Licensor or its representatives, including but not limited to 94 | communication on electronic mailing lists, source code control systems, 95 | and issue tracking systems that are managed by, or on behalf of, the 96 | Licensor for the purpose of discussing and improving the Work, but 97 | excluding communication that is conspicuously marked or otherwise 98 | designated in writing by the copyright owner as "Not a Contribution." 99 | 100 | "Contributor" shall mean Licensor and any individual or Legal Entity 101 | on behalf of whom a Contribution has been received by Licensor and 102 | subsequently incorporated within the Work. 103 | 104 | 2. Grant of Copyright License. Subject to the terms and conditions of 105 | this License, each Contributor hereby grants to You a perpetual, 106 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 107 | copyright license to reproduce, prepare Derivative Works of, 108 | publicly display, publicly perform, sublicense, and distribute the 109 | Work and such Derivative Works in Source or Object form. 110 | 111 | 3. Grant of Patent License. Subject to the terms and conditions of 112 | this License, each Contributor hereby grants to You a perpetual, 113 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 114 | (except as stated in this section) patent license to make, have made, 115 | use, offer to sell, sell, import, and otherwise transfer the Work, 116 | where such license applies only to those patent claims licensable 117 | by such Contributor that are necessarily infringed by their 118 | Contribution(s) alone or by combination of their Contribution(s) 119 | with the Work to which such Contribution(s) was submitted. If You 120 | institute patent litigation against any entity (including a 121 | cross-claim or counterclaim in a lawsuit) alleging that the Work 122 | or a Contribution incorporated within the Work constitutes direct 123 | or contributory patent infringement, then any patent licenses 124 | granted to You under this License for that Work shall terminate 125 | as of the date such litigation is filed. 126 | 127 | 4. Redistribution. You may reproduce and distribute copies of the 128 | Work or Derivative Works thereof in any medium, with or without 129 | modifications, and in Source or Object form, provided that You 130 | meet the following conditions: 131 | 132 | (a) You must give any other recipients of the Work or 133 | Derivative Works a copy of this License; and 134 | 135 | (b) You must cause any modified files to carry prominent notices 136 | stating that You changed the files; and 137 | 138 | (c) You must retain, in the Source form of any Derivative Works 139 | that You distribute, all copyright, patent, trademark, and 140 | attribution notices from the Source form of the Work, 141 | excluding those notices that do not pertain to any part of 142 | the Derivative Works; and 143 | 144 | (d) If the Work includes a "NOTICE" text file as part of its 145 | distribution, then any Derivative Works that You distribute must 146 | include a readable copy of the attribution notices contained 147 | within such NOTICE file, excluding those notices that do not 148 | pertain to any part of the Derivative Works, in at least one 149 | of the following places: within a NOTICE text file distributed 150 | as part of the Derivative Works; within the Source form or 151 | documentation, if provided along with the Derivative Works; or, 152 | within a display generated by the Derivative Works, if and 153 | wherever such third-party notices normally appear. The contents 154 | of the NOTICE file are for informational purposes only and 155 | do not modify the License. You may add Your own attribution 156 | notices within Derivative Works that You distribute, alongside 157 | or as an addendum to the NOTICE text from the Work, provided 158 | that such additional attribution notices cannot be construed 159 | as modifying the License. 160 | 161 | You may add Your own copyright statement to Your modifications and 162 | may provide additional or different license terms and conditions 163 | for use, reproduction, or distribution of Your modifications, or 164 | for any such Derivative Works as a whole, provided Your use, 165 | reproduction, and distribution of the Work otherwise complies with 166 | the conditions stated in this License. 167 | 168 | 5. Submission of Contributions. Unless You explicitly state otherwise, 169 | any Contribution intentionally submitted for inclusion in the Work 170 | by You to the Licensor shall be under the terms and conditions of 171 | this License, without any additional terms or conditions. 172 | Notwithstanding the above, nothing herein shall supersede or modify 173 | the terms of any separate license agreement you may have executed 174 | with Licensor regarding such Contributions. 175 | 176 | 6. Trademarks. This License does not grant permission to use the trade 177 | names, trademarks, service marks, or product names of the Licensor, 178 | except as required for reasonable and customary use in describing the 179 | origin of the Work and reproducing the content of the NOTICE file. 180 | 181 | 7. Disclaimer of Warranty. Unless required by applicable law or 182 | agreed to in writing, Licensor provides the Work (and each 183 | Contributor provides its Contributions) on an "AS IS" BASIS, 184 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 185 | implied, including, without limitation, any warranties or conditions 186 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 187 | PARTICULAR PURPOSE. You are solely responsible for determining the 188 | appropriateness of using or redistributing the Work and assume any 189 | risks associated with Your exercise of permissions under this License. 190 | 191 | 8. Limitation of Liability. In no event and under no legal theory, 192 | whether in tort (including negligence), contract, or otherwise, 193 | unless required by applicable law (such as deliberate and grossly 194 | negligent acts) or agreed to in writing, shall any Contributor be 195 | liable to You for damages, including any direct, indirect, special, 196 | incidental, or consequential damages of any character arising as a 197 | result of this License or out of the use or inability to use the 198 | Work (including but not limited to damages for loss of goodwill, 199 | work stoppage, computer failure or malfunction, or any and all 200 | other commercial damages or losses), even if such Contributor 201 | has been advised of the possibility of such damages. 202 | 203 | 9. Accepting Warranty or Additional Liability. While redistributing 204 | the Work or Derivative Works thereof, You may choose to offer, 205 | and charge a fee for, acceptance of support, warranty, indemnity, 206 | or other liability obligations and/or rights consistent with this 207 | License. However, in accepting such obligations, You may act only 208 | on Your own behalf and on Your sole responsibility, not on behalf 209 | of any other Contributor, and only if You agree to indemnify, 210 | defend, and hold each Contributor harmless for any liability 211 | incurred by, or claims asserted against, such Contributor by reason 212 | of your accepting any such warranty or additional liability. 213 | 214 | END OF TERMS AND CONDITIONS 215 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // CAF container decoder written in Rust 2 | // 3 | // Copyright (c) 2017 est31 4 | // and contributors. All rights reserved. 5 | // Licensed under MIT license, or Apache 2 license, 6 | // at your option. Please see the LICENSE file 7 | // attached to this source distribution for details. 8 | 9 | 10 | /*! 11 | An Apple Core Audio Format (CAF) container decoder 12 | 13 | For more information on CAF, see its [wiki page](https://en.wikipedia.org/wiki/Core_Audio_Format), and the [official specification](https://developer.apple.com/documentation/MusicAudio/Reference/CAFSpec/). 14 | */ 15 | 16 | #![forbid(unsafe_code)] 17 | 18 | extern crate byteorder; 19 | 20 | pub mod chunks; 21 | mod enums; 22 | mod error; 23 | 24 | pub use enums::ChunkType; 25 | pub use enums::FormatType; 26 | pub use error::CafError; 27 | 28 | use chunks::CafChunk; 29 | use chunks::CafChunkHeader; 30 | 31 | use std::io::{Read, Seek, SeekFrom}; 32 | use byteorder::{BigEndian as Be, ReadBytesExt}; 33 | 34 | /// The CAF file header 35 | const CAF_HEADER_MAGIC :[u8; 8] = [0x63, 0x61, 0x66, 0x66, 0x00, 0x01, 0x00, 0x00]; 36 | 37 | pub struct CafChunkReader where T :Read { 38 | rdr :T, 39 | } 40 | 41 | impl CafChunkReader where T :Read { 42 | pub fn new(mut rdr :T) -> Result { 43 | let mut hdr_buf = [0;8]; 44 | try!(rdr.read_exact(&mut hdr_buf)); 45 | if hdr_buf != CAF_HEADER_MAGIC { 46 | return Err(CafError::NotCaf); 47 | } 48 | Ok(CafChunkReader { rdr : rdr }) 49 | } 50 | /// Returns the reader that this Reader wraps 51 | pub fn into_inner(self) -> T { 52 | self.rdr 53 | } 54 | // TODO find a better API. 55 | // First, we don't want to pass the audio chunk via memory always. 56 | // Sometimes a file can be very big, so we better leave the choice 57 | // with the users of this library. 58 | // Second, we don't want to decode everything ad-hoc, maybe copying 59 | // around data that the user doesn't really want. 60 | // A good way to fix this would be to add three API functionalities: first one to 61 | // iterate the Read stream on a chunk granularity. The only info we read and 62 | // return here will be the type and size of the chunk. 63 | // The second API functionality should involve a function "give me some sort of view over the raw data of the chunk we last encountered in the first function". 64 | // this will be useful for the audio stuff. 65 | // The third API functionality would involve a function to read+decode the chunk 66 | // directly into memory, very similar to the read_chunk function now. 67 | // On top of this three layer API we can provide a simplified API that contains 68 | // even more logic, e.g. an iterator over audio packets, or similar. Maybe, it 69 | // can be integrated though, not sure... 70 | // The first priority though should be to get the alac crate working with our code. 71 | pub fn read_chunk(&mut self) -> Result { 72 | let hdr = try!(self.read_chunk_header()); 73 | self.read_chunk_body(&hdr) 74 | } 75 | /// Reads a chunk body into memory and decodes it 76 | pub fn read_chunk_body(&mut self, hdr :&CafChunkHeader) 77 | -> Result { 78 | if hdr.ch_size == -1 { 79 | // Unspecified chunk size: this means the chunk is extends up to the EOF. 80 | // TODO handle this case 81 | panic!("unspecified chunk size is not yet implemented"); 82 | } 83 | let mut chunk_content = vec![0; hdr.ch_size as usize]; 84 | try!(self.rdr.read_exact(&mut chunk_content)); 85 | chunks::decode_chunk(hdr.ch_type, chunk_content) 86 | } 87 | /// Reads a chunk header 88 | pub fn read_chunk_header(&mut self) -> Result { 89 | let chunk_type_u32 = try!(self.rdr.read_u32::()); 90 | let chunk_type = ChunkType::from(chunk_type_u32); 91 | // TODO return some kind of error if chunk_size < 0 and != -1 92 | let chunk_size = try!(self.rdr.read_i64::()); 93 | Ok(CafChunkHeader { 94 | ch_type : chunk_type, 95 | ch_size : chunk_size, 96 | }) 97 | } 98 | } 99 | 100 | impl CafChunkReader where T :Read + Seek { 101 | 102 | /** 103 | Seeks to the next chunk header in the file 104 | 105 | It is meant to be called directly after a chunk header 106 | has been read, with the internal reader's position 107 | at the start of a chunk's body. It then seeks to the 108 | next chunk header. 109 | 110 | With this function you can ignore chunks, not reading them, 111 | if they have uninteresting content, or if further knowledge 112 | on the file is needed before their content becomes interesting. 113 | 114 | Panics if the header's chunk size is unspecified per spec (==-1). 115 | "Skipping" would make no sense here, as it will put you to the end of the file. 116 | */ 117 | pub fn to_next_chunk(&mut self, hdr :&CafChunkHeader) -> Result<(), CafError> { 118 | if hdr.ch_size == -1 { 119 | // This would be EOF, makes no sense... 120 | panic!("can't seek to end of chunk with unspecified chunk size."); 121 | } 122 | try!(self.rdr.seek(SeekFrom::Current(hdr.ch_size))); 123 | Ok(()) 124 | } 125 | /** 126 | Seeks to the previous chunk header in the file 127 | 128 | It is meant to be called with the internal reader's position 129 | at the end of a chunk's body. It then seeks to the start of 130 | that chunk body. 131 | 132 | Panics if the header's chunk size is unspecified per spec (==-1). 133 | "Skipping" would make no sense here, as it will put you to the end of the file. 134 | */ 135 | pub fn to_previous_chunk(&mut self, hdr :&CafChunkHeader) -> Result<(), CafError> { 136 | if hdr.ch_size == -1 { 137 | // This would be EOF, makes no sense... 138 | panic!("can't seek to end of chunk with unspecified chunk size."); 139 | } 140 | try!(self.rdr.seek(SeekFrom::Current(-hdr.ch_size))); 141 | Ok(()) 142 | } 143 | 144 | /** 145 | Read chunks from a whitelist to memory 146 | 147 | Uses the given `CafChunkReader` to read all chunks to memory 148 | whose types are inside the `content_read` slice. 149 | Stops as soon as all chunk were encountered with types in the 150 | `required` argument list. 151 | 152 | As we don't have support for reading chunks with unspecified length, 153 | you shouldn't use this function to read audio data to memory. 154 | Generally, reading the audio data chunk to memory is a bad idea 155 | as it may possibly be very big. Instead, use the nice high level 156 | `CafPacketReader` struct. 157 | */ 158 | pub fn read_chunks_to_mem(&mut self, 159 | mut required :Vec, content_read :&[ChunkType]) 160 | -> Result<(Vec, Vec), CafError> { 161 | let mut res = Vec::with_capacity(content_read.len()); 162 | let mut read_headers = Vec::new(); 163 | loop { 164 | let hdr = try!(self.read_chunk_header()); 165 | let mut required_idx = None; 166 | let mut content_read_found = false; 167 | for (i, &searched_type) in required.iter().enumerate() { 168 | if searched_type == hdr.ch_type { 169 | required_idx = Some(i); 170 | break; 171 | } 172 | } 173 | for &searched_type in content_read.iter() { 174 | if searched_type == hdr.ch_type { 175 | content_read_found = true; 176 | break; 177 | } 178 | } 179 | if hdr.ch_size == -1 { 180 | // TODO: return an error. 181 | /* 182 | We don't support chunks with unspecified (=-1) length. 183 | Reading such a chunk to memory would be a bad idea as they 184 | can possibly be gigantic, and are only used for the audio chunk, 185 | which is a very uninteresting target to be read to memory anyways. 186 | Also, such chunks are only found at the end of the file, and if we 187 | encounter them it means we didn't find the chunks we searched for. 188 | */ 189 | } 190 | 191 | match required_idx { None => (), Some(i) => { required.remove(i); } } 192 | if content_read_found { 193 | res.push(try!(self.read_chunk_body(&hdr))); 194 | } else { 195 | try!(self.to_next_chunk(&hdr)); 196 | } 197 | read_headers.push(hdr.clone()); 198 | if required.len() == 0 { 199 | break; 200 | } 201 | } 202 | Ok((res, read_headers)) 203 | } 204 | } 205 | 206 | /** 207 | High level Packet reading 208 | 209 | Provides a very convenient iterator over the packets of the audio chunk. 210 | */ 211 | pub struct CafPacketReader where T :Read + Seek { 212 | ch_rdr :CafChunkReader, 213 | pub audio_desc :chunks::AudioDescription, 214 | pub packet_table :Option, 215 | pub chunks :Vec, 216 | /// The edit count value stored in the audio chunk. 217 | pub edit_count :u32, 218 | audio_chunk_len :i64, 219 | audio_chunk_offs :i64, 220 | packet_idx :usize, 221 | } 222 | 223 | impl CafPacketReader where T :Read + Seek { 224 | /// Creates a new CAF packet reader struct from a given reader. 225 | /// 226 | /// With the `filter_by` argument you can pass a list of chunk types 227 | /// that are important for you. You shouldn't specify three chunk 228 | /// types though: `AudioData`, `AudioDescription` and `PacketTable`. 229 | /// These are implicitly retrieved, and you can extract the content 230 | /// through iterating over the packets (which are all small parts of 231 | /// the `AudioData` chunk), and through the `audio_desc` and `packet_table` 232 | /// members. 233 | /// 234 | /// Equal to calling `CafChunkReader::new` and passing its result to 235 | /// `from_chunk_reader`. 236 | pub fn new(rdr :T, filter_by :Vec) -> Result { 237 | let ch_rdr = try!(CafChunkReader::new(rdr)); 238 | return CafPacketReader::from_chunk_reader(ch_rdr, filter_by); 239 | } 240 | 241 | /// Creates a new CAF packet reader struct from a given chunk reader. 242 | /// 243 | /// With the `filter_by` argument you can pass a list of chunk types 244 | /// that are important for you. You shouldn't specify three chunk 245 | /// types though: `AudioData`, `AudioDescription` and `PacketTable`. 246 | /// These are implicitly retrieved, and you can extract the content 247 | /// through iterating over the packets (which are all small parts of 248 | /// the `AudioData` chunk), and through the `audio_desc` and `packet_table` 249 | /// members. 250 | pub fn from_chunk_reader(mut ch_rdr :CafChunkReader, 251 | mut filter_by :Vec) -> Result { 252 | 253 | // 1. Read all the chunks we need to memory 254 | filter_by.push(ChunkType::AudioDescription); 255 | let mut content_read = filter_by.clone(); 256 | content_read.push(ChunkType::PacketTable); 257 | let (mut chunks_in_mem, mut read_headers) = 258 | try!(ch_rdr.read_chunks_to_mem(filter_by, &content_read)); 259 | 260 | // 2. Extract the special chunks we will need later on 261 | let mut audio_desc_idx = None; 262 | let mut packet_table_idx = None; 263 | for (idx, ch) in chunks_in_mem.iter().enumerate() { 264 | use ChunkType::*; 265 | //println!("{:?}", ch.get_type()); 266 | match ch.get_type() { 267 | AudioDescription => (audio_desc_idx = Some(idx)), 268 | PacketTable => (packet_table_idx = Some(idx)), 269 | _ => (), 270 | } 271 | } 272 | macro_rules! remove_and_unwrap { 273 | ($idx:expr, $id:ident) => { 274 | match chunks_in_mem.remove($idx) { 275 | CafChunk::$id(v) => v, 276 | _ => panic!(), 277 | } 278 | } 279 | } 280 | let audio_desc = remove_and_unwrap!(audio_desc_idx.unwrap(), Desc); 281 | let p_table_required = audio_desc.bytes_per_packet == 0 || 282 | audio_desc.frames_per_packet == 0; 283 | let packet_table = match packet_table_idx { 284 | Some(i) => Some(remove_and_unwrap!(i, PacketTable)), 285 | None if p_table_required => { 286 | let (chunks, hdrs) = try!(ch_rdr.read_chunks_to_mem( 287 | vec![ChunkType::PacketTable], 288 | &content_read)); 289 | chunks_in_mem.extend_from_slice(&chunks); 290 | read_headers.extend_from_slice(&hdrs); 291 | for (idx, ch) in chunks_in_mem.iter().enumerate() { 292 | use ChunkType::*; 293 | match ch.get_type() { 294 | PacketTable => (packet_table_idx = Some(idx)), 295 | _ => (), 296 | } 297 | } 298 | Some(remove_and_unwrap!(packet_table_idx.unwrap(), PacketTable)) 299 | }, 300 | // Only reaches this if p_table_required == false 301 | None => None, 302 | }; 303 | 304 | // 3. Navigate to audio chunk position. 305 | // Check whether we already read the audio block. 306 | // If yes, calculate the amount to seek back to get to it. 307 | let mut audio_chunk_len = 0; 308 | let mut seek_backwards = 0; 309 | const HEADER_LEN :i64 = 12; 310 | for hdr in read_headers.iter() { 311 | if seek_backwards > 0 || hdr.ch_type == ChunkType::AudioData { 312 | seek_backwards += HEADER_LEN; 313 | seek_backwards += hdr.ch_size; 314 | } 315 | if hdr.ch_type == ChunkType::AudioData { 316 | audio_chunk_len = hdr.ch_size; 317 | } 318 | } 319 | if seek_backwards != 0 { 320 | // We already skipped the audio chunk once. 321 | // Seek back to it, and we are done. 322 | seek_backwards -= HEADER_LEN; 323 | //println!("seek_backwards: {}", seek_backwards); 324 | try!(ch_rdr.rdr.seek(SeekFrom::Current(-(seek_backwards as i64)))); 325 | } else { 326 | // The audio chunk is ahead of us. Seek towards it. 327 | loop { 328 | let ch_hdr = try!(ch_rdr.read_chunk_header()); 329 | if ch_hdr.ch_type == ChunkType::AudioData { 330 | audio_chunk_len = ch_hdr.ch_size; 331 | break; 332 | } else { 333 | try!(ch_rdr.to_next_chunk(&ch_hdr)); 334 | } 335 | } 336 | } 337 | // 4. Read the edit count 338 | let edit_count = { 339 | use byteorder::{ReadBytesExt, BigEndian}; 340 | try!(ch_rdr.rdr.read_u32::()) 341 | }; 342 | // 5. Return the result 343 | Ok(CafPacketReader { 344 | ch_rdr : ch_rdr, 345 | audio_desc : audio_desc, 346 | packet_table : packet_table, 347 | chunks : chunks_in_mem, 348 | edit_count : edit_count, 349 | audio_chunk_len : audio_chunk_len, 350 | audio_chunk_offs : 4, // 4 bytes for the edit count. 351 | packet_idx : 0, 352 | }) 353 | } 354 | pub fn into_inner(self) -> CafChunkReader { 355 | self.ch_rdr 356 | } 357 | /// Returns whether the size of the packets doesn't change 358 | /// 359 | /// Some formats have a constant, not changing packet size 360 | /// (mostly the uncompressed ones). 361 | pub fn packet_size_is_constant(&self) -> bool { 362 | return self.audio_desc.bytes_per_packet != 0; 363 | } 364 | /// Returns the size of the next packet in bytes. 365 | /// 366 | /// Returns None if all packets were read, 367 | /// Some(_) otherwise. 368 | /// 369 | /// Very useful if you want to allocate the packet 370 | /// slice yourself. 371 | pub fn next_packet_size(&self) -> Option { 372 | let res = match self.audio_desc.bytes_per_packet { 373 | 0 => match self.packet_table.as_ref() 374 | .unwrap().lengths.get(self.packet_idx) { 375 | Some(v) => *v as usize, 376 | None => return None, 377 | }, 378 | v => v as usize, 379 | }; 380 | if self.audio_chunk_len != -1 && 381 | self.audio_chunk_offs + res as i64 > self.audio_chunk_len { 382 | // We would read outside of the chunk. 383 | // In theory this is a format error as the packet table is not 384 | // supposed to have such a length combination that the sum is larger 385 | // than the size of the audio chunk + 4 for edit_count. 386 | // But we are too lazy to return Result<...> here... 387 | None 388 | } else { 389 | Some(res) 390 | } 391 | } 392 | /// Read one packet from the audio chunk 393 | /// 394 | /// Returns Ok(Some(v)) if the next packet could be read successfully, 395 | /// Ok(None) if its the last chunk. 396 | pub fn next_packet(&mut self) -> Result>, CafError> { 397 | let next_packet_size = match self.next_packet_size() { 398 | Some(v) => v, 399 | None => return Ok(None), 400 | }; 401 | 402 | let mut arr = vec![0; next_packet_size]; 403 | try!(self.ch_rdr.rdr.read_exact(&mut arr)); 404 | self.packet_idx += 1; 405 | self.audio_chunk_offs += next_packet_size as i64; 406 | return Ok(Some(arr)); 407 | } 408 | /// Read one packet from the audio chunk into a pre-allocated array 409 | /// 410 | /// The method doesn't check whether the size of the passed slice matches 411 | /// the actual next packet length, it uses the length blindly. 412 | /// For correct operation, only use sizes returned from the 413 | /// `next_packet_size` function, and only if it didn't return `None`. 414 | pub fn read_packet_into(&mut self, data :&mut [u8]) -> Result<(), CafError> { 415 | try!(self.ch_rdr.rdr.read_exact(data)); 416 | self.packet_idx += 1; 417 | self.audio_chunk_offs += data.len() as i64; 418 | return Ok(()); 419 | } 420 | 421 | /// Gets the number of packets if its known. 422 | pub fn get_packet_count(&self) -> Option { 423 | match &self.packet_table { 424 | &Some(ref t) => Some(t.lengths.len()), 425 | &None => match self.audio_desc.bytes_per_packet { 426 | // We are supposed to never reach this as the constructor 427 | // should enforce a packet table to be present if the 428 | // number of bytes per packet is unspecified. 429 | 0 => panic!("No packet table was stored by the constructor"), 430 | // If the length of the audio chunk is unspecified, 431 | // and there is no packet table, 432 | // we won't know the count of packets. 433 | _ if self.audio_chunk_len == -1 => None, 434 | v => Some((self.audio_chunk_len as usize - 4) / v as usize), 435 | }, 436 | } 437 | } 438 | 439 | /// Returns the index of the currently read packet 440 | pub fn get_packet_idx(&self) -> usize { 441 | self.packet_idx 442 | } 443 | 444 | /// Seeks to the packet with the given index 445 | /// 446 | /// This function never has been tested. 447 | /// If there are bugs please report them. 448 | pub fn seek_to_packet(&mut self, packet_idx :usize) -> Result<(), CafError> { 449 | 450 | let min_idx = ::std::cmp::min(self.packet_idx, packet_idx); 451 | let max_idx = ::std::cmp::min(self.packet_idx, packet_idx); 452 | 453 | // The amount we need to seek by. 454 | let offs :i64 = match self.audio_desc.bytes_per_packet { 455 | 0 => self.packet_table.as_ref() 456 | .unwrap().lengths[min_idx..max_idx].iter().map(|v| *v as i64).sum(), 457 | v => (max_idx - min_idx) as i64 * v as i64, 458 | }; 459 | if self.packet_idx < packet_idx { 460 | try!(self.ch_rdr.rdr.seek(SeekFrom::Current(offs))); 461 | } else if self.packet_idx > packet_idx { 462 | try!(self.ch_rdr.rdr.seek(SeekFrom::Current(-offs))); 463 | } else { 464 | // No seek needed 465 | } 466 | Ok(()) 467 | } 468 | } 469 | --------------------------------------------------------------------------------