diff --git a/CHANGELOG.md b/CHANGELOG.md index ea4de731..f2db5982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `GeneratorFunction`. - Minimal builds without `cpal` audio output are now supported. See `README.md` for instructions. (#349) +- Added `Sample::is_zero()` method for checking zero samples. ### Changed - Breaking: `OutputStreamBuilder` should now be used to initialize an audio output stream. @@ -26,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: `Sink::try_new` renamed to `connect_new` and does not return error anymore. `Sink::new_idle` was renamed to `new`. - Breaking: In the `Source` trait, the method `current_frame_len()` was renamed to `current_span_len()`. +- Breaking: `Decoder` now outputs `f32` samples by default instead of `i16`. + Enable the `integer-decoder` to revert to `i16` samples. - The term 'frame' was renamed to 'span' in the crate and documentation. ### Fixed @@ -34,6 +37,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Symphonia decoder `total_duration` incorrect value caused by conversion from `Time` to `Duration`. - An issue with `SignalGenerator` that caused it to create increasingly distorted waveforms over long run times has been corrected. (#201) +- WAV and FLAC decoder duration calculation now calculated once and handles very large files + correctly +- Removed unwrap() calls in MP3, WAV, FLAC and Vorbis format detection for better error handling + +### Deprecated +- Deprecated `Sample::zero_value()` function in favor of `Sample::ZERO_VALUE` constant # Version 0.20.1 (2024-11-08) diff --git a/Cargo.toml b/Cargo.toml index 296ea901..4e756567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,17 +30,28 @@ default = ["playback", "flac", "vorbis", "wav", "mp3"] tracing = ["dep:tracing"] experimental = ["dep:atomic_float"] playback = ["dep:cpal"] +integer-decoder = [] flac = ["claxon"] vorbis = ["lewton"] wav = ["hound"] mp3 = ["symphonia-mp3"] minimp3 = ["dep:minimp3_fixed"] + noise = ["rand"] + wasm-bindgen = ["cpal/wasm-bindgen"] cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"] + symphonia-aac = ["symphonia/aac"] -symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"] +symphonia-all = [ + "symphonia-aac", + "symphonia-flac", + "symphonia-isomp4", + "symphonia-mp3", + "symphonia-vorbis", + "symphonia-wav", +] symphonia-flac = ["symphonia/flac"] symphonia-isomp4 = ["symphonia/isomp4"] symphonia-mp3 = ["symphonia/mp3"] diff --git a/benches/conversions.rs b/benches/conversions.rs index 9bebb974..40001ccb 100644 --- a/benches/conversions.rs +++ b/benches/conversions.rs @@ -1,18 +1,17 @@ use dasp_sample::FromSample; use divan::Bencher; -use rodio::Source; +use rodio::{decoder::DecoderSample, Source}; mod shared; -use shared::TestSource; fn main() { divan::main(); } #[divan::bench(types = [i16, u16, f32])] -fn from_i16_to>(bencher: Bencher) { +fn from_sample_to>(bencher: Bencher) { bencher - .with_inputs(|| TestSource::music_wav()) + .with_inputs(|| shared::music_wav()) .bench_values(|source| { source .convert_samples::() diff --git a/benches/effects.rs b/benches/effects.rs index 2849fc82..9e6e1a14 100644 --- a/benches/effects.rs +++ b/benches/effects.rs @@ -4,7 +4,7 @@ use divan::Bencher; use rodio::Source; mod shared; -use shared::TestSource; +use shared::music_wav; fn main() { divan::main(); @@ -12,76 +12,68 @@ fn main() { #[divan::bench] fn reverb(bencher: Bencher) { - bencher - .with_inputs(|| TestSource::music_wav()) - .bench_values(|source| { - source - .buffered() - .reverb(Duration::from_secs_f32(0.05), 0.3) - .for_each(divan::black_box_drop) - }) + bencher.with_inputs(|| music_wav()).bench_values(|source| { + source + .buffered() + .reverb(Duration::from_secs_f32(0.05), 0.3) + .for_each(divan::black_box_drop) + }) } #[divan::bench] fn high_pass(bencher: Bencher) { bencher - .with_inputs(|| TestSource::music_wav().to_f32s()) + .with_inputs(|| music_wav().to_f32s()) .bench_values(|source| source.high_pass(200).for_each(divan::black_box_drop)) } #[divan::bench] fn fade_out(bencher: Bencher) { - bencher - .with_inputs(|| TestSource::music_wav()) - .bench_values(|source| { - source - .fade_out(Duration::from_secs(5)) - .for_each(divan::black_box_drop) - }) + bencher.with_inputs(|| music_wav()).bench_values(|source| { + source + .fade_out(Duration::from_secs(5)) + .for_each(divan::black_box_drop) + }) } #[divan::bench] fn amplify(bencher: Bencher) { bencher - .with_inputs(|| TestSource::music_wav().to_f32s()) + .with_inputs(|| music_wav()) .bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop)) } #[divan::bench] fn agc_enabled(bencher: Bencher) { - bencher - .with_inputs(|| TestSource::music_wav().to_f32s()) - .bench_values(|source| { - source - .automatic_gain_control( - 1.0, // target_level - 4.0, // attack_time (in seconds) - 0.005, // release_time (in seconds) - 5.0, // absolute_max_gain - ) - .for_each(divan::black_box_drop) - }) + bencher.with_inputs(|| music_wav()).bench_values(|source| { + source + .automatic_gain_control( + 1.0, // target_level + 4.0, // attack_time (in seconds) + 0.005, // release_time (in seconds) + 5.0, // absolute_max_gain + ) + .for_each(divan::black_box_drop) + }) } #[cfg(feature = "experimental")] #[divan::bench] fn agc_disabled(bencher: Bencher) { - bencher - .with_inputs(|| TestSource::music_wav().to_f32s()) - .bench_values(|source| { - // Create the AGC source - let amplified_source = source.automatic_gain_control( - 1.0, // target_level - 4.0, // attack_time (in seconds) - 0.005, // release_time (in seconds) - 5.0, // absolute_max_gain - ); + bencher.with_inputs(|| music_wav()).bench_values(|source| { + // Create the AGC source + let amplified_source = source.automatic_gain_control( + 1.0, // target_level + 4.0, // attack_time (in seconds) + 0.005, // release_time (in seconds) + 5.0, // absolute_max_gain + ); - // Get the control handle and disable AGC - let agc_control = amplified_source.get_agc_control(); - agc_control.store(false, std::sync::atomic::Ordering::Relaxed); + // Get the control handle and disable AGC + let agc_control = amplified_source.get_agc_control(); + agc_control.store(false, std::sync::atomic::Ordering::Relaxed); - // Process the audio stream with AGC disabled - amplified_source.for_each(divan::black_box_drop) - }) + // Process the audio stream with AGC disabled + amplified_source.for_each(divan::black_box_drop) + }) } diff --git a/benches/resampler.rs b/benches/resampler.rs index 22ddbf15..ace155d8 100644 --- a/benches/resampler.rs +++ b/benches/resampler.rs @@ -1,9 +1,11 @@ use divan::Bencher; +use rodio::decoder::DecoderSample; use rodio::source::UniformSourceIterator; mod shared; +use shared::music_wav; + use rodio::Source; -use shared::TestSource; fn main() { divan::main(); @@ -13,11 +15,11 @@ fn main() { fn no_resampling(bencher: Bencher) { bencher .with_inputs(|| { - let source = TestSource::::music_wav(); + let source = music_wav(); (source.channels(), source.sample_rate(), source) }) .bench_values(|(channels, sample_rate, source)| { - UniformSourceIterator::<_, i16>::new(source, channels, sample_rate) + UniformSourceIterator::<_, DecoderSample>::new(source, channels, sample_rate) .for_each(divan::black_box_drop) }) } @@ -32,11 +34,11 @@ const COMMON_SAMPLE_RATES: [u32; 12] = [ fn resample_to(bencher: Bencher, target_sample_rate: u32) { bencher .with_inputs(|| { - let source = TestSource::::music_wav(); + let source = music_wav(); (source.channels(), source) }) .bench_values(|(channels, source)| { - UniformSourceIterator::<_, i16>::new(source, channels, target_sample_rate) + UniformSourceIterator::<_, DecoderSample>::new(source, channels, target_sample_rate) .for_each(divan::black_box_drop) }) } diff --git a/benches/shared.rs b/benches/shared.rs index 8cab969d..bba74cec 100644 --- a/benches/shared.rs +++ b/benches/shared.rs @@ -2,7 +2,7 @@ use std::io::Cursor; use std::time::Duration; use std::vec; -use rodio::{ChannelCount, SampleRate, Source}; +use rodio::{decoder::DecoderSample, ChannelCount, Sample, SampleRate, Source}; pub struct TestSource { samples: vec::IntoIter, @@ -14,53 +14,42 @@ pub struct TestSource { impl Iterator for TestSource { type Item = T; + #[inline] fn next(&mut self) -> Option { self.samples.next() } } impl ExactSizeIterator for TestSource { + #[inline] fn len(&self) -> usize { self.samples.len() } } -impl Source for TestSource { +impl Source for TestSource { + #[inline] fn current_span_len(&self) -> Option { None // forever } + #[inline] fn channels(&self) -> ChannelCount { self.channels } + #[inline] fn sample_rate(&self) -> SampleRate { self.sample_rate } + #[inline] fn total_duration(&self) -> Option { Some(self.total_duration) } } impl TestSource { - pub fn music_wav() -> Self { - let data = include_bytes!("../assets/music.wav"); - let cursor = Cursor::new(data); - - let duration = Duration::from_secs(10); - let sound = rodio::Decoder::new(cursor) - .expect("music.wav is correctly encoded & wav is supported") - .take_duration(duration); - - TestSource { - channels: sound.channels(), - sample_rate: sound.sample_rate(), - total_duration: duration, - samples: sound.into_iter().collect::>().into_iter(), - } - } - #[allow(unused, reason = "not everything from shared is used in all libs")] pub fn to_f32s(self) -> TestSource { let TestSource { @@ -69,10 +58,7 @@ impl TestSource { sample_rate, total_duration, } = self; - let samples = samples - .map(|s| dasp_sample::Sample::from_sample(s)) - .collect::>() - .into_iter(); + let samples = samples.map(|s| s.to_f32()).collect::>().into_iter(); TestSource { samples, channels, @@ -81,3 +67,27 @@ impl TestSource { } } } + +impl TestSource { + #[allow(unused, reason = "not everything from shared is used in all libs")] + pub fn to_f32s(self) -> TestSource { + self + } +} + +pub fn music_wav() -> TestSource { + let data = include_bytes!("../assets/music.wav"); + let cursor = Cursor::new(data); + + let duration = Duration::from_secs(10); + let sound = rodio::Decoder::new(cursor) + .expect("music.wav is correctly encoded & wav is supported") + .take_duration(duration); + + TestSource { + channels: sound.channels(), + sample_rate: sound.sample_rate(), + total_duration: duration, + samples: sound.into_iter().collect::>().into_iter(), + } +} diff --git a/src/conversions/mod.rs b/src/conversions/mod.rs index e6ad817e..a842ccd0 100644 --- a/src/conversions/mod.rs +++ b/src/conversions/mod.rs @@ -11,7 +11,5 @@ pub use self::sample::Sample; pub use self::sample_rate::SampleRateConverter; mod channels; -// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This -// `pub` mandatory -pub mod sample; +mod sample; mod sample_rate; diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index f1534285..8456e42c 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -1,4 +1,4 @@ -use dasp_sample::{FromSample, Sample as DaspSample}; +use dasp_sample::{FromSample, Sample as DaspSample, ToSample}; use std::marker::PhantomData; /// Converts the samples data type to `O`. @@ -71,7 +71,10 @@ where /// /// You can implement this trait on your own type as well if you wish so. /// -pub trait Sample: DaspSample { +pub trait Sample: DaspSample + ToSample { + /// The value corresponding to the absence of sound. + const ZERO_VALUE: Self = DaspSample::EQUILIBRIUM; + /// Linear interpolation between two samples. /// /// The result should be equivalent to @@ -81,47 +84,45 @@ pub trait Sample: DaspSample { fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self; /// Multiplies the value of this sample by the given amount. - fn amplify(self, value: f32) -> Self; - - /// Converts the sample to a f32 value. - fn to_f32(self) -> f32; - - /// Calls `saturating_add` on the sample. - fn saturating_add(self, other: Self) -> Self; - - /// Returns the value corresponding to the absence of sound. - fn zero_value() -> Self; -} + #[inline] + fn amplify(self, value: f32) -> Self { + self.mul_amp(value.to_sample()) + } -impl Sample for u16 { + /// Converts the sample to a normalized `f32` value. #[inline] - fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let a = first as i32; - let b = second as i32; - let n = numerator as i32; - let d = denominator as i32; - (a + (b - a) * n / d) as u16 + fn to_f32(self) -> f32 { + self.to_sample() } + /// Adds the amplitude of another sample to this one. #[inline] - fn amplify(self, value: f32) -> u16 { - ((self as f32) * value) as u16 + fn saturating_add(self, other: Self) -> Self { + self.add_amp(other.to_signed_sample()) } + /// Returns true if the sample is the zero value. #[inline] - fn to_f32(self) -> f32 { - // Convert u16 to f32 in the range [-1.0, 1.0] - (self as f32 - 32768.0) / 32768.0 + fn is_zero(self) -> bool { + self == Self::ZERO_VALUE } + /// Returns the value corresponding to the absence of sound. + #[deprecated(note = "Please use `Self::ZERO_VALUE` instead")] #[inline] - fn saturating_add(self, other: u16) -> u16 { - self.saturating_add(other) + fn zero_value() -> Self { + Self::ZERO_VALUE } +} +impl Sample for u16 { #[inline] - fn zero_value() -> u16 { - 32768 + fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { + let a = first as i32; + let b = second as i32; + let n = numerator as i32; + let d = denominator as i32; + (a + (b - a) * n / d) as u16 } } @@ -131,27 +132,6 @@ impl Sample for i16 { (first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32) as i16 } - - #[inline] - fn amplify(self, value: f32) -> i16 { - ((self as f32) * value) as i16 - } - - #[inline] - fn to_f32(self) -> f32 { - // Convert i16 to f32 in the range [-1.0, 1.0] - self as f32 / 32768.0 - } - - #[inline] - fn saturating_add(self, other: i16) -> i16 { - self.saturating_add(other) - } - - #[inline] - fn zero_value() -> i16 { - 0 - } } impl Sample for f32 { @@ -160,25 +140,15 @@ impl Sample for f32 { first + (second - first) * numerator as f32 / denominator as f32 } - #[inline] - fn amplify(self, value: f32) -> f32 { - self * value - } - #[inline] fn to_f32(self) -> f32 { - // f32 is already in the correct format self } #[inline] - fn saturating_add(self, other: f32) -> f32 { - self + other - } - - #[inline] - fn zero_value() -> f32 { - 0.0 + fn is_zero(self) -> bool { + 2.0 * (self - Self::ZERO_VALUE).abs() + <= f32::EPSILON * (self.abs() + Self::ZERO_VALUE.abs()) } } diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 9773f9d8..d00d01a0 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::io::{Read, Seek, SeekFrom}; use std::mem; use std::time::Duration; @@ -7,9 +6,13 @@ use crate::source::SeekError; use crate::Source; use crate::common::{ChannelCount, SampleRate}; + use claxon::FlacReader; +use dasp_sample::{Sample, I24}; + +use super::DecoderSample; -/// Decoder for the Flac format. +/// Decoder for the FLAC format. pub struct FlacDecoder where R: Read + Seek, @@ -21,21 +24,34 @@ where bits_per_sample: u32, sample_rate: SampleRate, channels: ChannelCount, - samples: Option, + total_duration: Option, } impl FlacDecoder where R: Read + Seek, { - /// Attempts to decode the data as Flac. + /// Attempts to decode the data as FLAC. pub fn new(mut data: R) -> Result, R> { if !is_flac(data.by_ref()) { return Err(data); } - let reader = FlacReader::new(data).unwrap(); + let reader = FlacReader::new(data).expect("should still be flac"); + let spec = reader.streaminfo(); + let sample_rate = spec.sample_rate; + + // `samples` in FLAC means "inter-channel samples" aka frames + // so we do not divide by `self.channels` here. + let total_duration = spec.samples.map(|s| { + // Calculate duration as (samples * 1_000_000) / sample_rate + // but do the division first to avoid overflow + let sample_rate = sample_rate as u64; + let secs = s / sample_rate; + let nanos = ((s % sample_rate) * 1_000_000_000) / sample_rate; + Duration::new(secs, nanos as u32) + }); Ok(FlacDecoder { reader, @@ -45,11 +61,13 @@ where current_block_channel_len: 1, current_block_off: 0, bits_per_sample: spec.bits_per_sample, - sample_rate: spec.sample_rate, + sample_rate, channels: spec.channels as ChannelCount, - samples: spec.samples, + total_duration, }) } + + #[inline] pub fn into_inner(self) -> R { self.reader.into_inner() } @@ -76,10 +94,7 @@ where #[inline] fn total_duration(&self) -> Option { - // `samples` in FLAC means "inter-channel samples" aka frames - // so we do not divide by `self.channels` here. - self.samples - .map(|s| Duration::from_micros(s * 1_000_000 / self.sample_rate as u64)) + self.total_duration } #[inline] @@ -94,10 +109,10 @@ impl Iterator for FlacDecoder where R: Read + Seek, { - type Item = i16; + type Item = DecoderSample; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { loop { if self.current_block_off < self.current_block.len() { // Read from current block. @@ -106,10 +121,24 @@ where + self.current_block_off / self.channels as usize; let raw_val = self.current_block[real_offset]; self.current_block_off += 1; - let real_val = match self.bits_per_sample.cmp(&16) { - Ordering::Less => (raw_val << (16 - self.bits_per_sample)) as i16, - Ordering::Equal => raw_val as i16, - Ordering::Greater => (raw_val >> (self.bits_per_sample - 16)) as i16, + let bits = self.bits_per_sample; + let real_val = match bits { + 8 => (raw_val as i8).to_sample(), + 16 => { + let raw_val = raw_val as i16; + #[cfg(not(feature = "integer-decoder"))] // perf + let raw_val = raw_val.to_sample(); + raw_val + } + 24 => I24::new(raw_val).unwrap_or(Sample::EQUILIBRIUM).to_sample(), + 32 => raw_val.to_sample(), + _ => { + // FLAC also supports 12 and 20 bits per sample. We use bit + // shifts to convert them to 32 bits, because: + // - I12 does not exist as a type + // - I20 exists but does not have `ToSample` implemented + (raw_val << (32 - bits)).to_sample() + } }; return Some(real_val); } @@ -128,18 +157,13 @@ where } } -/// Returns true if the stream contains Flac data, then resets it to where it was. +/// Returns true if the stream contains FLAC data, then tries to rewind it to where it was. fn is_flac(mut data: R) -> bool where R: Read + Seek, { - let stream_pos = data.stream_position().unwrap(); - - if FlacReader::new(data.by_ref()).is_err() { - data.seek(SeekFrom::Start(stream_pos)).unwrap(); - return false; - } - - data.seek(SeekFrom::Start(stream_pos)).unwrap(); - true + let stream_pos = data.stream_position().unwrap_or_default(); + let result = FlacReader::new(data.by_ref()).is_ok(); + let _ = data.seek(SeekFrom::Start(stream_pos)); + result } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ad1e61d7..1fb45fa4 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -31,6 +31,13 @@ mod vorbis; #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] mod wav; +#[cfg(feature = "integer-decoder")] +/// Output format of the decoders. +pub type DecoderSample = i16; +#[cfg(not(feature = "integer-decoder"))] +/// Output format of the decoders. +pub type DecoderSample = f32; + /// Source of audio samples from decoding a file. /// /// Supports MP3, WAV, Vorbis and Flac. @@ -68,7 +75,7 @@ where impl DecoderImpl { #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { match self { #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] DecoderImpl::Wav(source) => source.next(), @@ -396,10 +403,10 @@ impl Iterator for Decoder where R: Read + Seek, { - type Item = i16; + type Item = DecoderSample; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { self.0.next() } @@ -442,10 +449,10 @@ impl Iterator for LoopedDecoder where R: Read + Seek, { - type Item = i16; + type Item = DecoderSample; #[inline] - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { if let Some(sample) = self.0.next() { Some(sample) } else { diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index f85605c7..521c7ffc 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -1,6 +1,8 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; +use super::DecoderSample; +use crate::common::{ChannelCount, SampleRate}; use crate::source::SeekError; use crate::Source; @@ -8,6 +10,8 @@ use minimp3::Decoder; use minimp3::Frame; use minimp3_fixed as minimp3; +use dasp_sample::Sample; + pub struct Mp3Decoder where R: Read + Seek, @@ -15,7 +19,7 @@ where // decoder: SeekDecoder, decoder: Decoder, // what minimp3 calls frames rodio calls spans - current_span: minimp3::Frame, + current_span: Frame, current_span_offset: usize, } @@ -33,10 +37,7 @@ where // thus if we crash here one of these invariants is broken: // .expect("should be able to allocate memory, perform IO"); // let current_span = decoder.decode_frame() - let current_span = decoder.next_frame() - // the reader makes enough data available therefore - // if we crash here the invariant broken is: - .expect("data should not corrupt"); + let current_span = decoder.next_frame().expect("should still be mp3"); Ok(Mp3Decoder { decoder, @@ -44,6 +45,8 @@ where current_span_offset: 0, }) } + + #[inline] pub fn into_inner(self) -> R { self.decoder.into_inner() } @@ -73,7 +76,7 @@ where None } - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + fn try_seek(&mut self, _pos: Duration) -> Result<(), SeekError> { // TODO waiting for PR in minimp3_fixed or minimp3 // let pos = (pos.as_secs_f32() * self.sample_rate() as f32) as u64; @@ -92,10 +95,11 @@ impl Iterator for Mp3Decoder where R: Read + Seek, { - type Item = i16; + type Item = DecoderSample; - fn next(&mut self) -> Option { - if self.current_span_offset == self.current_span_len().unwrap() { + fn next(&mut self) -> Option { + let current_span_len = self.current_span_len()?; + if self.current_span_offset == current_span_len { if let Ok(span) = self.decoder.next_frame() { // if let Ok(span) = self.decoder.decode_frame() { self.current_span = span; @@ -108,6 +112,8 @@ where let v = self.current_span.data[self.current_span_offset]; self.current_span_offset += 1; + #[cfg(not(feature = "integer-decoder"))] // perf + let v = v.to_sample(); Some(v) } } @@ -117,10 +123,9 @@ fn is_mp3(mut data: R) -> bool where R: Read + Seek, { - let stream_pos = data.seek(SeekFrom::Current(0)).unwrap(); + let stream_pos = data.stream_position().unwrap_or_default(); let mut decoder = Decoder::new(data.by_ref()); - let ok = decoder.next_frame().is_ok(); - data.seek(SeekFrom::Start(stream_pos)).unwrap(); - - ok + let result = decoder.next_frame().is_ok(); + let _ = data.seek(SeekFrom::Start(stream_pos)); + result } diff --git a/src/decoder/read_seek_source.rs b/src/decoder/read_seek_source.rs index 52f20e6d..d9717023 100644 --- a/src/decoder/read_seek_source.rs +++ b/src/decoder/read_seek_source.rs @@ -9,28 +9,33 @@ pub struct ReadSeekSource { impl ReadSeekSource { /// Instantiates a new `ReadSeekSource` by taking ownership and wrapping the provided /// `Read + Seek`er. + #[inline] pub fn new(inner: T) -> Self { ReadSeekSource { inner } } } impl MediaSource for ReadSeekSource { + #[inline] fn is_seekable(&self) -> bool { true } + #[inline] fn byte_len(&self) -> Option { None } } impl Read for ReadSeekSource { + #[inline] fn read(&mut self, buf: &mut [u8]) -> Result { self.inner.read(buf) } } impl Seek for ReadSeekSource { + #[inline] fn seek(&mut self, pos: SeekFrom) -> Result { self.inner.seek(pos) } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index c466e6ee..eb2198ea 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -14,7 +14,7 @@ use symphonia::{ default::get_probe, }; -use super::DecoderError; +use super::{DecoderError, DecoderSample}; use crate::common::{ChannelCount, SampleRate}; use crate::{source, Source}; @@ -28,7 +28,7 @@ pub(crate) struct SymphoniaDecoder { current_span_offset: usize, format: Box, total_duration: Option