Skip to main content

falcon_library/audio/
native.rs

1//! The symphonia backend of [`super::AudioSource`].
2
3use std::fs::File;
4use std::path::{Path, PathBuf};
5use std::sync::LazyLock;
6
7use symphonia::core::audio::SampleBuffer;
8use symphonia::core::codecs::{CODEC_TYPE_NULL, CODEC_TYPE_OPUS, CodecRegistry, Decoder, DecoderOptions};
9use symphonia::core::errors::Error;
10use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
11use symphonia::core::io::MediaSourceStream;
12use symphonia::core::meta::MetadataOptions;
13use symphonia::core::probe::{Hint, Probe};
14use symphonia::core::units::{Time, TimeBase};
15use symphonia::default::formats;
16
17use super::{AudioError, AudioSource, Block, adts, edit};
18
19/// symphonia's decoders with the Opus adapter added.
20static CODECS: LazyLock<CodecRegistry> = LazyLock::new(|| {
21    let mut r = CodecRegistry::new();
22    symphonia::default::register_enabled_codecs(&mut r);
23    r.register_all::<super::opus::OpusAdapter>();
24    r
25});
26
27/// symphonia's readers, with falcon's ADTS reader in place of its own.
28static PROBE: LazyLock<Probe> = LazyLock::new(|| {
29    let mut p = Probe::default();
30    p.register_all::<adts::AdtsReader>();
31    p.register_all::<formats::FlacReader>();
32    p.register_all::<formats::IsoMp4Reader>();
33    p.register_all::<formats::MpaReader>();
34    p.register_all::<formats::WavReader>();
35    p.register_all::<formats::OggReader>();
36    p.register_all::<symphonia_metadata::id3v2::Id3v2Reader>();
37    p
38});
39
40/// Opens a file's container.
41pub(crate) fn open_format(path: &Path, ext: &str) -> Result<Box<dyn FormatReader>, AudioError> {
42    let file = File::open(path).map_err(|e| AudioError::Io(e.to_string()))?;
43    let mss = MediaSourceStream::new(Box::new(file), Default::default());
44    let mut hint = Hint::new();
45    hint.with_extension(ext);
46    PROBE
47        .format(&hint, mss, &FormatOptions { enable_gapless: true, ..Default::default() }, &MetadataOptions::default())
48        .map(|p| p.format)
49        .map_err(|e| match e {
50            Error::IoError(e) if e.kind() != std::io::ErrorKind::UnexpectedEof => AudioError::Io(e.to_string()),
51            _ => AudioError::UnknownFormat,
52        })
53}
54
55fn secs(tb: Option<TimeBase>, rate: Option<u32>, ts: u64) -> Option<f64> {
56    match tb {
57        Some(tb) => {
58            let t = tb.calc_time(ts);
59            Some(t.seconds as f64 + t.frac)
60        }
61        None => rate.filter(|r| *r > 0).map(|r| ts as f64 / f64::from(r)),
62    }
63}
64
65/// A file decoded by symphonia.
66pub struct SymphoniaSource {
67    path: PathBuf,
68    ext: String,
69    format: Box<dyn FormatReader>,
70    decoder: Box<dyn Decoder>,
71    track: u32,
72    time_base: Option<TimeBase>,
73    rate: u32,
74    channels: u16,
75    /// Seconds of priming at the stream's start that aren't audio.
76    lead: f64,
77    /// Seconds decoded before a seek's time so the decoder settles (80 ms
78    /// for Opus, RFC 7845 ยง4.6).
79    preroll: f64,
80    duration: Option<f64>,
81    buf: Vec<f32>,
82    /// The part of `buf` still to hand out.
83    start: usize,
84    /// Stream frames before `buf[start]`, priming included.
85    frame: u64,
86    /// Frames to drop from the next decoded blocks, after a seek lands
87    /// before the asked time.
88    skip: u64,
89    ended: bool,
90}
91
92impl SymphoniaSource {
93    pub fn open(path: &Path, ext: &str) -> Result<Self, AudioError> {
94        let format = open_format(path, ext)?;
95        let track = format.tracks().iter().find(|t| t.codec_params.codec != CODEC_TYPE_NULL).ok_or(AudioError::NoAudioTrack)?;
96        let params = track.codec_params.clone();
97        let id = track.id;
98        let decoder = CODECS.make(&params, &DecoderOptions::default()).map_err(|_| AudioError::UnsupportedCodec)?;
99        let header = params.n_frames.and_then(|n| secs(params.time_base, params.sample_rate, n));
100        let (lead, length) = match edit::read(path) {
101            Some(e) => (e.lead, e.length),
102            // An Ogg Opus stream's pre-skip, which symphonia 0.5 reports
103            // but doesn't trim.
104            None if params.codec == CODEC_TYPE_OPUS => {
105                let lead = f64::from(params.delay.unwrap_or(0)) / 48_000.0;
106                (lead, header.map(|h| h - lead))
107            }
108            None => (0.0, None),
109        };
110        let mut s = SymphoniaSource {
111            path: path.to_path_buf(),
112            ext: ext.to_string(),
113            format,
114            decoder,
115            track: id,
116            time_base: params.time_base,
117            rate: params.sample_rate.unwrap_or(0),
118            channels: params.channels.map_or(0, |c| c.count() as u16),
119            lead,
120            preroll: if params.codec == CODEC_TYPE_OPUS { 0.08 } else { 0.0 },
121            duration: length.or(header).filter(|d| *d > 0.0),
122            buf: Vec::new(),
123            start: 0,
124            frame: 0,
125            skip: 0,
126            ended: false,
127        };
128        // Decode the first block now, so a file whose codec fails at once
129        // is refused here and the real rate is known (an MP4's sample
130        // entry can disagree with the codec's own header).
131        if !s.fill() {
132            return Err(AudioError::UnsupportedCodec);
133        }
134        if s.lead > 0.0 {
135            s.skip_frames(s.lead_frames());
136            if s.start >= s.buf.len() && !s.fill() {
137                return Err(AudioError::UnsupportedCodec);
138            }
139        }
140        Ok(s)
141    }
142
143    fn lead_frames(&self) -> u64 {
144        (self.lead * f64::from(self.sample_rate())).round() as u64
145    }
146
147    fn end_frame(&self) -> Option<u64> {
148        self.duration.map(|d| self.lead_frames() + (d * f64::from(self.sample_rate())).round() as u64)
149    }
150
151    /// Drops `n` frames from the buffered block, and the rest from the
152    /// next ones.
153    fn skip_frames(&mut self, n: u64) {
154        let ch = usize::from(self.channels());
155        let here = ((self.buf.len() - self.start) / ch) as u64;
156        let now = n.min(here);
157        self.start += now as usize * ch;
158        self.frame += now;
159        self.skip += n - now;
160    }
161
162    /// Decodes packets until a block with samples is in `buf`; false at
163    /// the end of the stream.
164    fn fill(&mut self) -> bool {
165        self.buf.clear();
166        self.start = 0;
167        while !self.ended {
168            let packet = match self.format.next_packet() {
169                Ok(p) => p,
170                Err(Error::ResetRequired) => {
171                    self.decoder.reset();
172                    continue;
173                }
174                Err(_) => {
175                    self.ended = true;
176                    break;
177                }
178            };
179            if packet.track_id() != self.track {
180                continue;
181            }
182            let decoded = match self.decoder.decode(&packet) {
183                Ok(d) => d,
184                Err(Error::DecodeError(_)) => continue,
185                Err(_) => {
186                    self.ended = true;
187                    break;
188                }
189            };
190            let spec = *decoded.spec();
191            let frames = decoded.frames();
192            if frames == 0 {
193                continue;
194            }
195            let channels = spec.channels.count().max(1);
196            let mut sb = SampleBuffer::<f32>::new(decoded.capacity() as u64, spec);
197            sb.copy_interleaved_ref(decoded);
198            self.rate = spec.rate;
199            self.channels = channels as u16;
200            let drop = self.skip.min(frames as u64) as usize;
201            self.skip -= drop as u64;
202            self.frame += drop as u64;
203            if drop == frames {
204                continue;
205            }
206            let mut keep = frames - drop;
207            if let Some(end) = self.end_frame() {
208                keep = keep.min(end.saturating_sub(self.frame) as usize);
209                if keep == 0 {
210                    self.ended = true;
211                    break;
212                }
213            }
214            self.buf.extend_from_slice(&sb.samples()[drop * channels..(drop + keep) * channels]);
215            return true;
216        }
217        false
218    }
219
220    /// Starts over on a fresh reader, for containers that can't seek from
221    /// where they are.
222    fn reopen(&mut self) -> Result<(), AudioError> {
223        let format = open_format(&self.path, &self.ext)?;
224        let track = format.tracks().iter().find(|t| t.id == self.track).ok_or(AudioError::NoAudioTrack)?;
225        self.decoder = CODECS.make(&track.codec_params, &DecoderOptions::default()).map_err(|_| AudioError::UnsupportedCodec)?;
226        self.format = format;
227        self.frame = 0;
228        self.skip = 0;
229        self.ended = false;
230        self.buf.clear();
231        self.start = 0;
232        Ok(())
233    }
234}
235
236impl AudioSource for SymphoniaSource {
237    fn channels(&self) -> u16 {
238        self.channels.max(1)
239    }
240
241    fn sample_rate(&self) -> u32 {
242        self.rate.max(1)
243    }
244
245    fn duration(&self) -> Option<f64> {
246        self.duration
247    }
248
249    fn next_block(&mut self) -> Option<Block<'_>> {
250        if self.start >= self.buf.len() && !self.fill() {
251            return None;
252        }
253        let from = self.start;
254        self.start = self.buf.len();
255        self.frame += ((self.buf.len() - from) / usize::from(self.channels())) as u64;
256        Some(Block { samples: &self.buf[from..], channels: self.channels(), rate: self.sample_rate() })
257    }
258
259    fn seek(&mut self, to_secs: f64) -> Result<f64, AudioError> {
260        let at = match self.duration {
261            Some(d) => to_secs.clamp(0.0, d),
262            None => to_secs.max(0.0),
263        };
264        let target = at + self.lead;
265        let rate = f64::from(self.sample_rate());
266        let from = (target - self.preroll).max(0.0);
267        let to = || SeekTo::Time { time: Time::new(from.trunc() as u64, from.fract()), track_id: Some(self.track) };
268        let first = self.format.seek(SeekMode::Accurate, to());
269        let seeked = match first {
270            Ok(s) => Some(s),
271            Err(_) => {
272                let to = to();
273                self.reopen()?;
274                self.format.seek(SeekMode::Accurate, to).ok()
275            }
276        };
277        self.decoder.reset();
278        self.ended = false;
279        self.buf.clear();
280        self.start = 0;
281        match seeked {
282            Some(to) => {
283                // The reader lands on a packet at or before the asked
284                // time; the frames in between are decoded and dropped.
285                let actual = secs(self.time_base, Some(self.rate), to.actual_ts).unwrap_or(0.0).min(target);
286                self.frame = (actual * rate).round() as u64;
287                self.skip = ((target - actual) * rate).round() as u64;
288            }
289            // Still no seek: decode from the top and drop up to the time.
290            None => {
291                self.frame = 0;
292                self.skip = (target * rate).round() as u64;
293            }
294        }
295        Ok((self.frame + self.skip) as f64 / rate - self.lead)
296    }
297
298    fn position(&self) -> f64 {
299        ((self.frame + self.skip) as f64 / f64::from(self.sample_rate()) - self.lead).max(0.0)
300    }
301
302    fn backend(&self) -> &'static str {
303        "symphonia"
304    }
305}