1use std::{f32::consts::SQRT_2, fs::File};
6
7use audioadapter_buffers::direct::InterleavedSlice;
8use rubato::{Fft, FixedSync, Resampler};
9use symphonia::{
10 core::{
11 audio::{layouts::CHANNEL_LAYOUT_STEREO, AudioSpec, GenericAudioBufferRef},
12 codecs::audio::AudioDecoderOptions,
13 errors::Error,
14 formats::probe::Hint,
15 formats::{FormatReader, TrackType},
16 io::{MediaSourceStream, MediaSourceStreamOptions},
17 meta::MetadataOptions,
18 units,
19 },
20 default::get_probe,
21};
22use thiserror::Error;
23
24use crate::{BlissError, BlissResult, SAMPLE_RATE};
25
26use super::{Decoder, PreAnalyzedSong};
27
28#[derive(Debug, Error, PartialEq, Eq, Clone)]
29pub enum SymphoniaDecoderError {
31 #[error("Failed to resample audio: {0}")]
32 ResampleError(String),
35 #[error("Failed to create resampler: {0}")]
36 ResamplerConstructionError(String),
39 #[error("IO Error: {0}")]
40 IoError(String),
42 #[error("Failed to decode audio: {0}")]
43 DecodeError(String),
46 #[error("Unsupported codec")]
47 UnsupportedCodec,
49 #[error("No supported audio tracks")]
50 NoSupportedAudioTracks,
52 #[error("No streams")]
53 NoStreams,
55 #[error("The audio source's duration is either unknown or infinite")]
56 IndeterminantDuration,
58}
59
60impl From<rubato::ResampleError> for SymphoniaDecoderError {
61 fn from(err: rubato::ResampleError) -> Self {
62 Self::ResampleError(err.to_string())
63 }
64}
65impl From<rubato::ResamplerConstructionError> for SymphoniaDecoderError {
66 fn from(err: rubato::ResamplerConstructionError) -> Self {
67 Self::ResamplerConstructionError(err.to_string())
68 }
69}
70impl From<std::io::Error> for SymphoniaDecoderError {
71 fn from(err: std::io::Error) -> Self {
72 Self::IoError(err.to_string())
73 }
74}
75impl From<Error> for SymphoniaDecoderError {
76 fn from(err: Error) -> Self {
77 Self::DecodeError(err.to_string())
78 }
79}
80impl From<SymphoniaDecoderError> for BlissError {
81 fn from(err: SymphoniaDecoderError) -> Self {
82 Self::DecodingError(err.to_string())
83 }
84}
85
86const MAX_DECODE_RETRIES: usize = 3;
87const CHUNK_SIZE: usize = 4096;
88
89struct SymphoniaSource {
91 decoder: Box<dyn symphonia::core::codecs::audio::AudioDecoder>,
92 current_span_offset: usize,
93 format: Box<dyn FormatReader>,
94 total_duration: Option<units::Time>,
95 buffer: Vec<f32>,
96 spec: AudioSpec,
97}
98
99impl SymphoniaSource {
100 pub fn new(mss: MediaSourceStream<'static>) -> Result<Self, SymphoniaDecoderError> {
101 match Self::init(mss) {
102 Err(e) => match e {
103 Error::IoError(e) => Err(SymphoniaDecoderError::IoError(e.to_string())),
104 Error::SeekError(_) => {
105 unreachable!("Seek errors should not occur during initialization")
106 }
107 error => Err(SymphoniaDecoderError::DecodeError(error.to_string())),
108 },
109 Ok(Some(decoder)) => Ok(decoder),
110 Ok(None) => Err(SymphoniaDecoderError::NoStreams),
111 }
112 }
113
114 fn init(mss: MediaSourceStream<'static>) -> symphonia::core::errors::Result<Option<Self>> {
118 let hint = Hint::new();
119 let format_opts = Default::default();
120 let metadata_opts = MetadataOptions::default();
121 let mut format = get_probe().probe(&hint, mss, format_opts, metadata_opts)?;
122
123 if format.default_track(TrackType::Audio).is_none() {
124 return Ok(None);
125 };
126
127 let track = format
129 .default_track(TrackType::Audio)
130 .or_else(|| {
131 format.tracks().iter().find(|t| {
132 t.codec_params
133 .as_ref()
134 .and_then(|params| params.audio())
135 .is_some()
136 })
137 })
138 .ok_or(Error::Unsupported("No track with supported codec"))?;
139
140 let track_id = track.id;
141
142 let mut decoder = symphonia::default::get_codecs().make_audio_decoder(
143 track
144 .codec_params
145 .as_ref()
146 .ok_or(Error::Unsupported(
147 "Unable to determine the codec parameters",
148 ))?
149 .audio()
150 .ok_or(Error::Unsupported("The codec is not an audio codec"))?,
151 &AudioDecoderOptions::default(),
152 )?;
153 let total_duration = track.time_base.zip(track.duration).and_then(|(tb, dur)| {
154 let ts = units::Timestamp::ZERO.saturating_add(dur);
155 tb.calc_time(ts)
156 });
157
158 let mut decode_errors: usize = 0;
159 let decoded = loop {
160 let current_span = match format.next_packet() {
161 Ok(Some(packet)) => packet,
162 Ok(None) => break decoder.last_decoded(),
163 Err(e) => return Err(e),
164 };
165
166 if current_span.track_id != track_id {
168 continue;
169 }
170
171 match decoder.decode(¤t_span) {
172 Ok(decoded) => break decoded,
173 Err(Error::DecodeError(_)) if decode_errors < MAX_DECODE_RETRIES => {
174 decode_errors += 1;
175 continue;
176 }
177 Err(e) => return Err(e),
178 }
179 };
180
181 let spec = decoded.spec().to_owned();
182 let buffer = Self::get_buffer(decoded);
183 Ok(Some(Self {
184 decoder,
185 current_span_offset: 0,
186 format,
187 total_duration,
188 buffer,
189 spec,
190 }))
191 }
192
193 #[inline]
194 fn get_buffer(decoded: GenericAudioBufferRef) -> Vec<f32> {
195 let mut buffer: Vec<f32> = vec![0.0; decoded.samples_interleaved()];
196 decoded.copy_to_slice_interleaved(&mut buffer);
197 buffer
198 }
199}
200
201impl Iterator for SymphoniaSource {
205 type Item = f32;
206
207 fn size_hint(&self) -> (usize, Option<usize>) {
208 (
209 self.buffer.len(),
210 self.total_duration.map(|dur| {
211 (dur.as_secs() + 1) as usize
212 * self.spec.rate() as usize
213 * self.spec.channels().count()
214 }),
215 )
216 }
217
218 fn next(&mut self) -> Option<Self::Item> {
219 if self.current_span_offset >= self.buffer.len() {
220 let mut decode_errors = 0;
221 let decoded = loop {
222 let packet = self.format.next_packet().ok()??;
223 match self.decoder.decode(&packet) {
224 Ok(decoded) if decoded.frames() > 0 => break decoded,
230 Ok(_) => continue,
231 Err(Error::DecodeError(_)) if decode_errors < MAX_DECODE_RETRIES => {
232 decode_errors += 1;
233 continue;
234 }
235 Err(_) => return None,
236 }
237 };
238
239 decoded.spec().clone_into(&mut self.spec);
240 self.buffer = Self::get_buffer(decoded);
241 self.current_span_offset = 1;
242 return self.buffer.first().copied();
243 }
244
245 let sample = self.buffer.get(self.current_span_offset);
246 self.current_span_offset += 1;
247
248 sample.copied()
249 }
250}
251
252pub struct SymphoniaDecoder;
254
255impl SymphoniaDecoder {
256 #[inline]
266 fn into_mono_samples(source: SymphoniaSource) -> Result<Vec<f32>, SymphoniaDecoderError> {
267 let num_channels = source.spec.channels().count();
268 if source.total_duration.is_none() {
269 return Err(SymphoniaDecoderError::IndeterminantDuration);
270 }
271
272 match num_channels {
273 0 => Err(SymphoniaDecoderError::NoStreams),
275 1 => Ok(source.collect()),
277 2 => {
279 assert!(*source.spec.channels() == CHANNEL_LAYOUT_STEREO);
280
281 let mono_samples = source
282 .collect::<Vec<_>>()
283 .chunks_exact(2)
284 .map(|chunk| (chunk[0] + chunk[1]) * SQRT_2 / 2.)
285 .collect();
286
287 Ok(mono_samples)
288 }
289 _ => {
291 log::warn!("The audio source has more than 2 channels (might be 2.1 or 5.1 surround sound), will collapse to mono by averaging the channels");
292
293 let mono_samples = source
294 .collect::<Vec<_>>()
295 .chunks_exact(num_channels)
296 .map(|chunk| chunk.iter().sum::<f32>() / num_channels as f32)
297 .collect();
298
299 Ok(mono_samples)
300 }
301 }
302 }
303
304 #[inline]
306 fn resample_mono_samples(
307 mut samples: Vec<f32>,
308 sample_rate: u32,
309 ) -> Result<Vec<f32>, SymphoniaDecoderError> {
310 if sample_rate == SAMPLE_RATE {
311 samples.shrink_to_fit();
312 return Ok(samples);
313 }
314
315 let mut resampler = Fft::new(
316 sample_rate as usize,
317 SAMPLE_RATE as usize,
318 CHUNK_SIZE,
319 4,
320 1,
321 FixedSync::Input,
322 )
323 .map_err(SymphoniaDecoderError::from)?;
324
325 let capacity = resampler.process_all_needed_output_len(samples.len());
326 let mut resampled = Vec::with_capacity(capacity);
327
328 let delay = resampler.output_delay();
329
330 let output_chunk_size = resampler.output_frames_max();
332 let input_chunk_size = resampler.input_frames_next();
333 let mut output_buffer = vec![0.0; output_chunk_size];
334
335 let sample_chunks = samples.chunks_exact(input_chunk_size);
337 let remainder = sample_chunks.remainder();
338
339 for chunk in sample_chunks {
340 debug_assert!(resampler.input_frames_next() == input_chunk_size);
341
342 let input = InterleavedSlice::new(chunk, 1, input_chunk_size)
343 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
344
345 let mut output_adapter =
346 InterleavedSlice::new_mut(&mut output_buffer, 1, output_chunk_size)
347 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
348 let (_, output_written) =
349 resampler.process_into_buffer(&input, &mut output_adapter, None)?;
350 resampled.extend_from_slice(&output_buffer[..output_written]);
351 }
352
353 if !remainder.is_empty() {
355 let remainder_indexing = rubato::Indexing {
356 input_offset: 0,
357 output_offset: 0,
358 partial_len: Some(remainder.len()),
359 active_channels_mask: None,
360 };
361 let input = InterleavedSlice::new(remainder, 1, remainder.len())
362 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
363 let mut output_adapter =
364 InterleavedSlice::new_mut(&mut output_buffer, 1, output_chunk_size)
365 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
366
367 let (_, output_written) = resampler.process_into_buffer(
368 &input,
369 &mut output_adapter,
370 Some(&remainder_indexing),
371 )?;
372 resampled.extend_from_slice(&output_buffer[..output_written]);
373 }
374
375 let flush_indexing = rubato::Indexing {
376 input_offset: 0,
377 output_offset: 0,
378 partial_len: Some(0),
379 active_channels_mask: None,
380 };
381
382 let expected_output_len =
383 (resampler.resample_ratio() * samples.len() as f64).ceil() as usize;
384
385 let padded_zeros = vec![0.0; input_chunk_size];
387 while resampled.len() < expected_output_len + delay {
388 let input = InterleavedSlice::new(&padded_zeros, 1, input_chunk_size)
389 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
390 let mut output_adapter =
391 InterleavedSlice::new_mut(&mut output_buffer, 1, output_chunk_size)
392 .map_err(|e| SymphoniaDecoderError::ResampleError(e.to_string()))?;
393
394 let (_, output_written) = resampler.process_into_buffer(
395 &input,
396 &mut output_adapter,
397 Some(&flush_indexing),
398 )?;
399 resampled.extend_from_slice(&output_buffer[..output_written]);
400 }
401
402 Ok(resampled[delay..expected_output_len + delay].to_vec())
403 }
404}
405
406impl Decoder for SymphoniaDecoder {
407 #[allow(clippy::missing_inline_in_public_items)]
413 fn decode(path: &std::path::Path) -> BlissResult<PreAnalyzedSong> {
414 let file = File::open(path).map_err(SymphoniaDecoderError::from)?;
416 let mss = MediaSourceStream::new(Box::new(file), MediaSourceStreamOptions::default());
418
419 let source = SymphoniaSource::new(mss)?;
420
421 let sample_rate = source.spec.rate();
423 if source.total_duration.is_none() {
424 return Err(SymphoniaDecoderError::IndeterminantDuration.into());
425 };
426
427 let mono_sample_array = Self::into_mono_samples(source)?;
428
429 let resampled_array = Self::resample_mono_samples(mono_sample_array, sample_rate)?;
431
432 Ok(PreAnalyzedSong {
433 path: path.to_owned(),
434 sample_array: resampled_array,
435 ..Default::default()
436 })
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::{Decoder as DecoderTrait, SymphoniaDecoder as Decoder};
443 use adler32::RollingAdler32;
444 use pretty_assertions::assert_eq;
445 use std::path::Path;
446
447 fn _test_decode(path: &Path, expected_hash: u32) {
448 let song = Decoder::decode(path).unwrap();
449 let mut hasher = RollingAdler32::new();
450 for sample in &song.sample_array {
451 hasher.update_buffer(&sample.to_le_bytes());
452 }
453
454 assert_eq!(expected_hash, hasher.hash());
455 }
456
457 #[cfg(feature = "symphonia-wav")]
460 #[test]
461 fn test_decode_wav() {
462 let expected_hash = 0xde831e82;
463 _test_decode(Path::new("data/piano.wav"), expected_hash);
464 }
465
466 #[cfg(feature = "symphonia-flac")]
467 #[test]
468 #[ignore = "fails when asked to resample to 22050 Hz, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
469 fn test_resample_mono() {
470 let path = Path::new("data/s32_mono_44_1_kHz.flac");
471 let expected_hash = 0xa0f8b8af;
472 _test_decode(&path, expected_hash);
473 }
474
475 #[cfg(feature = "symphonia-flac")]
476 #[test]
477 #[ignore = "fails when asked to resample to 22050 Hz, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
478 fn test_resample_frame_rate() {
479 let path = Path::new("data/s16_mono_44_1_kHz.flac");
480 let expected_hash = 0xa0f8b8af;
481
482 _test_decode(&path, expected_hash);
483 }
484
485 #[cfg(feature = "symphonia-flac")]
486 #[test]
487 fn test_resample_mono_ffmpeg_v_symphonia() {
488 let path = Path::new("data/s32_mono_44_1_kHz.flac");
531 let symphonia_decoded = Decoder::decode(&path).unwrap();
532 let ffmpeg_decoded = crate::decoder::ffmpeg::FFmpegDecoder::decode(&path).unwrap();
533 let mut diff = 0.0;
544 for (a, b) in symphonia_decoded
545 .sample_array
546 .iter()
547 .zip(ffmpeg_decoded.sample_array.iter())
548 {
549 diff += (a - b).abs();
550 }
551 diff /= symphonia_decoded.sample_array.len() as f32;
552 assert!(
553 diff < 1.0e-5,
554 "Difference between symphonia and ffmpeg: {}",
555 diff
556 );
557 }
558
559 #[cfg(feature = "symphonia-flac")]
560 #[test]
561 #[ignore = "fails when asked to resample to 22050 Hz, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
562 fn test_resample_multi() {
563 let path = Path::new("data/s32_stereo_44_1_kHz.flac");
564 let expected_hash = 0xbbcba1cf;
565 _test_decode(&path, expected_hash);
566 }
567
568 #[cfg(feature = "symphonia-flac")]
569 #[test]
570 fn test_resample_multi_ffmpeg_v_symphonia() {
571 let path = Path::new("data/s32_stereo_44_1_kHz.flac");
572 let symphonia_decoded = Decoder::decode(&path).unwrap();
573 let ffmpeg_decoded = crate::decoder::ffmpeg::FFmpegDecoder::decode(&path).unwrap();
574
575 let mut diff = 0.0;
577 for (a, b) in symphonia_decoded
578 .sample_array
579 .iter()
580 .zip(ffmpeg_decoded.sample_array.iter())
581 {
582 diff += (a - b).abs();
583 }
584 diff /= symphonia_decoded.sample_array.len() as f32;
585 assert!(
586 diff < 1.0e-5,
587 "Difference between symphonia and ffmpeg: {}",
588 diff
589 );
590 }
591
592 #[cfg(feature = "symphonia-flac")]
593 #[test]
594 fn test_resample_stereo() {
595 let path = Path::new("data/s16_stereo_22_5kHz.flac");
596 let expected_hash = 0x1d7b2d6d;
597 _test_decode(&path, expected_hash);
598 }
599
600 #[cfg(feature = "symphonia-flac")]
601 #[test]
602 fn test_stereo_ffmpeg_v_symphonia() {
605 let path = Path::new("data/s16_stereo_22_5kHz.flac");
606 let expected_hash = 0x1d7b2d6d;
607 _test_decode(&path, expected_hash);
608 }
609
610 #[cfg(feature = "symphonia-flac")]
611 #[test]
612 fn test_decode_mono() {
613 let path = Path::new("data/s16_mono_22_5kHz.flac");
614 let expected_hash = 0x5e01930b;
618 _test_decode(&path, expected_hash);
619 }
620
621 #[cfg(feature = "symphonia-mp3")]
622 #[test]
623 #[ignore = "fails when asked to convert stereo to mono, ig ffmpeg does it differently, but I'm not sure what the difference actually is"]
624 fn test_decode_mp3() {
625 let path = Path::new("data/s16_mono_22_5kHz.mp3");
626 let expected_hash = 0xeebac7ce;
631 _test_decode(&path, expected_hash);
632 }
633
634 #[cfg(feature = "symphonia-mp3")]
635 #[test]
636 fn test_decode_mp3_ffmpeg_v_symphonia() {
637 let path = Path::new("data/s16_mono_22_5kHz.mp3");
638 let symphonia_decoded = Decoder::decode(&path).unwrap();
639 let ffmpeg_decoded = crate::decoder::ffmpeg::FFmpegDecoder::decode(&path).unwrap();
640
641 let mut diff = 0.0;
643 for (a, b) in symphonia_decoded
644 .sample_array
645 .iter()
646 .zip(ffmpeg_decoded.sample_array.iter())
647 {
648 diff += (a - b).abs();
649 }
650 diff /= symphonia_decoded.sample_array.len() as f32;
651 assert!(
652 diff < 1.0e-6,
653 "Difference between symphonia and ffmpeg: {}",
654 diff
655 );
656 }
657
658 #[cfg(feature = "symphonia-wav")]
659 #[test]
660 fn test_dont_panic_no_channel_layout() {
661 let path = Path::new("data/no_channel.wav");
662 Decoder::decode(path).unwrap();
663 }
664
665 #[cfg(all(feature = "symphonia-flac", feature = "symphonia-ogg"))]
666 #[test]
667 fn test_decode_right_capacity_vec() {
668 let path = Path::new("data/s16_mono_22_5kHz.flac");
669 let song = Decoder::decode(path).unwrap();
670 let sample_array = song.sample_array;
671 assert_eq!(
672 sample_array.len(), sample_array.capacity()
674 );
675
676 let path = Path::new("data/s32_stereo_44_1_kHz.flac");
677 let song = Decoder::decode(path).unwrap();
678 let sample_array = song.sample_array;
679 assert_eq!(
680 sample_array.len(), sample_array.capacity()
682 );
683
684 let path = Path::new("data/capacity_fix.ogg");
685 let song = Decoder::decode(path).unwrap();
686 let sample_array = song.sample_array;
687 assert_eq!(
688 sample_array.len(), sample_array.capacity()
690 );
691 }
692
693 #[cfg(all(
694 feature = "symphonia-flac",
695 feature = "symphonia-ogg",
696 feature = "symphonia-vorbis",
697 feature = "symphonia-wav",
698 feature = "symphonia-mp3"
699 ))]
700 #[test]
701 fn compare_ffmpeg_to_symphonia_for_all_test_songs() {
702 let paths_and_tolerances = [
703 ("data/piano.flac", f32::EPSILON),
704 ("data/piano.wav", f32::EPSILON),
705 ("data/s16_mono_22_5kHz.flac", f32::EPSILON),
706 ("data/s16_stereo_22_5kHz.flac", f32::EPSILON),
707 ("data/capacity_fix.ogg", f32::EPSILON),
708 ("data/s16_mono_22_5kHz.mp3", f32::EPSILON),
709 ("data/s16_mono_44_1_kHz.flac", 1e-5),
710 ("data/s32_mono_44_1_kHz.flac", 1e-5),
711 ("data/s32_stereo_44_1_kHz.flac", 1e-5),
712 ("data/s32_stereo_44_1_kHz.mp3", 1e-5),
713 ("data/flush_test_52000.wav", 1e-4),
714 ("data/special-tags.mp3", 0.03),
717 ("data/unsupported-tags.mp3", 0.03),
718 ("data/white_noise.mp3", 0.03),
719 ("data/no_channel.wav", 0.03),
720 ("data/tone_11080Hz.flac", 0.175),
721 ("data/no_tags.flac", 0.175),
722 ];
723
724 for (path_str, tolerance) in paths_and_tolerances {
725 let path = Path::new(path_str);
726 let symphonia_decoded = Decoder::decode(&path).unwrap();
727 let ffmpeg_decoded = crate::decoder::ffmpeg::FFmpegDecoder::decode(&path).unwrap();
728
729 assert_eq!(
730 symphonia_decoded.sample_array.len(),
731 ffmpeg_decoded.sample_array.len(),
732 "Different sample numbers between ffmpeg and symphonia for song: {}",
733 path.display(),
734 );
735 let mut diff = 0.0;
737 for (a, b) in symphonia_decoded
738 .sample_array
739 .iter()
740 .zip(ffmpeg_decoded.sample_array.iter())
741 {
742 diff += (a - b).abs();
743 }
744 diff /= symphonia_decoded.sample_array.len() as f32;
745 assert!(
746 diff < tolerance,
747 "Difference between symphonia and ffmpeg: {diff}, tolerance: {tolerance}, file: {path_str}",
748 );
749 }
750 }
751}