1mod aac;
16mod m4a;
17mod mp3;
18mod opus;
19mod pcm;
20
21pub use pcm::DecodedPcm;
22
23use std::path::Path;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum AudioFormat {
28 Wav,
29 Flac,
30 OggOpus,
31 Mp3,
32 M4a,
33 AacAdts,
34 Unknown,
35}
36
37impl AudioFormat {
38 pub fn detect(path: &Path, header: &[u8]) -> Self {
40 if header.len() >= 12 {
41 if &header[0..4] == b"RIFF" && header.len() >= 12 && &header[8..12] == b"WAVE" {
42 return AudioFormat::Wav;
43 }
44 if &header[0..4] == b"fLaC" {
45 return AudioFormat::Flac;
46 }
47 if &header[0..4] == b"OggS" {
48 return AudioFormat::OggOpus;
49 }
50 if &header[4..8] == b"ftyp" {
51 return AudioFormat::M4a;
52 }
53 if header[0] == 0xFF && (header[1] & 0xF6) == 0xF0 {
56 return AudioFormat::AacAdts;
57 }
58 if &header[0..3] == b"ID3" {
59 return AudioFormat::Mp3;
60 }
61 if header[0] == 0xFF && (header[1] & 0xE0) == 0xE0 {
62 return AudioFormat::Mp3;
63 }
64 }
65
66 match path
67 .extension()
68 .and_then(|e| e.to_str())
69 .map(|s| s.to_ascii_lowercase())
70 .as_deref()
71 {
72 Some("wav") => AudioFormat::Wav,
73 Some("flac") => AudioFormat::Flac,
74 Some("opus" | "ogg") => AudioFormat::OggOpus,
75 Some("mp3") => AudioFormat::Mp3,
76 Some("m4a" | "m4b" | "m4p" | "mp4") => AudioFormat::M4a,
77 Some("aac") => AudioFormat::AacAdts,
78 _ => AudioFormat::Unknown,
79 }
80 }
81
82 pub fn extensions(self) -> &'static [&'static str] {
83 match self {
84 AudioFormat::Wav => &["wav"],
85 AudioFormat::Flac => &["flac"],
86 AudioFormat::OggOpus => &["opus", "ogg"],
87 AudioFormat::Mp3 => &["mp3"],
88 AudioFormat::M4a => &["m4a", "m4b", "mp4", "aac"],
89 AudioFormat::AacAdts => &["aac"],
90 AudioFormat::Unknown => &[],
91 }
92 }
93}
94
95pub fn decode_file(path: &Path) -> Result<DecodedPcm, String> {
97 let header = read_header(path, 4096)?;
98 let fmt = AudioFormat::detect(path, &header);
99
100 match fmt {
101 AudioFormat::Wav => decode_wav(path),
102 AudioFormat::Flac => decode_flac(path),
103 AudioFormat::OggOpus => opus::decode_ogg_opus(path),
104 AudioFormat::Mp3 => mp3::decode_mp3_file(path),
105 AudioFormat::M4a => m4a::decode_m4a(path),
106 AudioFormat::AacAdts => aac::decode_adts(path),
107 AudioFormat::Unknown => Err(format!(
108 "unsupported audio format ({}); supported input: wav, flac, opus, mp3, m4a, aac",
109 path.display()
110 )),
111 }
112}
113
114fn decode_flac(path: &Path) -> Result<DecodedPcm, String> {
115 let mut reader = claxon::FlacReader::open(path).map_err(|e| format!("FLAC open: {e}"))?;
116 let info = reader.streaminfo();
117 let channels = info.channels as usize;
118 let scale = 1.0 / (1_u64 << (info.bits_per_sample - 1)) as f64;
119 let mut output = vec![Vec::new(); channels];
120 for (index, sample) in reader.samples().enumerate() {
121 output[index % channels]
122 .push(sample.map_err(|e| format!("FLAC decode: {e}"))? as f64 * scale);
123 }
124 Ok(DecodedPcm {
125 sample_rate: info.sample_rate,
126 channels: output,
127 })
128}
129
130fn read_header(path: &Path, n: usize) -> Result<Vec<u8>, String> {
131 use std::io::Read;
132 let mut f = std::fs::File::open(path).map_err(|e| format!("open: {e}"))?;
133 let mut buf = vec![0u8; n];
134 let got = f.read(&mut buf).map_err(|e| format!("read: {e}"))?;
135 buf.truncate(got);
136 Ok(buf)
137}
138
139fn decode_wav(path: &Path) -> Result<DecodedPcm, String> {
140 let audio = crate::audio::read_wav(path)?;
141 Ok(DecodedPcm {
142 sample_rate: audio.sample_rate,
143 channels: audio.channels,
144 })
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn detect_wav() {
153 let h = b"RIFF\x00\x00\x00\x00WAVE";
154 assert_eq!(AudioFormat::detect(Path::new("x.wav"), h), AudioFormat::Wav);
155 }
156
157 #[test]
158 fn detect_mp3_id3() {
159 assert_eq!(
160 AudioFormat::detect(Path::new("x.mp3"), b"ID3"),
161 AudioFormat::Mp3
162 );
163 }
164
165 #[test]
166 fn detect_m4a_ftyp() {
167 let h = b"\x00\x00\x00\x20ftypM4A ";
168 assert_eq!(AudioFormat::detect(Path::new("x.m4a"), h), AudioFormat::M4a);
169 }
170
171 #[test]
172 fn detect_adts_before_mp3() {
173 let h = b"\xff\xf1\x50\x80\x00\x1f\xfc\x00\x00\x00\x00\x00";
174 assert_eq!(
175 AudioFormat::detect(Path::new("x.aac"), h),
176 AudioFormat::AacAdts
177 );
178 assert_eq!(
179 AudioFormat::detect(Path::new("x.aac"), b""),
180 AudioFormat::AacAdts
181 );
182 }
183}