diff --git a/Cargo.lock b/Cargo.lock index bf4f0a113..a1eaf16ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,12 +108,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "asio-sys" version = "0.3.0" @@ -606,15 +600,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-primitive-derive" version = "0.3.0" @@ -2015,9 +2000,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "symphonia" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +checksum = "a7edef6a96b696d4e0cab5ee9ebb7ca155ed95f30a6b45bbb8b97d2727f02424" dependencies = [ "lazy_static", "symphonia-bundle-flac", @@ -2038,9 +2023,9 @@ dependencies = [ [[package]] name = "symphonia-adapter-fdk-aac" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82194865c2b438a75a2b40a12495d5d1a6c3cb2bb36671f9921ab44756ee77d2" +checksum = "b9cc5d16c27e9cd23e028655c3740ec78e31eb6cb513f1837d36afed82b2dd44" dependencies = [ "fdk-aac", "log", @@ -2049,9 +2034,9 @@ dependencies = [ [[package]] name = "symphonia-adapter-libopus" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc8e95f95c23ed1b5328eb66920ad28d9968c797f9c7aa755d4b45a5f47a41" +checksum = "c6febe6f88f9a9483db7e5b72a2dad916d8f8eb588d18905a39c861319fc7fa1" dependencies = [ "log", "opusic-sys", @@ -2060,44 +2045,44 @@ dependencies = [ [[package]] name = "symphonia-bundle-flac" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +checksum = "6405d5c34ff6f8f7ca08a4101efe66d21e180f7322ef9359900a0e55698a7892" dependencies = [ "log", + "symphonia-common", "symphonia-core", "symphonia-metadata", - "symphonia-utils-xiph", ] [[package]] name = "symphonia-bundle-mp3" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +checksum = "98ea5ffc8716bff677dfb3b01b420c7b758de901a72b8c330bf2040ab74b4add" dependencies = [ "lazy_static", "log", "symphonia-core", - "symphonia-metadata", ] [[package]] name = "symphonia-codec-aac" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +checksum = "f5bf8e39552d34a3c4c98333370e62f48c92456d2e814f273b5c3ad7c4a5f45c" dependencies = [ "lazy_static", "log", + "symphonia-common", "symphonia-core", ] [[package]] name = "symphonia-codec-adpcm" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" +checksum = "445932ecb0c59362fde9c082dd63a75380650c5077fdb8b80b8cac850442a74c" dependencies = [ "log", "symphonia-core", @@ -2105,19 +2090,20 @@ dependencies = [ [[package]] name = "symphonia-codec-alac" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" +checksum = "920a78f96f3cf62932d0c497959c0cc2100ef36b03c7ca1417b260f432a482d8" dependencies = [ "log", + "symphonia-common", "symphonia-core", ] [[package]] name = "symphonia-codec-pcm" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" +checksum = "e04ba75686acbe43542fdd374571195f0530c0b7785ca25cc6840e9c6c4b6eea" dependencies = [ "log", "symphonia-core", @@ -2125,83 +2111,93 @@ dependencies = [ [[package]] name = "symphonia-codec-vorbis" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" +checksum = "73d90b4fcf796137cc683c538282804ff9629f8ad9dbfd881fcbba331ac4e986" dependencies = [ "log", + "symphonia-common", "symphonia-core", - "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-common" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acc3fcc18ec9b8cdd48614e259c4cf0d27b71d41e5d9b120b42c5adab12d7c4" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", ] [[package]] name = "symphonia-core" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +checksum = "01c412864d599d4750d0c3d684d7e093ec05e5309681ef5252cc1096a437f6e0" dependencies = [ - "arrayvec", - "bitflags 1.3.2", + "bitflags 2.13.0", "bytemuck", "lazy_static", "log", + "num-complex", "rustfft", + "smallvec", ] [[package]] name = "symphonia-format-caf" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096" +checksum = "ab64327ee1920531c5bf86cf7e9cd1a599d57013ddc30691da62683cefc65191" dependencies = [ "log", + "symphonia-common", "symphonia-core", - "symphonia-metadata", ] [[package]] name = "symphonia-format-isomp4" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" +checksum = "0e681a70e1870d34e02abf1dbc51e4267c3f1827801474e8870be8c689fc4dc3" dependencies = [ - "encoding_rs", "log", + "symphonia-common", "symphonia-core", "symphonia-metadata", - "symphonia-utils-xiph", ] [[package]] name = "symphonia-format-mkv" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" +checksum = "d015c5c0558864665894b3f4cbd95e10abb01b9c868e751c72670f326a56360e" dependencies = [ "lazy_static", "log", + "symphonia-common", "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", ] [[package]] name = "symphonia-format-ogg" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" +checksum = "0b5495e7f7e3c7035328d82b6d6e377eef289bb0c4105bdeb557fc93a833f994" dependencies = [ "log", + "symphonia-common", "symphonia-core", "symphonia-metadata", - "symphonia-utils-xiph", ] [[package]] name = "symphonia-format-riff" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" +checksum = "1ff70929083a8c1a5f6cd7c904b6071c7914ad04739b510c2f7239dfc9b7dabe" dependencies = [ "extended", "log", @@ -2211,26 +2207,17 @@ dependencies = [ [[package]] name = "symphonia-metadata" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +checksum = "83713a97705d77bdef7cdbc0768fd6e5a54e4cd7e48d60a806ae85639e2c87c6" dependencies = [ - "encoding_rs", "lazy_static", "log", + "regex-lite", + "smallvec", "symphonia-core", ] -[[package]] -name = "symphonia-utils-xiph" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" -dependencies = [ - "symphonia-core", - "symphonia-metadata", -] - [[package]] name = "syn" version = "2.0.117" diff --git a/Cargo.toml b/Cargo.toml index 1dfac553e..6b91f223e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,7 +154,7 @@ claxon = { version = "0.4", optional = true } hound = { version = "3.5", optional = true } lewton = { version = "0.10", optional = true } minimp3_fixed = { version = "0.5.4", optional = true } -symphonia = { version = "0.5.5", optional = true, default-features = false } +symphonia = { version = "0.6.1", optional = true, default-features = false } crossbeam-channel = { version = "0.5.15", optional = true } thiserror = "2" @@ -168,7 +168,7 @@ rtrb = { version = "0.3.2", optional = true } # Rubato resampling rubato = { version = "3.0", default-features = false } -symphonia-adapter-libopus = { version = "0.2", optional = true } +symphonia-adapter-libopus = { version = "0.3", optional = true } [dev-dependencies] quickcheck = "1" @@ -177,7 +177,7 @@ rstest_reuse = "0.7" approx = "0.5.1" divan = "0.1.14" inquire = "0.9.3" -symphonia-adapter-fdk-aac = "0.1" +symphonia-adapter-fdk-aac = "0.2" clap = { version = "4", features = ["derive"] } [[bench]] diff --git a/src/decoder/builder.rs b/src/decoder/builder.rs index 7f9bcb726..f56a38dc8 100644 --- a/src/decoder/builder.rs +++ b/src/decoder/builder.rs @@ -46,7 +46,7 @@ use crate::decoder::symphonia::Registry; use self::read_seek_source::ReadSeekSource; #[cfg(feature = "symphonia")] use ::symphonia::core::{ - codecs::CodecRegistry, + codecs::registry::{CodecRegistry, RegisterableAudioDecoder}, io::{MediaSource, MediaSourceStream}, }; #[cfg(feature = "symphonia")] @@ -104,7 +104,7 @@ impl Default for Settings { let mut codec_registry = CodecRegistry::new(); register_enabled_codecs(&mut codec_registry); #[cfg(feature = "symphonia-libopus")] - codec_registry.register_all::(); + codec_registry.register_audio_decoder::(); Registry::new(codec_registry) }, } @@ -257,9 +257,12 @@ impl DecoderBuilder { #[cfg(feature = "symphonia")] pub fn with_symphonia_decoder(self) -> Self where - D: ::symphonia::core::codecs::Decoder, + D: RegisterableAudioDecoder, { - self.settings.codec_registry.write().register_all::(); + self.settings + .codec_registry + .write() + .register_audio_decoder::(); self } @@ -296,7 +299,7 @@ impl DecoderBuilder { } /// Creates the decoder implementation with configured settings. - fn build_impl(self) -> Result<(DecoderImpl, Settings), DecoderError> { + fn build_impl(self) -> Result<(DecoderImpl<'static, R>, Settings), DecoderError> { let data = self.data.ok_or(DecoderError::UnrecognizedFormat)?; #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))] @@ -346,7 +349,7 @@ impl DecoderBuilder { /// /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined /// or is not supported. - pub fn build(self) -> Result, DecoderError> { + pub fn build(self) -> Result, DecoderError> { let (decoder, _) = self.build_impl()?; Ok(Decoder(decoder)) } @@ -357,7 +360,7 @@ impl DecoderBuilder { /// /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined /// or is not supported. - pub fn build_looped(self) -> Result, DecoderError> { + pub fn build_looped(self) -> Result, DecoderError> { let (decoder, settings) = self.build_impl()?; Ok(LoopedDecoder { inner: Some(decoder), diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 801220446..84623a695 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -81,7 +81,7 @@ mod wav; /// Source of audio samples decoded from an input stream. /// See the [module-level documentation](self) for examples and usage. -pub struct Decoder(DecoderImpl); +pub struct Decoder<'a, R: Read + Seek>(DecoderImpl<'a, R>); /// Source of audio samples from decoding a file that never ends. /// When the end of the file is reached, the decoder starts again from the beginning. @@ -98,9 +98,9 @@ pub struct Decoder(DecoderImpl); /// let file = File::open("audio.mp3").unwrap(); /// let looped_decoder = Decoder::new_looped(file).unwrap(); /// ``` -pub struct LoopedDecoder { +pub struct LoopedDecoder<'a, R: Read + Seek> { /// The underlying decoder implementation. - inner: Option>, + inner: Option>, /// Configuration settings for the decoder. settings: Settings, @@ -109,7 +109,7 @@ pub struct LoopedDecoder { // Cannot really reduce the size of the VorbisDecoder. There are not any // arrays just a lot of struct fields. #[allow(clippy::large_enum_variant)] -enum DecoderImpl { +enum DecoderImpl<'a, R: Read + Seek> { #[cfg(all(feature = "hound", not(feature = "symphonia-wav")))] Wav(wav::WavDecoder), #[cfg(all(feature = "lewton", not(feature = "symphonia-vorbis")))] @@ -119,7 +119,7 @@ enum DecoderImpl { #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] Mp3(mp3::Mp3Decoder), #[cfg(feature = "symphonia")] - Symphonia(symphonia::SymphoniaDecoder, PhantomData), + Symphonia(symphonia::SymphoniaDecoder<'a>, PhantomData), // This variant is here just to satisfy the compiler when there are no decoders enabled. // It is unreachable and should never be constructed. #[allow(dead_code)] @@ -128,7 +128,7 @@ enum DecoderImpl { enum Unreachable {} -impl DecoderImpl { +impl<'a, R: Read + Seek> DecoderImpl<'a, R> { #[inline] fn next(&mut self) -> Option { match self { @@ -281,7 +281,7 @@ impl DecoderImpl { /// let file = File::open("audio.mp3").unwrap(); /// let decoder = Decoder::try_from(file).unwrap(); /// ``` -impl TryFrom for Decoder> { +impl TryFrom for Decoder<'_, BufReader> { type Error = DecoderError; fn try_from(file: std::fs::File) -> Result { @@ -317,7 +317,7 @@ impl TryFrom for Decoder> { /// let reader = BufReader::new(file); /// let decoder = Decoder::try_from(reader).unwrap(); /// ``` -impl TryFrom> for Decoder> +impl<'a, R> TryFrom> for Decoder<'a, BufReader> where R: Read + Seek + Send + Sync + 'static, { @@ -348,7 +348,7 @@ where /// let cursor = Cursor::new(data); /// let decoder = Decoder::try_from(cursor).unwrap(); /// ``` -impl TryFrom> for Decoder> +impl TryFrom> for Decoder<'_, std::io::Cursor> where T: AsRef<[u8]> + Send + Sync + 'static, { @@ -359,7 +359,7 @@ where } } -impl Decoder { +impl<'a, R: Read + Seek + Send + Sync + 'static> Decoder<'a, R> { /// Returns a builder for creating a new decoder with customizable settings. /// /// # Examples @@ -400,7 +400,7 @@ impl Decoder { /// /// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined /// or is not supported. - pub fn new_looped(data: R) -> Result, DecoderError> { + pub fn new_looped(data: R) -> Result, DecoderError> { DecoderBuilder::new().with_data(data).build_looped() } @@ -561,7 +561,7 @@ impl Decoder { } } -impl Iterator for Decoder +impl<'a, R> Iterator for Decoder<'a, R> where R: Read + Seek, { @@ -578,7 +578,7 @@ where } } -impl Source for Decoder +impl<'a, R> Source for Decoder<'a, R> where R: Read + Seek, { @@ -607,7 +607,7 @@ where } } -impl Iterator for LoopedDecoder +impl<'a, R> Iterator for LoopedDecoder<'a, R> where R: Read + Seek, { @@ -707,7 +707,7 @@ where } } -impl Source for LoopedDecoder +impl<'a, R> Source for LoopedDecoder<'a, R> where R: Read + Seek, { diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 5e1c68849..9197ee741 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -5,14 +5,16 @@ use std::{ }; use symphonia::{ core::{ - audio::{AudioBufferRef, SampleBuffer, SignalSpec}, - codecs::{CodecRegistry, Decoder, DecoderOptions, CODEC_TYPE_NULL}, + audio::{AudioSpec, GenericAudioBufferRef}, + codecs::{ + audio::{AudioCodecParameters, AudioDecoder, AudioDecoderOptions}, + registry::CodecRegistry, + CodecParameters, + }, errors::Error, - formats::{FormatOptions, FormatReader, SeekMode, SeekTo, SeekedTo}, + formats::{probe::Hint, FormatOptions, FormatReader, SeekMode, SeekTo, SeekedTo}, io::MediaSourceStream, meta::MetadataOptions, - probe::Hint, - units, }, default::get_probe, }; @@ -48,21 +50,37 @@ impl Registry { } } -pub(crate) struct SymphoniaDecoder { - decoder: Box, +fn samples_from_time_f64( + t: symphonia::core::units::Time, + sample_rate: u32, + channels: u32, +) -> usize { + let (secs_i64, nanos_u32) = t.parts(); + if secs_i64 < 0 { + return 0; + } + let secs = secs_i64 as f64 + (nanos_u32 as f64) / 1e9_f64; + (secs * sample_rate as f64 * channels as f64).ceil() as usize +} + +pub(crate) struct SymphoniaDecoder<'a> { + decoder: Box, current_span_offset: usize, - format: Box, + format: Box, total_duration: Option, - buffer: SampleBuffer, - spec: SignalSpec, + buffer: Vec, + spec: AudioSpec, seek_mode: SeekMode, selected_track_id: u32, samples_in_current_frame: usize, silence_samples_remaining: usize, } -impl SymphoniaDecoder { - pub(crate) fn new(mss: MediaSourceStream, settings: &Settings) -> Result { +impl<'a> SymphoniaDecoder<'a> { + pub(crate) fn new( + mss: MediaSourceStream<'a>, + settings: &Settings, + ) -> Result { match SymphoniaDecoder::init(mss, settings) { Err(e) => match e { Error::IoError(e) => Err(DecoderError::IoError(e.to_string())), @@ -73,6 +91,8 @@ impl SymphoniaDecoder { Error::Unsupported(_) => Err(DecoderError::UnrecognizedFormat), Error::LimitError(e) => Err(DecoderError::LimitError(e)), Error::ResetRequired => Err(DecoderError::ResetRequired), + // Catch-all for future/other Error variants (required because Error is non-exhaustive) + _ => Err(DecoderError::IoError(format!("probe error: {:?}", e))), }, Ok(Some(decoder)) => Ok(decoder), Ok(None) => Err(DecoderError::NoStreams), @@ -80,14 +100,14 @@ impl SymphoniaDecoder { } #[inline] - pub(crate) fn into_inner(self) -> MediaSourceStream { + pub(crate) fn into_inner(self) -> MediaSourceStream<'a> { self.format.into_inner() } fn init( - mss: MediaSourceStream, + mss: MediaSourceStream<'a>, settings: &Settings, - ) -> symphonia::core::errors::Result> { + ) -> symphonia::core::errors::Result>> { let mut hint = Hint::new(); if let Some(ext) = settings.hint.as_ref() { hint.with_extension(ext); @@ -95,55 +115,70 @@ impl SymphoniaDecoder { if let Some(typ) = settings.mime_type.as_ref() { hint.mime_type(typ); } - let format_opts: FormatOptions = FormatOptions { - enable_gapless: settings.gapless, - ..Default::default() - }; + + let format_opts: FormatOptions = Default::default(); + let metadata_opts: MetadataOptions = Default::default(); let seek_mode = if settings.coarse_seek { SeekMode::Coarse } else { SeekMode::Accurate }; - let mut probed = get_probe().format(&hint, mss, &format_opts, &metadata_opts)?; - let stream = match probed.format.default_track() { - Some(stream) => stream, - None => return Ok(None), - }; + // Probe the input and get the FormatReader directly. + let mut format = get_probe().probe(&hint, mss, format_opts, metadata_opts)?; - // Select the first supported track - let track = probed - .format + // Find the first track that contains audio codec parameters + let track = format .tracks() .iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .find(|t| matches!(t.codec_params.as_ref(), Some(CodecParameters::Audio(_)))) .ok_or(symphonia::core::errors::Error::Unsupported( - "No track with supported codec", + "No track with audio codec parameters", ))?; let track_id = track.id; + let audio_params: &AudioCodecParameters = match &track.codec_params { + Some(CodecParameters::Audio(a)) => a, + _ => { + return Err(symphonia::core::errors::Error::Unsupported( + "Track does not contain audio codec parameters", + )) + } + }; + + let mut decoder_opts = AudioDecoderOptions::default(); + decoder_opts.gapless = settings.gapless; + let mut decoder = settings .codec_registry .read() - .make(&track.codec_params, &DecoderOptions::default())?; + .make_audio_decoder(audio_params, &decoder_opts)?; let total_duration = track - .codec_params .time_base - .zip(stream.codec_params.n_frames) - .map(|(base, spans)| base.calc_time(spans).into()) - .filter(|d: &Duration| !d.is_zero()); + .zip(track.duration) + .and_then(|(tb, dur)| tb.calc_duration(dur)) + .and_then(|t| { + let (secs, nanos) = t.parts(); + if secs < 0 { + None // std::time::Duration can't represent negative times + } else { + Some(Duration::new(secs as u64, nanos)) + } + }) + .filter(|d| !d.is_zero()); let decoded = loop { - let current_span = match probed.format.next_packet() { - Ok(packet) => packet, + let current_span = match format.next_packet() { + Ok(Some(packet)) => packet, + Ok(None) => break decoder.last_decoded(), // EOF Err(Error::IoError(_)) => break decoder.last_decoded(), Err(e) => return Err(e), }; // If the packet does not belong to the selected track, skip over it - if current_span.track_id() != track_id { + if current_span.track_id != track_id { continue; } @@ -162,11 +197,11 @@ impl SymphoniaDecoder { } }; let spec = decoded.spec().to_owned(); - let buffer = SymphoniaDecoder::get_buffer(decoded, &spec); + let buffer = SymphoniaDecoder::get_buffer(decoded); Ok(Some(SymphoniaDecoder { decoder, current_span_offset: 0, - format: probed.format, + format, total_duration, buffer, spec, @@ -178,15 +213,14 @@ impl SymphoniaDecoder { } #[inline] - fn get_buffer(decoded: AudioBufferRef, spec: &SignalSpec) -> SampleBuffer { - let duration = units::Duration::from(decoded.capacity() as u64); - let mut buffer = SampleBuffer::::new(duration, *spec); - buffer.copy_interleaved_ref(decoded); - buffer + fn get_buffer(decoded: GenericAudioBufferRef) -> Vec { + let mut out = Vec::new(); + decoded.copy_to_vec_interleaved(&mut out); + out } } -impl Source for SymphoniaDecoder { +impl<'a> Source for SymphoniaDecoder<'a> { #[inline] fn current_span_len(&self) -> Option { Some(self.buffer.len()) @@ -196,7 +230,7 @@ impl Source for SymphoniaDecoder { fn channels(&self) -> ChannelCount { ChannelCount::new( self.spec - .channels + .channels() .count() .try_into() .expect("rodio only support up to u16::MAX channels (65_535)"), @@ -206,7 +240,7 @@ impl Source for SymphoniaDecoder { #[inline] fn sample_rate(&self) -> SampleRate { - SampleRate::new(self.spec.rate).expect("audio should always have a non zero SampleRate") + SampleRate::new(self.spec.rate()).expect("audio should always have a non zero SampleRate") } #[inline] @@ -215,9 +249,20 @@ impl Source for SymphoniaDecoder { } fn try_seek(&mut self, pos: Duration) -> Result<(), source::SeekError> { - if matches!(self.seek_mode, SeekMode::Accurate) - && self.decoder.codec_params().time_base.is_none() - { + // Find track by selected_track_id + let track = self + .format + .tracks() + .iter() + .find(|t| t.id == self.selected_track_id) + .ok_or_else(|| { + source::SeekError::SymphoniaDecoder(SeekError::Demuxer(Arc::new( + symphonia::core::errors::Error::Unsupported("Selected track not found"), + ))) + })?; + + // Refuse accurate seek if time base is missing. + if matches!(self.seek_mode, SeekMode::Accurate) && track.time_base.is_none() { return Err(source::SeekError::SymphoniaDecoder( SeekError::AccurateSeekNotSupported, )); @@ -232,13 +277,41 @@ impl Source for SymphoniaDecoder { } } - // Remember the current channel, so we can restore it after seeking. - let active_channel = self.current_span_offset % self.channels().get() as usize; + let tb = track.time_base.expect("time base checked above"); + let num = tb.numer.get() as u64; + let den = tb.denom.get() as u64; + + let secs = target.as_secs(); + let nanos = target.subsec_nanos() as u64; + + // Compute integer ticks (truncated). + let ticks_secs = secs.saturating_mul(den) / num; + let ticks_nanos = (nanos.saturating_mul(den)) / (num * 1_000_000_000u64); + let mut ticks = ticks_secs.saturating_add(ticks_nanos); + + // Defensive clamp: ensure ticks does not exceed the track's last valid tick (if available) + if let Some(max_ticks_dur) = track.duration { + let mut max_ticks: u64 = max_ticks_dur.get(); + // Make sure we use the last valid tick (avoid handing the demuxer a tick equal to duration) + if max_ticks > 0 { + max_ticks = max_ticks.saturating_sub(1); + } + if ticks > max_ticks { + ticks = max_ticks; + } + } + + let units_dur = symphonia::core::units::Duration::from(ticks); + let units_time = tb.calc_duration(units_dur).ok_or_else(|| { + source::SeekError::SymphoniaDecoder(SeekError::AccurateSeekNotSupported) + })?; + + // Perform seek on the format reader let seek_res = match self.format.seek( self.seek_mode, SeekTo::Time { - time: target.into(), + time: units_time, track_id: None, }, ) { @@ -250,22 +323,18 @@ impl Source for SymphoniaDecoder { other => other.map_err(Arc::new).map_err(SeekError::Demuxer), }?; - // Seeking is a demuxer operation without the decoder knowing about it, - // so we need to reset the decoder to make sure it's in sync and prevent - // audio glitches. + // Reset decoder state and mark buffer offset invalid self.decoder.reset(); - - // Force the iterator to decode the next packet. self.current_span_offset = usize::MAX; - // Symphonia does not seek to the exact position, it seeks to the closest keyframe. - // If accurate seeking is required, fast-forward to the exact position. + // Refine position when accurate seek requested if matches!(self.seek_mode, SeekMode::Accurate) { self.refine_position(seek_res)?; } // After seeking, we are at the beginning of an inter-sample frame, i.e. the first // channel. We need to advance the iterator to the right channel. + let active_channel = self.current_span_offset % self.channels().get() as usize; for _ in 0..active_channel { self.next(); } @@ -294,26 +363,40 @@ pub enum SeekError { } assert_error_traits!(SeekError); -impl SymphoniaDecoder { +impl<'a> SymphoniaDecoder<'a> { /// Note span offset must be set after fn refine_position(&mut self, seek_res: SeekedTo) -> Result<(), source::SeekError> { - // Calculate the number of samples to skip. - let mut samples_to_skip = (Duration::from( - self.decoder - .codec_params() - .time_base - .expect("time base availability guaranteed by caller") - .calc_time(seek_res.required_ts.saturating_sub(seek_res.actual_ts)), - ) - .as_secs_f32() - * self.sample_rate().get() as f32 - * self.channels().get() as f32) - .ceil() as usize; + // Get track and time base for timestamp conversion + let track = self + .format + .tracks() + .iter() + .find(|t| t.id == self.selected_track_id) + .expect("selected track must exist"); + let tb = track + .time_base + .expect("time base availability guaranteed by caller"); + + // Convert required and actual seek timestamps to `Time` + let req_time_opt = tb.calc_time(seek_res.required_ts); + let act_time_opt = tb.calc_time(seek_res.actual_ts); - // Re-align the seek position to the first channel. - samples_to_skip -= samples_to_skip % self.channels().get() as usize; + let sr = self.sample_rate().get(); + let ch = self.channels().get(); - // Skip ahead to the precise position. + // Convert times to sample counts + let req_samples = req_time_opt + .map(|t| samples_from_time_f64(t, sr, ch.into())) + .unwrap_or(0); + let act_samples = act_time_opt + .map(|t| samples_from_time_f64(t, sr, ch.into())) + .unwrap_or(0); + + // Compute whole-frame samples to skip + let mut samples_to_skip = req_samples.saturating_sub(act_samples); + samples_to_skip -= samples_to_skip % ch as usize; + + // Skip the computed number of samples via `next()` for _ in 0..samples_to_skip { self.next(); } @@ -322,7 +405,7 @@ impl SymphoniaDecoder { } } -impl Iterator for SymphoniaDecoder { +impl<'a> Iterator for SymphoniaDecoder<'a> { type Item = Sample; fn next(&mut self) -> Option { @@ -334,17 +417,24 @@ impl Iterator for SymphoniaDecoder { } if self.current_span_offset >= self.buffer.len() { + // Decode next packet(s) into buffer. let decoded = loop { let packet = match self.format.next_packet() { - Ok(packet) => { - if packet.track_id() == self.selected_track_id { - packet - } else { - continue; + Ok(Some(p)) if p.track_id == self.selected_track_id => p, + Ok(Some(_)) => continue, // packet for another track, skip + Ok(None) => { + // Input exhausted - check if mid-frame + let channels = self.channels(); + self.silence_samples_remaining = + padding_samples_needed(self.samples_in_current_frame, channels); + if self.silence_samples_remaining > 0 { + self.samples_in_current_frame = 0; + break None; } + return None; } Err(_) => { - // Input exhausted - check if mid-frame + // Error from demuxer - treat like exhaustion for padding let channels = self.channels(); self.silence_samples_remaining = padding_samples_needed(self.samples_in_current_frame, channels); @@ -389,7 +479,7 @@ impl Iterator for SymphoniaDecoder { match decoded { Some(decoded) => { decoded.spec().clone_into(&mut self.spec); - self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec); + self.buffer = SymphoniaDecoder::get_buffer(decoded); self.current_span_offset = 0; } None => { @@ -399,7 +489,7 @@ impl Iterator for SymphoniaDecoder { } } - let sample = *self.buffer.samples().get(self.current_span_offset)?; + let sample = *self.buffer.get(self.current_span_offset)?; self.current_span_offset += 1; let channels = self.channels(); diff --git a/tests/channel_volume.rs b/tests/channel_volume.rs index cfeb02948..eadf2247c 100644 --- a/tests/channel_volume.rs +++ b/tests/channel_volume.rs @@ -6,7 +6,7 @@ use std::io::BufReader; use rodio::source::ChannelVolume; use rodio::{queue, Decoder, Sample, Source}; -fn create_6_channel_source() -> ChannelVolume>> { +fn create_6_channel_source() -> ChannelVolume>> { let file = fs::File::open("assets/music.mp3").unwrap(); let decoder = Decoder::try_from(file).unwrap(); assert_eq!(decoder.channels().get(), 2);