diff --git a/Cargo.lock b/Cargo.lock index d2c88ff6..ed4cc456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "alsa" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3" dependencies = [ "alsa-sys", "bitflags 2.9.1", @@ -78,6 +78,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -188,8 +197,7 @@ dependencies = [ [[package]] name = "cpal" version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f" +source = "git+https://github.com/RustAudio/cpal?rev=e32ee65a5335301553cc9650b949b868f922d748#e32ee65a5335301553cc9650b949b868f922d748" dependencies = [ "alsa", "coreaudio-rs", @@ -197,14 +205,18 @@ dependencies = [ "jni", "js-sys", "libc", - "mach2", + "mach2 0.6.0", "ndk", "ndk-context", "num-derive", "num-traits", + "objc2", "objc2-audio-toolbox", + "objc2-avf-audio", "objc2-core-audio", "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -560,6 +572,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "memchr" version = "2.7.5" @@ -711,9 +729,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ "objc2-encode", ] @@ -733,6 +751,16 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-avf-audio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc1d11521c211a7ebe17739fc806719da41f56c6b3f949d9861b459188ce910" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-audio" version = "0.3.1" @@ -743,6 +771,7 @@ dependencies = [ "objc2", "objc2-core-audio-types", "objc2-core-foundation", + "objc2-foundation", ] [[package]] @@ -762,7 +791,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.9.1", + "block2", "dispatch2", + "libc", "objc2", ] @@ -778,7 +809,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", "objc2", + "objc2-core-foundation", ] [[package]] @@ -1181,7 +1216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ae312bda09b2368f79f985fdb4df4a0b5cbc75546b511303972d195f8c27d6" dependencies = [ "libc", - "mach2", + "mach2 0.4.3", "winapi", ] @@ -1676,31 +1711,103 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.54.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +checksum = "49e6c4a1f363c8210c6f77ba24f645c61c6fb941eccf013da691f7e09515b8ac" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123e712f464a8a60ce1a13f4c446d2d43ab06464cb5842ff68f5c71b6fb7852e" dependencies = [ "windows-core", - "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.54.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", "windows-result", - "windows-targets 0.52.6", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f3db6b24b120200d649cd4811b4947188ed3a8d2626f7075146c5d178a9a4a" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-numerics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8" +dependencies = [ + "windows-core", + "windows-link", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", ] [[package]] @@ -1801,6 +1908,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab47f085ad6932defa48855254c758cdd0e2f2d48e62a34118a268d8f345e118" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index bc53c68b..82a48166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,8 +46,6 @@ noise = ["rand", "rand_distr"] # # Enable WebAssembly support for web browsers wasm-bindgen = ["cpal/wasm-bindgen"] -# Use shared C++ stdlib on Android (reduces APK size, fixes linking issues) -cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"] # To decode an audio source with Rodio, you need to enable the appropriate features for *both* the # demuxer and the decoder. @@ -103,7 +101,7 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -cpal = { version = "0.16", optional = true } +cpal = { git = "https://github.com/RustAudio/cpal", rev = "e32ee65a5335301553cc9650b949b868f922d748", optional = true } dasp_sample = "0.11.0" claxon = { version = "0.4.2", optional = true } hound = { version = "3.5", optional = true } diff --git a/examples/error_callback.rs b/examples/error_callback.rs index 2e70d4eb..ab335583 100644 --- a/examples/error_callback.rs +++ b/examples/error_callback.rs @@ -14,8 +14,11 @@ fn main() -> Result<(), Box> { let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)? .with_error_callback(move |err| { - // Filter for where err is a DeviceNotAvailable error. - if let cpal::StreamError::DeviceNotAvailable = err { + // Filter for where err is an actionable error. + if matches!( + err, + cpal::StreamError::DeviceNotAvailable | cpal::StreamError::StreamInvalidated + ) { if let Err(e) = tx.send(err) { eprintln!("Error emitting StreamError: {e}"); } @@ -31,9 +34,10 @@ fn main() -> Result<(), Box> { mixer.add(wave); if let Ok(err) = rx.recv_timeout(Duration::from_secs(30)) { - // Here we print the error that was emitted by the error callback. - // but in a real application we may want to destroy the stream and - // try to reopen it, either with the same device or a different one. + // Here we received an error that requires action from the error callback. + // In a real application you would destroy the stream and try to reopen it, + // either with the same device (for StreamInvalidated) or a different device + // (for DeviceNotAvailable). eprintln!("Error with stream {err}"); } diff --git a/examples/microphone.rs b/examples/microphone.rs index c3e55918..97419327 100644 --- a/examples/microphone.rs +++ b/examples/microphone.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { .prompt()?; let input = MicrophoneBuilder::new() - .device(input)? + .device(input.into_inner())? .default_config()? .open_stream()?; diff --git a/src/microphone.rs b/src/microphone.rs index eb98985d..8e0594ea 100644 --- a/src/microphone.rs +++ b/src/microphone.rs @@ -91,7 +91,7 @@ //! //! // Use a specific device (e.g., the second one) //! let mic = MicrophoneBuilder::new() -//! .device(inputs[1].clone())? +//! .device(inputs[1].clone().into_inner())? //! .default_config()? //! .open_stream()?; //! # Ok(()) @@ -130,23 +130,38 @@ pub struct Input { inner: cpal::Device, } -impl From for cpal::Device { - fn from(val: Input) -> Self { - val.inner +impl Input { + /// Consumes the input and returns the inner device. + pub fn into_inner(self) -> cpal::Device { + self.inner } } impl fmt::Debug for Input { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Device") - .field("inner", &self.inner.name().unwrap_or("unknown".to_string())) + .field( + "inner", + &self + .inner + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()), + ) .finish() } } impl fmt::Display for Input { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.inner.name().unwrap_or("unknown".to_string())) + write!( + f, + "{}", + self.inner + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()) + ) } } @@ -271,8 +286,7 @@ impl Microphone { I64, i64; U8, u8; U16, u16; - // TODO: uncomment when https://github.com/RustAudio/cpal/pull/1011 is merged - // U24, cpal::U24; + U24, cpal::U24; U32, u32; U64, u64 ) diff --git a/src/microphone/builder.rs b/src/microphone/builder.rs index deac72da..cc10b383 100644 --- a/src/microphone/builder.rs +++ b/src/microphone/builder.rs @@ -76,10 +76,11 @@ where f.debug_struct("MicrophoneBuilder") .field( "device", - &self - .device - .as_ref() - .map(|d| d.0.name().unwrap_or("unknown".to_string())), + &self.device.as_ref().map(|d| { + d.0.description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()) + }), ) .field("config", &self.config) .finish() @@ -128,7 +129,7 @@ where /// ```no_run /// # use rodio::microphone::{MicrophoneBuilder, available_inputs}; /// let input = available_inputs()?.remove(2); - /// let builder = MicrophoneBuilder::new().device(input)?; + /// let builder = MicrophoneBuilder::new().device(input.into_inner())?; /// # Ok::<(), Box>(()) /// ``` pub fn device( @@ -140,7 +141,10 @@ where .supported_input_configs() .map_err(|source| Error::InputConfigs { source, - device_name: device.name().unwrap_or_else(|_| "unknown".to_string()), + device_name: device + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()), })? .collect(); Ok(MicrophoneBuilder { @@ -169,8 +173,9 @@ where .map_err(|source| Error::InputConfigs { source, device_name: default_device - .name() - .unwrap_or_else(|_| "unknown".to_string()), + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()), })? .collect(); Ok(MicrophoneBuilder { @@ -203,7 +208,10 @@ where .default_input_config() .map_err(|source| Error::DefaultInputConfig { source, - device_name: device.name().unwrap_or_else(|_| "unknown".to_string()), + device_name: device + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()), })? .into(); @@ -266,7 +274,10 @@ where .any(|range| config.supported_given(range)) { Err(Error::UnsupportedByDevice { - device_name: device.name().unwrap_or_else(|_| "unknown".to_string()), + device_name: device + .description() + .ok() + .map_or("unknown".to_string(), |d| d.name().to_string()), }) } else { Ok(()) diff --git a/src/microphone/config.rs b/src/microphone/config.rs index 21c6d5c5..923a77d4 100644 --- a/src/microphone/config.rs +++ b/src/microphone/config.rs @@ -34,8 +34,8 @@ impl InputConfig { buffer_ok && self.channel_count.get() == supported.channels() && self.sample_format == supported.sample_format() - && self.sample_rate.get() <= supported.max_sample_rate().0 - && self.sample_rate.get() >= supported.min_sample_rate().0 + && self.sample_rate.get() <= supported.max_sample_rate() + && self.sample_rate.get() >= supported.min_sample_rate() } pub(crate) fn with_f32_samples(&self) -> Self { @@ -47,7 +47,7 @@ impl InputConfig { pub(crate) fn stream_config(&self) -> cpal::StreamConfig { cpal::StreamConfig { channels: self.channel_count.get(), - sample_rate: cpal::SampleRate(self.sample_rate.get()), + sample_rate: self.sample_rate.get(), buffer_size: self.buffer_size, } } @@ -62,7 +62,7 @@ impl From for InputConfig { Self { channel_count: NonZero::new(value.channels()) .expect("A supported config never has 0 channels"), - sample_rate: NonZero::new(value.sample_rate().0) + sample_rate: NonZero::new(value.sample_rate()) .expect("A supported config produces samples"), buffer_size, sample_format: value.sample_format(), diff --git a/src/stream.rs b/src/stream.rs index fc014b8f..21b8756c 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -131,7 +131,12 @@ impl OutputStreamConfig { impl core::fmt::Debug for OutputStreamBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let device = if let Some(device) = &self.device { - "Some(".to_owned() + device.name().as_deref().unwrap_or("UnNamed") + ")" + "Some(".to_owned() + + &device + .description() + .ok() + .map_or("UnNamed".to_string(), |d| d.name().to_string()) + + ")" } else { "None".to_owned() }; @@ -307,7 +312,7 @@ where self.config = OutputStreamConfig { channel_count: NonZero::new(config.channels()) .expect("no valid cpal config has zero channels"), - sample_rate: NonZero::new(config.sample_rate().0) + sample_rate: NonZero::new(config.sample_rate()) .expect("no valid cpal config has zero sample rate"), sample_format: config.sample_format(), ..Default::default() @@ -320,7 +325,7 @@ where self.config = OutputStreamConfig { channel_count: NonZero::new(config.channels) .expect("no valid cpal config has zero channels"), - sample_rate: NonZero::new(config.sample_rate.0) + sample_rate: NonZero::new(config.sample_rate) .expect("no valid cpal config has zero sample rate"), buffer_size: config.buffer_size, ..self.config @@ -390,7 +395,7 @@ impl From<&OutputStreamConfig> for StreamConfig { fn from(config: &OutputStreamConfig) -> Self { cpal::StreamConfig { channels: config.channel_count.get() as cpal::ChannelCount, - sample_rate: cpal::SampleRate(config.sample_rate.get()), + sample_rate: config.sample_rate.get(), buffer_size: config.buffer_size, } } @@ -515,8 +520,7 @@ impl OutputStream { I64, i64; U8, u8; U16, u16; - // TODO: uncomment when https://github.com/RustAudio/cpal/pull/1011 is merged - // U24, U24; + U24, cpal::U24; U32, u32; U64, u64 ); @@ -539,7 +543,7 @@ pub fn supported_output_configs( let max_rate = sf.max_sample_rate(); let min_rate = sf.min_sample_rate(); let mut formats = vec![sf.with_max_sample_rate()]; - let preferred_rate = cpal::SampleRate(HZ_44100.get()); + let preferred_rate = HZ_44100.get(); if preferred_rate < max_rate && preferred_rate > min_rate { formats.push(sf.with_sample_rate(preferred_rate)) } diff --git a/src/wav_output.rs b/src/wav_output.rs index ddf40f7b..bdf3bf03 100644 --- a/src/wav_output.rs +++ b/src/wav_output.rs @@ -46,7 +46,7 @@ pub fn wav_to_file( /// # Example /// ```rust /// # use rodio::static_buffer::StaticSamplesBuffer; -/// # use rodio::collect_to_wav; +/// # use rodio::wav_to_writer; /// # const SAMPLES: [rodio::Sample; 5] = [0.0, 1.0, 2.0, 3.0, 4.0]; /// # let source = StaticSamplesBuffer::new( /// # 1.try_into().unwrap(),