Skip to main content

shuyi_sound/
vorbis.rs

1#![cfg(feature = "std")]
2//! Ogg Vorbis files, over the `lewton` crate. Besides the samples it keeps what
3//! a loop importer needs: the file's comments, and where in the sound it is.
4//!
5//! [`seek_page`](VorbisSource::seek_page) lands on a page, not a sample: the
6//! first page whose end is at or past the frame asked for. The packet after a
7//! seek decodes to nothing, as each overlaps the one before, and
8//! [`position`](VorbisSource::position) isn't known until that page ends. So an
9//! exact boundary seeks to a frame some way before it, reads on until the
10//! position is known and is still before the boundary, and throws away the
11//! frames up to it.
12
13use std::io::Cursor;
14
15use lewton::VorbisError;
16use lewton::audio::AudioReadError;
17use lewton::inside_ogg::OggStreamReader;
18
19use crate::error::Error;
20use crate::guard::guarded;
21use crate::pcm::{Limits, PcmFormat};
22use crate::source::{Source, room};
23
24const FORMAT: &str = "Ogg Vorbis";
25/// How far from a file's end its last page can start: the most an Ogg page is.
26const MAX_PAGE_BYTES: usize = 65_307;
27/// Where a page's header holds its flags, granule position and serial number.
28const FLAGS_AT: usize = 5;
29const GRANULE_AT: usize = 6;
30const SERIAL_AT: usize = 14;
31/// The flag of a stream's last page.
32const LAST_PAGE: u8 = 4;
33
34/// The granule position and serial number of the page that ends `bytes`, if it
35/// is a stream's last, as its flag says.
36fn last_page(bytes: &[u8]) -> Option<(u64, u32)> {
37    let tail = &bytes[bytes.len().saturating_sub(MAX_PAGE_BYTES)..];
38    let start = tail.windows(4).rposition(|magic| magic == b"OggS")?;
39    let page = &tail[start..];
40    if page.get(FLAGS_AT)? & LAST_PAGE == 0 {
41        return None;
42    }
43    let granule = page.get(GRANULE_AT..GRANULE_AT + 8)?;
44    let serial = page.get(SERIAL_AT..SERIAL_AT + 4)?;
45    Some((
46        u64::from_le_bytes(granule.try_into().ok()?),
47        u32::from_le_bytes(serial.try_into().ok()?),
48    ))
49}
50
51/// An Ogg Vorbis file's samples, decoded from its bytes as they are asked for. It
52/// borrows them or owns them, as `B` does: a worker that outlives its caller holds
53/// an `Arc<[u8]>`.
54pub struct VorbisSource<B: AsRef<[u8]>> {
55    reader: OggStreamReader<Cursor<B>>,
56    format: PcmFormat,
57    max_frames: u64,
58    /// How many frames it has decoded, to check against `max_frames`.
59    decoded: u64,
60    /// What the last page says is the stream's end, if there is one stream.
61    end: Option<u64>,
62    /// Where the end of the last packet is, in frames from the start of the
63    /// sound. A seek forgets it until a page's end says.
64    at: Option<u64>,
65    /// The last packet's samples, and how many of them have gone out.
66    packet: Vec<i16>,
67    sent: usize,
68    ended: bool,
69    /// The library failed: nothing more comes of this reader.
70    broken: bool,
71}
72
73fn malformed(error: &VorbisError) -> Error {
74    Error::Malformed {
75        format: FORMAT,
76        reason: format!("{error:?}"),
77    }
78}
79
80impl<B: AsRef<[u8]>> VorbisSource<B> {
81    /// A source of `bytes`, whose headers say what it holds, which must be
82    /// within `limits`. It decodes no audio yet.
83    pub fn new(bytes: B, limits: &Limits) -> Result<Self, Error> {
84        guarded(FORMAT, || {
85            let last = last_page(bytes.as_ref());
86            let reader = OggStreamReader::new(Cursor::new(bytes)).map_err(|e| malformed(&e))?;
87            let format = PcmFormat::new(
88                reader.ident_hdr.audio_sample_rate,
89                u32::from(reader.ident_hdr.audio_channels),
90                limits,
91            )?;
92            let serial = reader.stream_serial();
93            let end = last
94                .filter(|(_, last)| *last == serial)
95                .map(|(granule, _)| granule);
96            Ok(Self {
97                reader,
98                format,
99                max_frames: limits.max_frames,
100                decoded: 0,
101                end,
102                at: Some(0),
103                packet: Vec::new(),
104                sent: 0,
105                ended: false,
106                broken: false,
107            })
108        })
109    }
110
111    /// The file's comments, as it has them, each a name and its value.
112    pub fn comments(&self) -> &[(String, String)] {
113        &self.reader.comment_hdr.comment_list
114    }
115
116    /// The first comment called `name`, whatever its case, as Vorbis says names
117    /// are compared.
118    pub fn comment(&self, name: &str) -> Option<&str> {
119        self.comments()
120            .iter()
121            .find(|(key, _)| key.eq_ignore_ascii_case(name))
122            .map(|(_, value)| value.as_str())
123    }
124
125    /// The name of what encoded the file.
126    pub fn vendor(&self) -> &str {
127        &self.reader.comment_hdr.vendor
128    }
129
130    /// What the file's last page says is its end, in frames from the start of
131    /// the sound, if it has such a page. Nothing has checked it yet.
132    pub fn end_granule(&self) -> Option<u64> {
133        self.end
134    }
135
136    /// Where the next frame read is, in frames from the start of the sound. After
137    /// a seek it isn't known until the end of a page has been decoded.
138    pub fn position(&self) -> Option<u64> {
139        let channels = usize::from(self.format.channels.count());
140        let waiting = ((self.packet.len() - self.sent) / channels) as u64;
141        self.at.map(|at| at.saturating_sub(waiting))
142    }
143
144    /// Moves to the first page that ends at or past `frame`, or to the file's end
145    /// if none does, dropping what was decoded of the last packet. The module's
146    /// documentation says how to get from there to a sample. What it has decoded
147    /// still counts against the limit.
148    pub fn seek_page(&mut self, frame: u64) -> Result<(), Error> {
149        self.check_broken()?;
150        let reader = &mut self.reader;
151        let sought = guarded(FORMAT, || {
152            reader.seek_absgp_pg(frame).map_err(|e| malformed(&e))
153        });
154        self.broken = sought.is_err();
155        sought?;
156        self.packet.clear();
157        self.sent = 0;
158        self.ended = false;
159        self.at = None;
160        Ok(())
161    }
162
163    fn check_broken(&self) -> Result<(), Error> {
164        if self.broken {
165            return Err(Error::Malformed {
166                format: FORMAT,
167                reason: "it failed earlier, and the decoder can't go on from there".to_owned(),
168            });
169        }
170        Ok(())
171    }
172
173    /// Decodes the next packet, or says the file has ended.
174    fn next_packet(&mut self) -> Result<(), Error> {
175        let reader = &mut self.reader;
176        let packet = guarded(FORMAT, || match reader.read_dec_packet_itl() {
177            // A seek near the start lands among the headers; the sound follows.
178            Err(VorbisError::BadAudio(AudioReadError::AudioIsHeader)) => Ok(Some(Vec::new())),
179            other => other.map_err(|e| malformed(&e)),
180        });
181        self.broken = packet.is_err();
182        let Some(mut samples) = packet? else {
183            self.ended = true;
184            return Ok(());
185        };
186        let channels = u64::from(self.format.channels.count());
187        let count = samples.len() as u64;
188        if !count.is_multiple_of(channels) {
189            // A stream that changed its channels partway.
190            self.broken = true;
191            return Err(Error::PartialFrame {
192                samples: count,
193                channels: self.format.channels.count(),
194            });
195        }
196        let frames = count / channels;
197        self.decoded += frames;
198        // Only a page's end is exact: the library's count between them isn't.
199        let at = self
200            .at
201            .map_or(self.reader.get_last_absgp(), |at| Some(at + frames));
202        // The last packet decodes whole, and the stream's end cuts it. The library
203        // does that only once a page has ended before it, which in a file of one
204        // page of sound none has.
205        let extra = at
206            .zip(self.end)
207            .map_or(0, |(at, end)| at.saturating_sub(end))
208            .min(frames);
209        samples.truncate(usize::try_from((frames - extra) * channels).unwrap_or(0));
210        self.at = at.map(|at| at - extra);
211        if self.decoded > self.max_frames {
212            self.broken = true;
213            return Err(Error::TooLong {
214                frames: self.decoded,
215                limit: self.max_frames,
216            });
217        }
218        self.packet = samples;
219        self.sent = 0;
220        Ok(())
221    }
222}
223
224impl<B: AsRef<[u8]>> Source for VorbisSource<B> {
225    fn format(&self) -> PcmFormat {
226        self.format
227    }
228
229    /// Not known without decoding: the last page says, and a file can lie.
230    fn total_frames(&self) -> Option<u64> {
231        None
232    }
233
234    fn read(&mut self, out: &mut [i16]) -> Result<usize, Error> {
235        let room = room(self.format, out)?;
236        self.check_broken()?;
237        let channels = usize::from(self.format.channels.count());
238        let wanted = room * channels;
239        let mut rest = &mut out[..wanted];
240        while !rest.is_empty() {
241            let left = &self.packet[self.sent..];
242            if left.is_empty() {
243                if self.ended {
244                    break;
245                }
246                self.next_packet()?;
247                continue;
248            }
249            let count = rest.len().min(left.len());
250            let (filled, after) = core::mem::take(&mut rest).split_at_mut(count);
251            filled.copy_from_slice(&left[..count]);
252            rest = after;
253            self.sent += count;
254        }
255        Ok((wanted - rest.len()) / channels)
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    /// An Ogg page header with these fields and no packets.
264    fn page(flags: u8, granule: u64, serial: u32) -> Vec<u8> {
265        let mut out = b"OggS\0".to_vec();
266        out.push(flags);
267        out.extend_from_slice(&granule.to_le_bytes());
268        out.extend_from_slice(&serial.to_le_bytes());
269        out.extend_from_slice(&[0; 9]);
270        out
271    }
272
273    #[test]
274    fn the_last_page_is_read_from_its_own_fields() {
275        let mut bytes = page(0, 1, 7);
276        bytes.extend_from_slice(&page(LAST_PAGE, 22_050, 9));
277        assert_eq!(last_page(&bytes), Some((22_050, 9)));
278    }
279
280    #[test]
281    fn a_last_page_without_its_flag_or_cut_short_says_nothing() {
282        assert_eq!(last_page(&page(0, 22_050, 9)), None);
283        assert_eq!(last_page(b""), None);
284        assert_eq!(last_page(b"no page here"), None);
285        let whole = page(LAST_PAGE, 22_050, 9);
286        for cut in 4..18 {
287            assert_eq!(last_page(&whole[..cut]), None, "{cut}");
288        }
289    }
290}