access_unit/
lib.rs

1use bytes::Bytes;
2
3pub mod aac;
4pub mod chunk;
5pub mod flac;
6pub mod h264;
7pub mod mp3;
8pub mod mp4;
9pub mod webm;
10
11pub const PSI_STREAM_MP3: u8 = 0x04; // ISO/IEC 13818-3 Audio
12pub const PSI_STREAM_PRIVATE_DATA: u8 = 0x06;
13pub const PSI_STREAM_H264: u8 = 0x1b; // H.264
14pub const PSI_STREAM_AAC: u8 = 0x0f;
15pub const PSI_STREAM_MPEG4_AAC: u8 = 0x1c;
16pub const PSI_STREAM_AUDIO_OPUS: u8 = 0x9c;
17
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum AudioType {
20    Unknown,
21    AAC,  // Raw AAC (ADTS format)
22    M4A,  // AAC in MP4/M4A container
23    FLAC,
24    MP3,
25    OggOpus,
26    Opus,
27    Wav,
28    WebM,
29}
30
31#[derive(Debug, Clone)]
32pub struct Fmp4 {
33    pub init: Option<Bytes>,
34    pub key: bool,
35    pub data: Bytes,
36    pub duration: u32,
37}
38
39#[derive(Debug, Clone)]
40pub struct AccessUnit {
41    pub key: bool,
42    pub pts: u64,
43    pub dts: u64,
44    pub data: Bytes,
45    pub stream_type: u8,
46    pub id: u64,
47}
48
49pub fn detect_audio(data: &[u8]) -> AudioType {
50    // Quick M4A detection via ftyp box (works even with moov at end)
51    if mp4::is_m4a(data) {
52        return AudioType::M4A;
53    }
54    // Full MP4 parsing for other containers (MP4 with video, etc.)
55    if let Some(audio_type) = mp4::detect_audio_track(data) {
56        return audio_type;
57    }
58    if flac::is_flac(data) {
59        AudioType::FLAC
60    } else if aac::is_aac(data) {
61        AudioType::AAC
62    } else if webm::is_webm(data) {
63        AudioType::WebM
64    } else if is_ogg_opus(data) {
65        AudioType::OggOpus
66    } else if is_opus(data) {
67        AudioType::Opus
68    } else if is_wav(data) {
69        AudioType::Wav
70    } else if mp3::is_mp3(data) {
71        AudioType::MP3
72    } else {
73        AudioType::Unknown
74    }
75}
76
77pub fn is_mp4(data: &[u8]) -> bool {
78    mp4::is_mp4(data)
79}
80
81pub fn is_webm(data: &[u8]) -> bool {
82    webm::is_webm(data)
83}
84
85fn is_wav(data: &[u8]) -> bool {
86    data.len() >= 12 && &data[0..4] == b"RIFF" && &data[8..12] == b"WAVE"
87}
88
89fn is_opus(data: &[u8]) -> bool {
90    if data.starts_with(b"OggS") {
91        return false;
92    }
93
94    let search_len = data.len().min(64);
95    data[..search_len]
96        .windows(b"OpusHead".len())
97        .any(|w| w == b"OpusHead")
98}
99
100fn is_ogg_opus(data: &[u8]) -> bool {
101    if data.len() < 36 || !data.starts_with(b"OggS") {
102        return false;
103    }
104    // Look for the Opus ID header within the first page payload
105    let search_len = data.len().min(256);
106    data[..search_len]
107        .windows(b"OpusHead".len())
108        .any(|w| w == b"OpusHead")
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use std::fs;
115
116    fn read(path: &str) -> Vec<u8> {
117        fs::read(path).unwrap_or_else(|err| panic!("read {}: {}", path, err))
118    }
119
120    #[test]
121    fn detect_aac_from_testdata() {
122        let data = read("testdata/wav_stereo/A_Tusk_is_used_to_make_costly_gifts.wav.aac");
123        assert_eq!(detect_audio(&data), AudioType::AAC);
124    }
125
126    #[test]
127    fn detect_flac_from_testdata() {
128        let data = read("testdata/flac/A_Tusk_is_used_to_make_costly_gifts.flac");
129        assert_eq!(detect_audio(&data), AudioType::FLAC);
130    }
131
132    #[test]
133    fn detect_mp3_from_testdata() {
134        let data = read("testdata/mp3/A_Tusk_is_used_to_make_costly_gifts.mp3");
135        assert_eq!(detect_audio(&data), AudioType::MP3);
136
137        let (offset, header) = mp3::find_frame(&data).expect("mp3 frame");
138        assert!(offset < data.len());
139        assert!(header.frame_length > 0);
140    }
141
142    #[test]
143    fn detect_mp4_audio_from_testdata() {
144        let data = read("testdata/mp4/heat.mp4");
145        assert_eq!(detect_audio(&data), AudioType::AAC);
146    }
147
148    #[test]
149    fn detect_wav_from_testdata() {
150        let data = read("testdata/wav_stereo/A_Tusk_is_used_to_make_costly_gifts.wav");
151        assert_eq!(detect_audio(&data), AudioType::Wav);
152    }
153
154    #[test]
155    fn detect_opus_from_testdata() {
156        let data = read("testdata/opus/A_Tusk_is_used_to_make_costly_gifts.opus");
157        assert_eq!(detect_audio(&data), AudioType::Opus);
158    }
159
160    #[test]
161    fn detect_ogg_opus_from_testdata() {
162        let data = read("testdata/ogg_opus/A_Tusk_is_used_to_make_costly_gifts.ogg");
163        assert_eq!(detect_audio(&data), AudioType::OggOpus);
164    }
165
166    #[test]
167    fn detect_webm_from_testdata() {
168        let data = read("testdata/webm/A_Tusk_is_used_to_make_costly_gifts.webm");
169        assert_eq!(detect_audio(&data), AudioType::WebM);
170    }
171}