1use 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
19static 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
27static 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
40pub(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
65pub 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 lead: f64,
77 preroll: f64,
80 duration: Option<f64>,
81 buf: Vec<f32>,
82 start: usize,
84 frame: u64,
86 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(¶ms, &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 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 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 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 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 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 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 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}