Skip to main content

pito_codecs/opus/
ogg.rs

1use std::io::Write;
2use std::ops::Range;
3use std::sync::OnceLock;
4
5use crate::media::{AudioBuffer, Error, Result};
6use crate::opus::header::OpusHead;
7use crate::opus::multistream::Decoder;
8use crate::opus::packet::packet_samples_48k;
9
10const MAX_PAGE_BODY: usize = 4096;
11const MAX_PAGE_PACKETS: usize = 50;
12
13fn crc_table() -> &'static [u32; 256] {
14    static TABLE: OnceLock<[u32; 256]> = OnceLock::new();
15    TABLE.get_or_init(|| {
16        let mut t = [0u32; 256];
17        for (i, e) in t.iter_mut().enumerate() {
18            let mut r = (i as u32) << 24;
19            for _ in 0..8 {
20                r = if r & 0x8000_0000 != 0 {
21                    (r << 1) ^ 0x04C1_1DB7
22                } else {
23                    r << 1
24                };
25            }
26            *e = r;
27        }
28        t
29    })
30}
31
32pub fn crc32(data: &[u8]) -> u32 {
33    let t = crc_table();
34    data.iter()
35        .fold(0u32, |c, &b| (c << 8) ^ t[((c >> 24) as u8 ^ b) as usize])
36}
37
38pub fn sniff(head: &[u8]) -> bool {
39    head.len() >= 27 && &head[..4] == b"OggS" && head[4] == 0
40}
41
42pub struct OggOpus {
43    pub head: OpusHead,
44    pub tags: Vec<u8>,
45    pub serial: u32,
46    data: Vec<u8>,
47    pub packets: Vec<Range<usize>>,
48    pub granules: Vec<Option<i64>>,
49    pub warnings: Vec<String>,
50}
51
52impl OggOpus {
53    pub fn parse(src: &[u8]) -> Result<OggOpus> {
54        let mut pos = 0usize;
55        let mut serial = None;
56        let mut headers: Vec<Vec<u8>> = Vec::new();
57        let mut data = Vec::new();
58        let mut packets = Vec::new();
59        let mut granules = Vec::new();
60        let mut warnings = Vec::new();
61        let mut partial: Option<usize> = None;
62        let mut hdr_buf = Vec::new();
63        let mut found = false;
64        while pos + 27 <= src.len() {
65            if !sniff(&src[pos..]) {
66                match src[pos + 1..].windows(4).position(|w| w == b"OggS") {
67                    Some(i) => {
68                        if found {
69                            warnings.push(format!("lost sync at byte {pos}"));
70                        }
71                        pos += 1 + i;
72                        continue;
73                    }
74                    None => break,
75                }
76            }
77            let nseg = src[pos + 26] as usize;
78            let lacing_end = pos + 27 + nseg;
79            if lacing_end > src.len() {
80                warnings.push(format!("truncated page at byte {pos}"));
81                break;
82            }
83            let lacing = &src[pos + 27..lacing_end];
84            let body_len: usize = lacing.iter().map(|&b| b as usize).sum();
85            let page_end = lacing_end + body_len;
86            if page_end > src.len() {
87                warnings.push(format!("truncated page at byte {pos}"));
88                break;
89            }
90            let page = &src[pos..page_end];
91            let stored = u32::from_le_bytes([page[22], page[23], page[24], page[25]]);
92            let t = crc_table();
93            let mut crc = 0u32;
94            for (i, &b) in page.iter().enumerate() {
95                let b = if (22..26).contains(&i) { 0 } else { b };
96                crc = (crc << 8) ^ t[((crc >> 24) as u8 ^ b) as usize];
97            }
98            if crc != stored {
99                warnings.push(format!("bad CRC on page at byte {pos}"));
100                pos += 1;
101                continue;
102            }
103            found = true;
104            let flags = page[5];
105            let granule = i64::from_le_bytes([
106                page[6], page[7], page[8], page[9], page[10], page[11], page[12], page[13],
107            ]);
108            let page_serial = u32::from_le_bytes([page[14], page[15], page[16], page[17]]);
109            pos = page_end;
110            match serial {
111                None if flags & 0x02 != 0 && src[lacing_end..page_end].starts_with(b"OpusHead") => {
112                    serial = Some(page_serial)
113                }
114                Some(s) if s == page_serial => {}
115                _ => continue,
116            }
117            if flags & 0x01 == 0 && partial.is_some() {
118                warnings.push(format!("incomplete packet before page at byte {pos}"));
119                partial = None;
120                hdr_buf.clear();
121            }
122            let mut skip = flags & 0x01 != 0 && partial.is_none() && headers.len() == 2;
123            let mut off = lacing_end;
124            let mut completed_any = false;
125            for &l in lacing {
126                let seg = &src[off..off + l as usize];
127                off += l as usize;
128                if skip {
129                    if l < 255 {
130                        skip = false;
131                    }
132                    continue;
133                }
134                if headers.len() < 2 {
135                    hdr_buf.extend_from_slice(seg);
136                    partial = Some(0);
137                    if l < 255 {
138                        headers.push(std::mem::take(&mut hdr_buf));
139                        partial = None;
140                    }
141                    continue;
142                }
143                let start = *partial.get_or_insert(data.len());
144                data.extend_from_slice(seg);
145                if l < 255 {
146                    packets.push(start..data.len());
147                    granules.push(None);
148                    partial = None;
149                    completed_any = true;
150                }
151            }
152            if completed_any
153                && granule != -1
154                && let Some(g) = granules.last_mut()
155            {
156                *g = Some(granule);
157            }
158        }
159        if !found {
160            return Err(Error::Invalid("not an Ogg file".into()));
161        }
162        let mut headers = headers.into_iter();
163        let head_bytes = headers
164            .next()
165            .ok_or_else(|| Error::Invalid("no Opus stream in the Ogg file".into()))?;
166        let head = OpusHead::parse(&head_bytes)?;
167        let tags = headers.next().unwrap_or_default();
168        Ok(OggOpus {
169            head,
170            tags,
171            serial: serial.unwrap_or(0),
172            data,
173            packets,
174            granules,
175            warnings,
176        })
177    }
178
179    pub fn packet(&self, i: usize) -> &[u8] {
180        &self.data[self.packets[i].clone()]
181    }
182
183    pub fn end_granule(&self) -> Option<i64> {
184        self.granules.iter().rev().find_map(|g| *g)
185    }
186
187    pub fn samples(&self) -> u64 {
188        let coded: u64 = (0..self.packets.len())
189            .map(|i| packet_samples_48k(self.packet(i)).unwrap_or(0) as u64)
190            .sum();
191        let end = self.end_granule().map_or(coded, |g| g.max(0) as u64);
192        end.min(coded).saturating_sub(self.head.pre_skip as u64)
193    }
194
195    pub fn decode(&self) -> Result<AudioBuffer> {
196        let mut dec = Decoder::from_head(self.head.clone(), 48000)?;
197        let channels = dec.channels();
198        let total = self.samples() as usize;
199        let skip = self.head.pre_skip as usize;
200        let mut out = AudioBuffer::new(48000, channels as u16);
201        let most = self.packets.len().saturating_mul(5760);
202        let frames = total.saturating_add(skip).saturating_add(5760).min(most);
203        out.samples.reserve(frames.saturating_mul(channels));
204        for i in 0..self.packets.len() {
205            dec.decode_into(Some(self.packet(i)), &mut out.samples)?;
206        }
207        let have = out.samples.len() / channels.max(1);
208        out.samples.drain(..skip.min(have) * channels);
209        out.samples.truncate(total.saturating_mul(channels));
210        Ok(out)
211    }
212}
213
214pub fn decode(src: &[u8]) -> Result<AudioBuffer> {
215    OggOpus::parse(src)?.decode()
216}
217
218pub struct Writer<W: Write> {
219    w: W,
220    serial: u32,
221    sequence: u32,
222    body: Vec<u8>,
223    lacing: Vec<u8>,
224    packets: usize,
225    granule: i64,
226    page: Vec<u8>,
227}
228
229impl<W: Write> Writer<W> {
230    pub fn new(w: W, serial: u32, head: &OpusHead, vendor: &str) -> Result<Self> {
231        let mut s = Writer {
232            w,
233            serial,
234            sequence: 0,
235            body: Vec::new(),
236            lacing: Vec::new(),
237            packets: 0,
238            granule: 0,
239            page: Vec::new(),
240        };
241        s.add(&head.to_bytes());
242        s.flush_page(0x02, 0)?;
243        let mut tags = b"OpusTags".to_vec();
244        tags.extend((vendor.len() as u32).to_le_bytes());
245        tags.extend(vendor.as_bytes());
246        tags.extend(0u32.to_le_bytes());
247        s.add(&tags);
248        s.flush_page(0, 0)?;
249        Ok(s)
250    }
251
252    fn add(&mut self, packet: &[u8]) {
253        let mut n = packet.len();
254        while n >= 255 {
255            self.lacing.push(255);
256            n -= 255;
257        }
258        self.lacing.push(n as u8);
259        self.body.extend_from_slice(packet);
260        self.packets += 1;
261    }
262
263    pub fn push(&mut self, packet: &[u8], granule: i64) -> Result<()> {
264        if self.lacing.len() + packet.len() / 255 + 1 > 255 {
265            self.flush_page(0, self.granule)?;
266        }
267        self.add(packet);
268        self.granule = granule;
269        if self.body.len() >= MAX_PAGE_BODY || self.packets >= MAX_PAGE_PACKETS {
270            self.flush_page(0, granule)?;
271        }
272        Ok(())
273    }
274
275    fn flush_page(&mut self, flags: u8, granule: i64) -> Result<()> {
276        let mut segs = &self.lacing[..];
277        let mut body = &self.body[..];
278        let mut continued = false;
279        loop {
280            let n = segs.len().min(255);
281            let (now, rest) = segs.split_at(n);
282            let len: usize = now.iter().map(|&b| b as usize).sum();
283            let last = rest.is_empty();
284            let page_granule = if last && now.last().is_none_or(|&l| l < 255) {
285                granule
286            } else {
287                -1
288            };
289            let page = &mut self.page;
290            page.clear();
291            page.extend_from_slice(b"OggS\0");
292            page.push(flags | if continued { 0x01 } else { 0 });
293            page.extend(page_granule.to_le_bytes());
294            page.extend(self.serial.to_le_bytes());
295            page.extend(self.sequence.to_le_bytes());
296            page.extend([0u8; 4]);
297            page.push(n as u8);
298            page.extend_from_slice(now);
299            page.extend_from_slice(&body[..len]);
300            let crc = crc32(page);
301            page[22..26].copy_from_slice(&crc.to_le_bytes());
302            self.w.write_all(page)?;
303            self.sequence += 1;
304            body = &body[len..];
305            segs = rest;
306            continued = now.last().is_some_and(|&l| l == 255);
307            if last {
308                break;
309            }
310        }
311        self.lacing.clear();
312        self.body.clear();
313        self.packets = 0;
314        Ok(())
315    }
316
317    pub fn finish(mut self) -> Result<W> {
318        self.flush_page(0x04, self.granule)?;
319        self.w.flush()?;
320        Ok(self.w)
321    }
322}