Skip to main content

bliss_audio/song/decoder/
symphonia.rs

1//! Decoder implementation that uses the `symphonia` crate to decode audio files, and the `rubato` crate to resample the audio files.
2//!
3//! Upstreamed from the `mecomp-analysis` crate.
4
5use 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)]
29/// Error raised when trying to decode a song with the `SymphoniaDecoder`.
30pub enum SymphoniaDecoderError {
31    #[error("Failed to resample audio: {0}")]
32    /// Error raised when trying to resample audio.
33    /// (from rubato)
34    ResampleError(String),
35    #[error("Failed to create resampler: {0}")]
36    /// Error raised when trying to create a resampler.
37    /// (from rubato)
38    ResamplerConstructionError(String),
39    #[error("IO Error: {0}")]
40    /// General IO error.
41    IoError(String),
42    #[error("Failed to decode audio: {0}")]
43    /// Error raised when trying to decode audio.
44    /// (from symphonia)
45    DecodeError(String),
46    #[error("Unsupported codec")]
47    /// Error raised when trying to decode a file with an unsupported codec.
48    UnsupportedCodec,
49    #[error("No supported audio tracks")]
50    /// Error raised when trying to decode a file with no supported audio tracks.
51    NoSupportedAudioTracks,
52    #[error("No streams")]
53    /// Error raised when trying to decode a file with no streams.
54    NoStreams,
55    #[error("The audio source's duration is either unknown or infinite")]
56    /// Error raised when the audio source's duration is either unknown or infinite.
57    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
89/// Struct used by the symphonia-based bliss decoders to decode audio files
90struct 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    /// A "substantial portion" of this implementation comes from the `rodio` crate,
115    /// <https://github.com/RustAudio/rodio/blob/1c2cd2f6d99c005533b7a2b4c19ef41728f62116/src/decoder/symphonia.rs>
116    /// and is licensed under the MIT License.
117    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        // Select the first supported track
128        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 the packet does not belong to the selected track, skip over it
167            if current_span.track_id != track_id {
168                continue;
169            }
170
171            match decoder.decode(&current_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
201/// This implementation comes from the `rodio` crate,
202/// <https://github.com/RustAudio/rodio/blob/1c2cd2f6d99c005533b7a2b4c19ef41728f62116/src/decoder/symphonia.rs>
203/// and is licensed under the MIT License.
204impl 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                    // Loop until we get a packet with audio frames. This is necessary because some
225                    // formats can have packets with only metadata, particularly when rewinding, in
226                    // which case the iterator would otherwise end with `None`.
227                    // Note: checking `decoded.frames()` is more reliable than `packet.dur()`, which
228                    // can resturn non-zero durations for packets without audio frames.
229                    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
252/// Sequential, single-threaded decoder based on Symphonia
253pub struct SymphoniaDecoder;
254
255impl SymphoniaDecoder {
256    /// we need to collapse the audio source into one channel
257    /// channels are interleaved, so if we have 2 channels, `[1, 2, 3, 4]` and `[5, 6, 7, 8]`,
258    /// they will be stored as `[1, 5, 2, 6, 3, 7, 4, 8]`
259    ///
260    /// For stereo sound, we can make this mono by averaging the channels and multiplying by the square root of 2,
261    /// This recovers the exact behavior of ffmpeg when converting stereo to mono, however for 2.1 and 5.1 surround sound,
262    /// ffmpeg might be doing something different, and I'm not sure what that is (don't have a 5.1 surround sound file to test with)
263    ///
264    /// TODO: Figure out how ffmpeg does it for 2.1 and 5.1 surround sound, and do it the same way
265    #[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            // no channels
274            0 => Err(SymphoniaDecoderError::NoStreams),
275            // mono
276            1 => Ok(source.collect()),
277            // stereo
278            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            // 2.1 or 5.1 surround
290            _ => {
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    /// Resample the given mono samples to 22050 Hz
305    #[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        // Since this is mono
331        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        // chunks of frames, each being CHUNKSIZE long.
336        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        // process the remainder
354        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        // Flush the remaining samples
386        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    /// A function that should decode and resample a song, optionally
408    /// extracting the song's metadata such as the artist, the album, etc.
409    ///
410    /// The output sample array should be resampled to f32le, one channel, with a sampling rate
411    /// of 22050 Hz. Anything other than that will yield wrong results.
412    #[allow(clippy::missing_inline_in_public_items)]
413    fn decode(path: &std::path::Path) -> BlissResult<PreAnalyzedSong> {
414        // open the file
415        let file = File::open(path).map_err(SymphoniaDecoderError::from)?;
416        // create the media source stream
417        let mss = MediaSourceStream::new(Box::new(file), MediaSourceStreamOptions::default());
418
419        let source = SymphoniaSource::new(mss)?;
420
421        // Convert the audio source into a mono channel
422        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        // then we need to resample the audio source into 22050 Hz
430        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    // expected hashs Obtained through
458    // ffmpeg -i data/s16_stereo_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le -f hash -hash adler32 -
459    #[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        /*
489        configurations tested:
490
491        | Resampler, and configuration | difference from ffmpeg |
492        - SincFixedIn, on whole buffer, with process_into_buffer:       0.0020331843
493        - SincFixedIn, on whole buffer, with process:                   0.0020285384
494        - FastFixedIn, on whole buffer, with process_into_buffer:       0.0039299703
495        - FastFixedIn, on whole buffer, with process:                   0.0039298288
496        - FftFixedIn, on whole buffer, with process_into_buffer:        0.017518902
497        - FftFixedIn, on whole buffer, with process:                    0.0154156
498
499        - SincFixedIn, on chunks of 1024, Cubic interp, Blackman        0.024933979
500        - SincFixedIn, on chunks of 1024, Linear interp, Blackman       0.024933979
501        - SincFixedIn, on chunks of 1024, Cubic interp, Blackman2       0.024934249
502        - SincFixedIn, on chunks of 1024, Cubic interp, Hann            0.024934188
503        - SincFixedIn, on chunks of 1024, Cubic interp, BlackmanHarris  0.024934053
504        - FastFixedIn, on chunks of 1024, Cubic interp                  0.0039299796
505        - FastFixedIn, on chunks of 8192, Cubic interp                  0.0039299796
506        - FastFixedIn, on chunks of 8192, Linear interp                 0.0039299796
507        - FftFixedIn, on chunks of 128, 1 subchunk                      0.000033739863
508        - FftFixedIn, on chunks of 256, 1 subchunk                      0.000015570473
509        - FftFixedIn, on chunks of 512, 1 subchunk                      0.0000071326162
510        - FftFixedIn, on chunks of 1024, 32 subchunks                   0.0018597797
511        - FftFixedIn, on chunks of 1024, 16 subchunks                   0.000092027316
512        - FftFixedIn, on chunks of 1024, 1 subchunk                     0.0000068506047 // <--
513        - FftFixedIn, on chunks of 2048, 1 subchunk                     0.0000070857413
514        - FftFixedIn, on chunks of 4096, 1 subchunk                     0.0000071542086
515        - FftFixedIn, on chunks of 4096, 4 subchunk                     0.0000068506047 // that makes sense actually, 4096/4 = 1024 so it makes sense this matches the output of CHUNK_SIZE=1024
516        - FftFixedIn, on chunks of 8192, 1 subchunk                     0.000007135614
517        - FftFixedIn, on chunks of 16384, 1 subchunk                    0.0000071084633
518        - FftFixedIn, on chunks of 32768, 1 subchunk                    0.0000071034465
519        - FftFixedIn, on chunks of 65736, 1 subchunk                    0.000007098081
520        - FftFixedIn, on chunks of 1024*128, 1 subchunk                 0.000007097704
521        - FftFixedIn, on chunks of 1024*256, 1 subchunk                 0.000007096261
522
523        so FftFixedIn on chunks is definitely the best, if we make the chunks too small it diverges,
524        and if we make them large we get diminishing returns, so we should probably stick to 1024
525
526        Now, what can we do to eliminate the remaining error?
527
528
529         */
530        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        // if the first 100 samples are equal, then the rest should be equal.
534        // we check this first since the sample arrays are large enough that printing the diff would attempt
535        // and fail to allocate memory for the string
536        // assert_eq!(
537        //     symphonia_decoded.sample_array[..100],
538        //     ffmpeg_decoded.sample_array[..100]
539        // );
540        // assert_eq!(symphonia_decoded.sample_array, ffmpeg_decoded.sample_array);
541
542        // calculate the similarity between the two arrays
543        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        // calculate the similarity between the two arrays
576        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    // From this test, I was able to determine that multiplying the average of the channels by the square root of 2
603    // recovers the exact behavior of ffmpeg when converting stereo to mono
604    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        // Obtained through
615        // ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
616        // -f hash -hash adler32 -
617        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        // Obtained through
627        // ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
628        // -f hash -hash adler32 -
629        //1030601839
630        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        // calculate the similarity between the two arrays
642        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_RATE as usize, // The + SAMPLE_RATE is because bliss-rs would add an extra second as a buffer, we don't need to because we know the exact length of the song
673            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_RATE as usize,
681            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_RATE as usize,
689            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            // TODO those files are "special" files with e.g. sin waves tones,
715            // which are very sensitive to resampling.
716            ("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            // calculate the similarity between the two arrays
736            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}