Skip to main content

denoize/decode/
mod.rs

1//! denoize 自作デコード層 — MP3 / M4A / WAV を高品質 PCM (`f64`) へ。
2//!
3//! # 設計方針(劣化最小)
4//! - デコード出力は `f32` → `f64` へ拡張のみ(再量子化なし)
5//! - サンプルレート変換なし(ソースレートを維持)
6//! - 内部パイプラインは 32-bit float 相当精度で denoise へ渡す
7//!
8//! # バックエンド
9//! | 形式 | 実装 |
10//! |------|------|
11//! | WAV  | `hound`(既存) |
12//! | MP3  | `nanomp3`(Pure Rust / minimp3 移植) |
13//! | M4A  | `mp4` demux + `oxideav-aac` Pure-Rust AAC-LC decode |
14
15mod aac;
16mod m4a;
17mod mp3;
18mod opus;
19mod pcm;
20
21pub use pcm::DecodedPcm;
22
23use std::path::Path;
24
25/// Detected container / codec family.
26#[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    /// Sniff from file content and extension.
39    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            // ADTS has a 12-bit sync word and its two layer bits are always 0.
54            // Check it before the broader 11-bit MPEG audio sync test.
55            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
95/// Decode any supported audio file to high-fidelity planar PCM.
96pub 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}