diff --git a/CHANGELOG.md b/CHANGELOG.md index b20065d43..b801d6883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `Display` and `FromStr` implementations for `HostId`. - Add support for custom `Host`s, `Device`s, and `Stream`s. - Add `Sample::bits_per_sample` method. +- Add `Copy` impl to `InputCallbackInfo` and `OutputCallbackInfo`. - Change `SampleRate` from struct to `u32` type alias. - Update `audio_thread_priority` to 0.34. - AAudio: Configure buffer to ensure consistent callback buffer sizes. diff --git a/Cargo.toml b/Cargo.toml index 3418be8e5..6e7d50bc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,35 @@ edition = "2021" rust-version = "1.70" [features] +# ASIO backend for Windows +# Provides low-latency audio I/O by bypassing the Windows audio stack +# Requires: ASIO drivers and LLVM/Clang for build-time bindings +# See README for detailed setup instructions asio = [ - "asio-sys", - "num-traits", -] # Only available on Windows. See README for setup instructions. + "dep:asio-sys", + "dep:num-traits", +] -# Only available on web when atomics are enabled. See README for what it does. +# JACK Audio Connection Kit backend for Linux/BSD +# Provides low-latency connections between applications and audio hardware +# Requires: JACK server and client libraries installed on the system +# Platform: Linux, DragonFly BSD, FreeBSD, NetBSD only +# Note: While JACK exists on Windows/macOS, CPAL's JACK backend is only implemented for Linux/BSD +jack = ["dep:jack"] + +# WebAssembly backend using wasm-bindgen +# Enables the Web Audio API backend for browser-based audio +# Required for any WebAssembly audio support +# Platform: WebAssembly (wasm32-unknown-unknown) +# Note: This is typically enabled automatically when targeting wasm32 +wasm-bindgen = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures"] + +# Audio Worklet backend for WebAssembly +# Provides lower-latency web audio processing compared to default Web Audio API +# Requires: Build with atomics support and Cross-Origin headers for SharedArrayBuffer +# Platform: WebAssembly (wasm32-unknown-unknown) audioworklet = [ - "dep:wasm-bindgen-futures", + "wasm-bindgen", "web-sys/Blob", "web-sys/BlobPropertyBag", "web-sys/Url", @@ -27,7 +48,9 @@ audioworklet = [ ] # Support for user-defined custom hosts, devices, and streams +# Allows integration with audio systems not natively supported by CPAL # See examples/custom.rs for usage +# Platform: All platforms custom = [] [dependencies] @@ -152,3 +175,20 @@ name = "record_wav" [[example]] name = "synth_tones" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "aarch64-apple-ios", + "wasm32-unknown-unknown", + "wasm32-unknown-emscripten", + "aarch64-linux-android", + "x86_64-unknown-freebsd", + "x86_64-unknown-netbsd", + "x86_64-unknown-dragonfly", +] diff --git a/README.md b/README.md index f031fcf1b..1e2e80765 100644 --- a/README.md +++ b/README.md @@ -27,30 +27,75 @@ Note that on Linux, the ALSA development files are required. These are provided as part of the `libasound2-dev` package on Debian and Ubuntu distributions and `alsa-lib-devel` on Fedora. -## Compiling for Web Assembly +## Compiling for WebAssembly -If you are interested in using CPAL with WASM, please see [this guide](https://github.com/RustAudio/cpal/wiki/Setting-up-a-new-CPAL-WASM-project) in our Wiki which walks through setting up a new project from scratch. +If you are interested in using CPAL with WebAssembly, please see [this guide](https://github.com/RustAudio/cpal/wiki/Setting-up-a-new-CPAL-WASM-project) in our Wiki which walks through setting up a new project from scratch. Some of the examples in this repository also provide working configurations that you can use as reference. -## Feature flags for audio backends +## Optional Features -Some audio backends are optional and will only be compiled with a [feature flag](https://doc.rust-lang.org/cargo/reference/features.html). +CPAL provides the following optional features: -- JACK (on Linux): `jack` -- ASIO (on Windows): `asio` -- AudioWorklet (on Web): `audioworklet` +### `asio` -For AudioWorklet backend usage see the README for the `audioworklet-beep` example. +**Platform:** Windows -## ASIO on Windows +Enables the ASIO (Audio Stream Input/Output) backend. ASIO provides low-latency audio I/O by bypassing the Windows audio stack. + +**Requirements:** +- ASIO drivers for your audio device +- LLVM/Clang for build-time bindings generation + +**Setup:** See the [ASIO setup guide](#asio-on-windows) below for detailed installation instructions. + +### `jack` + +**Platform:** Linux, DragonFly BSD, FreeBSD, NetBSD + +Enables the JACK (JACK Audio Connection Kit) backend. JACK is an audio server providing low-latency connections between applications and audio hardware. + +**Requirements:** +- JACK server and client libraries must be installed on the system + +**Usage:** See the [beep example](examples/beep.rs) for selecting the JACK host at runtime. + +**Note:** While JACK is available on Windows and macOS, CPAL's JACK backend is currently only implemented for Linux and BSD systems. On other platforms, use the native backends (WASAPI/ASIO for Windows, CoreAudio for macOS). + +### `wasm-bindgen` + +**Platform:** WebAssembly (wasm32-unknown-unknown) + +Enables the Web Audio API backend for browser-based audio. This is the base feature required for any WebAssembly audio support. + +**Requirements:** +- Target `wasm32-unknown-unknown` +- Web browser with Web Audio API support + +**Usage:** See the `wasm-beep` example for basic WebAssembly audio setup. + +### `audioworklet` + +**Platform:** WebAssembly (wasm32-unknown-unknown) + +Enables the Audio Worklet backend for lower-latency web audio processing compared to the default Web Audio API backend. + +**Requirements:** +- The `wasm-bindgen` feature (automatically enabled) +- Build with atomics support: `RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals"` +- Web server must send Cross-Origin headers for SharedArrayBuffer support + +**Setup:** See the `audioworklet-beep` example README for complete setup instructions. + +**Note:** Audio Worklet provides better performance than the default Web Audio API by running audio processing on a separate thread. + +### `custom` + +**Platform:** All platforms -[ASIO](https://en.wikipedia.org/wiki/Audio_Stream_Input/Output) is an audio -driver protocol by Steinberg. While it is available on multiple operating -systems, it is most commonly used on Windows to work around limitations of -WASAPI including access to large numbers of channels and lower-latency audio -processing. +Enables support for user-defined custom host implementations, allowing integration with audio systems not natively supported by CPAL. -CPAL allows for using the ASIO SDK as the audio host on Windows instead of -WASAPI. +**Usage:** See `examples/custom.rs` for implementation details. + +## ASIO on Windows ### Locating the ASIO SDK @@ -64,78 +109,81 @@ The build script will try to find the ASIO SDK by following these steps in order In an ideal situation you don't need to worry about this step. -### Preparing the build environment +### Preparing the Build Environment + +1. **Install LLVM/Clang**: `bindgen`, the library used to generate bindings to the C++ SDK, requires clang. Download and install LLVM from under the "Pre-Built Binaries" section. -1. `bindgen`, the library used to generate bindings to the C++ SDK, requires - clang. **Download and install LLVM** from - [here](http://releases.llvm.org/download.html) under the "Pre-Built Binaries" - section. The version as of writing this is 17.0.1. -2. Add the LLVM `bin` directory to a `LIBCLANG_PATH` environment variable. If - you installed LLVM to the default directory, this should work in the command - prompt: +2. **Set LIBCLANG_PATH**: Add the LLVM `bin` directory to a `LIBCLANG_PATH` environment variable. If you installed LLVM to the default directory, this should work in the command prompt: ``` setx LIBCLANG_PATH "C:\Program Files\LLVM\bin" ``` -3. If you don't have any ASIO devices or drivers available, you can [**download - and install ASIO4ALL**](http://www.asio4all.org/). Be sure to enable the - "offline" feature during installation despite what the installer says about - it being useless. -4. Our build script assumes that Microsoft Visual Studio is installed if the host OS for compilation is Windows. The script will try to find `vcvarsall.bat` - and execute it with the right host and target machine architecture regardless of the Microsoft Visual Studio version. - If there are any errors encountered in this process which is unlikely, - you may find the `vcvarsall.bat` manually and execute it with your machine architecture as an argument. - The script will detect this and skip the step. - - A manually executed command example for 64 bit machines: +3. **Install ASIO Drivers** (optional for testing): If you don't have any ASIO devices or drivers available, you can download and install ASIO4ALL from . Be sure to enable the "offline" feature during installation. + +4. **Visual Studio**: The build script assumes Microsoft Visual Studio is installed. It will try to find `vcvarsall.bat` and execute it with the right host and target architecture. If needed, you can manually execute it: ``` "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 ``` + For more information see the [vcvarsall.bat documentation](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line). - For more information please refer to the documentation of [`vcvarsall.bat``](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax). +### Using ASIO in Your Application -5. Select the ASIO host at the start of our program with the following code: +1. **Enable the feature** in your `Cargo.toml`: + ```toml + cpal = { version = "*", features = ["asio"] } + ``` +2. **Select the ASIO host** in your code: ```rust - let host; - #[cfg(target_os = "windows")] - { - host = cpal::host_from_id(cpal::HostId::Asio).expect("failed to initialise ASIO host"); - } + let host = cpal::host_from_id(cpal::HostId::Asio) + .expect("failed to initialise ASIO host"); ``` - If you run into compilations errors produced by `asio-sys` or `bindgen`, make - sure that `CPAL_ASIO_DIR` is set correctly and try `cargo clean`. +### Troubleshooting -6. Make sure to enable the `asio` feature when building CPAL: +If you encounter compilation errors from `asio-sys` or `bindgen`: +- Verify `CPAL_ASIO_DIR` is set correctly +- Try running `cargo clean` +- Ensure LLVM/Clang is properly installed and `LIBCLANG_PATH` is set - ``` - cargo build --features "asio" - ``` +### Cross-Compilation - or if you are using CPAL as a dependency in a downstream project, enable the - feature like this: +When Windows is the host and target OS, the build script supports all cross-compilation targets supported by the MSVC compiler. - ```toml - cpal = { version = "*", features = ["asio"] } - ``` +It is also possible to compile Windows applications with ASIO support on Linux and macOS using the MinGW-w64 toolchain. -_Updated as of ASIO version 2.3.3._ +**Requirements:** +- Include the MinGW-w64 include directory in your `CPLUS_INCLUDE_PATH` environment variable +- Include the LLVM include directory in your `CPLUS_INCLUDE_PATH` environment variable -### Cross compilation +**Example for macOS** (targeting `x86_64-pc-windows-gnu` with `mingw-w64` installed via brew): +``` +export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/opt/homebrew/Cellar/mingw-w64/11.0.1/toolchain-x86_64/x86_64-w64-mingw32/include" +``` -When Windows is the host and the target OS, the build script of `asio-sys` supports all cross compilation targets -which are supported by the MSVC compiler. An exhaustive list of combinations could be found [here](https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax) with the addition of undocumented `arm64`, `arm64_x86`, `arm64_amd64` and `arm64_arm` targets. (5.11.2023) +## Troubleshooting -It is also possible to compile Windows applications with ASIO support on Linux and macOS. +### No Default Device Available -For both platforms the common way to do this is to use the [MinGW-w64](https://www.mingw-w64.org/) toolchain. +If you receive errors about no default input or output device: -Make sure that you have included the `MinGW-w64` include directory in your `CPLUS_INCLUDE_PATH` environment variable. -Make sure that LLVM is installed and include directory is also included in your `CPLUS_INCLUDE_PATH` environment variable. +- **Linux/ALSA:** Ensure your user is in the `audio` group and that ALSA is properly configured +- **Linux/PulseAudio:** Check that PulseAudio is running: `pulseaudio --check` +- **Windows:** Verify your audio device is enabled in Sound Settings +- **macOS:** Check System Preferences > Sound for available devices +- **Mobile (iOS/Android):** Ensure your app has microphone/audio permissions -Example for macOS for the target of `x86_64-pc-windows-gnu` where `mingw-w64` is installed via brew: +### Buffer Size Issues -``` -export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/opt/homebrew/Cellar/mingw-w64/11.0.1/toolchain-x86_64/x86_64-w64-mingw32/include" -``` +If you experience audio glitches or dropouts: + +- Try `BufferSize::Default` first before requesting specific sizes +- When using `BufferSize::Fixed`, query `SupportedBufferSize` to find valid ranges +- Smaller buffers reduce latency but increase CPU load and risk dropouts +- Ensure your audio callback completes quickly and avoids blocking operations + +### Build Errors + +- **ASIO on Windows:** Verify `LIBCLANG_PATH` is set and LLVM is installed +- **ALSA on Linux:** Install development packages: `libasound2-dev` (Debian/Ubuntu) or `alsa-lib-devel` (Fedora) +- **JACK:** Install JACK development libraries before enabling the `jack` feature diff --git a/examples/beep.rs b/examples/beep.rs index 50649d57c..f5e32c1f5 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,3 +1,16 @@ +//! Plays a simple 440 Hz sine wave (beep) tone. +//! +//! This example demonstrates: +//! - Selecting audio hosts (with optional JACK support on Linux) +//! - Selecting devices by ID or using the default output device +//! - Querying the default output configuration +//! - Building and running an output stream with typed samples +//! - Generating audio data in the stream callback +//! +//! Run with: `cargo run --example beep` +//! With JACK (Linux): `cargo run --example beep --features jack -- --jack` +//! With specific device: `cargo run --example beep -- --device "wasapi:device_id"` + use clap::Parser; use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, diff --git a/examples/enumerate.rs b/examples/enumerate.rs index dccb5368a..7d824c3ac 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,3 +1,14 @@ +//! Enumerates all available audio hosts, devices, and their supported configurations. +//! +//! This example demonstrates: +//! - Querying available audio hosts on the system +//! - Enumerating all audio devices for each host +//! - Retrieving device IDs for persistent identification +//! - Getting device descriptions with metadata +//! - Listing supported input and output stream configurations +//! +//! Run with: `cargo run --example enumerate` + extern crate anyhow; extern crate cpal; diff --git a/src/device_description.rs b/src/device_description.rs index 60ff57620..c99bb240a 100644 --- a/src/device_description.rs +++ b/src/device_description.rs @@ -1,3 +1,9 @@ +//! Device metadata and description types. +//! +//! This module provides structured information about audio devices including manufacturer, +//! device type, interface type, and connection details. Not all backends provide complete +//! information - availability depends on platform capabilities. + use std::fmt; use crate::ChannelCount; @@ -29,7 +35,7 @@ pub struct DeviceDescription { /// Physical address or connection identifier address: Option, - /// Additional description lines with non-structured, detailed information + /// Additional description lines with non-structured, detailed information. extended: Vec, } diff --git a/src/error.rs b/src/error.rs index fe1c0b51d..94598857d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,8 +23,9 @@ impl Error for HostUnavailable {} /// `panic!` caused by some unforeseen or unknown reason. /// /// **Note:** If you notice a `BackendSpecificError` that you believe could be better handled in a -/// cross-platform manner, please create an issue or submit a pull request with a patch that adds -/// the necessary error variant to the appropriate error enum. +/// cross-platform manner, please create an issue at +/// with details about your use case, the backend you're using, and the error message. Or submit +/// a pull request with a patch that adds the necessary error variant to the appropriate error enum. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BackendSpecificError { pub description: String, @@ -65,7 +66,7 @@ impl From for DevicesError { } } -/// An error that may occur while attempting to retrieve a device id. +/// An error that may occur while attempting to retrieve a device ID. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceIdError { /// See the [`BackendSpecificError`] docs for more information about this error variant. @@ -79,7 +80,7 @@ impl Display for DeviceIdError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::BackendSpecific { err } => err.fmt(f), - Self::UnsupportedPlatform => f.write_str("Device ids are unsupported for this OS"), + Self::UnsupportedPlatform => f.write_str("Device IDs are unsupported for this OS"), } } } diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index e36a4d851..c2afe3b21 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -1,3 +1,7 @@ +//! AAudio backend implementation. +//! +//! Default backend on Android. + use std::cmp; use std::convert::TryInto; use std::sync::{Arc, Mutex}; diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 2fbc4a74a..240e2cd7a 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -1,3 +1,7 @@ +//! ALSA backend implementation. +//! +//! Default backend on Linux and BSD systems. + extern crate alsa; extern crate libc; diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 9530f2568..54e7771a8 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -1,3 +1,8 @@ +//! ASIO backend implementation. +//! +//! ASIO is available on Windows with the `asio` feature. +//! See the project README for setup instructions. + extern crate asio_sys as sys; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; diff --git a/src/host/audioworklet/mod.rs b/src/host/audioworklet/mod.rs index 535cb07ec..7f02c88c2 100644 --- a/src/host/audioworklet/mod.rs +++ b/src/host/audioworklet/mod.rs @@ -1,3 +1,8 @@ +//! Audio Worklet backend implementation. +//! +//! Available on WebAssembly with the `audioworklet` feature. Requires atomics support. +//! See the `audioworklet-beep` example for setup instructions. + mod dependent_module; use js_sys::wasm_bindgen; @@ -212,7 +217,7 @@ impl DeviceTrait for Device { let ctx = audio_context.clone(); wasm_bindgen_futures::spawn_local(async move { - let result: Result<(), JsValue> = (async move || { + let result: Result<(), JsValue> = async move { let mod_url = dependent_module!("worklet.js")?; wasm_bindgen_futures::JsFuture::from(ctx.audio_worklet()?.add_module(&mod_url)?) .await?; @@ -255,7 +260,7 @@ impl DeviceTrait for Device { audio_worklet_node.connect_with_audio_node(&destination)?; Ok(()) - })() + } .await; if let Err(err) = result { diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index ebe08a210..dd9b85fea 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -1,3 +1,7 @@ +//! CoreAudio backend implementation. +//! +//! Default backend on macOS and iOS. + use objc2_core_audio_types::{ kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatFlagIsSignedInteger, kAudioFormatLinearPCM, AudioStreamBasicDescription, diff --git a/src/host/custom/mod.rs b/src/host/custom/mod.rs index 7bcc94347..68fdea9cc 100644 --- a/src/host/custom/mod.rs +++ b/src/host/custom/mod.rs @@ -1,3 +1,8 @@ +//! Custom host backend. +//! +//! Allows user-defined host implementations with the `custom` feature. +//! See `examples/custom.rs` for usage. + use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BuildStreamError, Data, DefaultStreamConfigError, DeviceDescription, DeviceId, DeviceIdError, diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index eff9ab44b..81f73ec79 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -1,3 +1,7 @@ +//! Emscripten backend implementation. +//! +//! Default backend on Emscripten. + use js_sys::Float32Array; use std::time::Duration; use wasm_bindgen::prelude::*; diff --git a/src/host/jack/mod.rs b/src/host/jack/mod.rs index fbee69b91..69da7a143 100644 --- a/src/host/jack/mod.rs +++ b/src/host/jack/mod.rs @@ -1,3 +1,7 @@ +//! JACK backend implementation. +//! +//! Available on Linux/BSD with the `jack` feature. Requires JACK server and client libraries. + extern crate jack; use crate::traits::HostTrait; @@ -16,7 +20,13 @@ const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; pub type Devices = std::vec::IntoIter; -/// The JACK Host type +/// The JACK host, providing access to JACK audio devices. +/// +/// # JACK-Specific Configuration +/// +/// Unlike other backends, JACK provides configuration options to control connection and server behavior: +/// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically) +/// - Server auto-start via [`set_start_server_automatically`](Host::set_start_server_automatically) #[derive(Debug)] pub struct Host { /// The name that the client will have in JACK. @@ -43,13 +53,23 @@ impl Host { host.initialize_default_devices(); Ok(host) } - /// Set whether the ports should automatically be connected to system - /// (default is true) + /// Configures whether created ports should automatically connect to system playback/capture ports. + /// + /// When enabled (default), output streams connect to system playback ports and input streams + /// connect to system capture ports automatically. When disabled, applications must manually + /// connect ports using JACK tools or APIs. + /// + /// Default: `true` pub fn set_connect_automatically(&mut self, do_connect: bool) { self.connect_ports_automatically = do_connect; } - /// Set whether a JACK server should be automatically started if it isn't already. - /// (default is false) + + /// Configures whether the JACK server should automatically start if not already running. + /// + /// When enabled, attempting to create a JACK client will start the JACK server if it's not + /// running. When disabled (default), client creation fails if the server is not running. + /// + /// Default: `false` pub fn set_start_server_automatically(&mut self, do_start_server: bool) { self.start_server_automatically = do_start_server; } diff --git a/src/host/mod.rs b/src/host/mod.rs index 8476fcdf5..1226da0e8 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -10,7 +10,6 @@ pub(crate) mod alsa; #[cfg(all(windows, feature = "asio"))] pub(crate) mod asio; #[cfg(all( - target_arch = "wasm32", feature = "wasm-bindgen", feature = "audioworklet", target_feature = "atomics" @@ -50,23 +49,3 @@ pub(crate) mod custom; all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] pub(crate) mod null; - -/// Compile-time assertion that a type implements Send. -/// Use this macro in each host module to ensure Stream is Send. -#[macro_export] -macro_rules! assert_stream_send { - ($t:ty) => { - const fn _assert_stream_send() {} - const _: () = _assert_stream_send::<$t>(); - }; -} - -/// Compile-time assertion that a type implements Sync. -/// Use this macro in each host module to ensure Stream is Sync. -#[macro_export] -macro_rules! assert_stream_sync { - ($t:ty) => { - const fn _assert_stream_sync() {} - const _: () = _assert_stream_sync::<$t>(); - }; -} diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 9f54d580e..a5e54a290 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,3 +1,7 @@ +//! Null backend implementation. +//! +//! Fallback no-op backend for unsupported platforms. + use std::time::Duration; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index e563c0f1b..8b8762410 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,3 +1,7 @@ +//! WASAPI backend implementation. +//! +//! Default backend on Windows. + #[allow(unused_imports)] pub use self::device::{ default_input_device, default_output_device, Device, Devices, SupportedInputConfigs, diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index eab19a7da..fe1bd8a5d 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -1,3 +1,7 @@ +//! Web Audio backend implementation. +//! +//! Default backend on WebAssembly. + extern crate js_sys; extern crate wasm_bindgen; extern crate web_sys; @@ -358,7 +362,7 @@ impl DeviceTrait for Device { // See this issue: https://github.com/WebAudio/web-audio-api/issues/2565 #[cfg(target_feature = "atomics")] { - temporary_channel_array_view.copy_from(&mut temporary_channel_buffer); + temporary_channel_array_view.copy_from(&temporary_channel_buffer); ctx_buffer .unchecked_ref::() .copy_to_channel(&temporary_channel_array_view, channel as i32) diff --git a/src/lib.rs b/src/lib.rs index 7ba2ab925..e2cae1c5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,8 +78,9 @@ //! ``` //! //! While the stream is running, the selected audio device will periodically call the data callback -//! that was passed to the function. The callback is passed an instance of either [`&Data` or -//! `&mut Data`](Data) depending on whether the stream is an input stream or output stream respectively. +//! that was passed to the function. For input streams, the callback receives `&`[`Data`] containing +//! captured audio samples. For output streams, the callback receives `&mut`[`Data`] to be filled +//! with audio samples for playback. //! //! > **Note**: Creating and running a stream will *not* block the thread. On modern platforms, the //! > given callback is called by a dedicated, high-priority thread responsible for delivering @@ -153,16 +154,31 @@ //! [`supported_input_configs()`]: traits::DeviceTrait::supported_input_configs //! [`supported_output_configs()`]: traits::DeviceTrait::supported_output_configs -#![recursion_limit = "2048"] +#![cfg_attr(docsrs, feature(doc_cfg))] // Extern crate declarations with `#[macro_use]` must unfortunately be at crate root. -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] extern crate js_sys; -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] extern crate wasm_bindgen; -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] extern crate web_sys; +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] +use wasm_bindgen::prelude::*; + pub use device_description::{ DeviceDescription, DeviceDescriptionBuilder, DeviceDirection, DeviceType, InterfaceType, }; @@ -174,8 +190,6 @@ pub use platform::{ pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24}; use std::convert::TryInto; use std::time::Duration; -#[cfg(target_os = "emscripten")] -use wasm_bindgen::prelude::*; pub mod device_description; mod error; @@ -208,6 +222,33 @@ pub type FrameCount = u32; /// Device IDs should remain stable across application restarts and can be serialized using `Display`/`FromStr`. /// /// A device ID consists of a [`HostId`] identifying the audio backend and a device-specific identifier string. +/// +/// # Example +/// +/// ```no_run +/// use cpal::traits::{HostTrait, DeviceTrait}; +/// use cpal::DeviceId; +/// use std::str::FromStr; +/// +/// let host = cpal::default_host(); +/// let device = host.default_output_device().unwrap(); +/// let device_id = device.id().unwrap(); +/// +/// // Serialize to string (e.g., for storage in config file) +/// let id_string = device_id.to_string(); +/// println!("Device ID: {}", id_string); // e.g., "wasapi:device_identifier" +/// +/// // Deserialize from string +/// match DeviceId::from_str(&id_string) { +/// Ok(parsed_id) => { +/// // Retrieve the device by its ID +/// if let Some(device) = host.device_by_id(&parsed_id) { +/// println!("Found device: {:?}", device.id()); +/// } +/// } +/// Err(e) => eprintln!("Failed to parse device ID: {}", e), +/// } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct DeviceId(pub crate::platform::HostId, pub String); @@ -224,7 +265,7 @@ impl std::str::FromStr for DeviceId { let (host_str, device_str) = s.split_once(':').ok_or(DeviceIdError::BackendSpecific { err: BackendSpecificError { description: format!( - "Failed to parse device id from: {s}\nCheck if format matches \"host:device_id\"" + "Failed to parse device ID from: {s}\nCheck if format matches \"host:device_id\"" ), }, })?; @@ -263,6 +304,31 @@ impl std::str::FromStr for DeviceId { /// Smaller buffer sizes reduce latency but may increase CPU usage and risk audio /// dropouts if the callback cannot process audio quickly enough. /// +/// # Example +/// +/// ```no_run +/// use cpal::traits::{DeviceTrait, HostTrait}; +/// use cpal::{BufferSize, SupportedBufferSize}; +/// +/// let host = cpal::default_host(); +/// let device = host.default_output_device().unwrap(); +/// let config = device.default_output_config().unwrap(); +/// +/// // Check supported buffer size range +/// match config.buffer_size() { +/// SupportedBufferSize::Range { min, max } => { +/// println!("Buffer size range: {} - {}", min, max); +/// // Request a small buffer for low latency +/// let mut stream_config = config.config(); +/// stream_config.buffer_size = BufferSize::Fixed(256); +/// } +/// SupportedBufferSize::Unknown => { +/// // Platform doesn't expose buffer size control +/// println!("Buffer size cannot be queried on this platform"); +/// } +/// } +/// ``` +/// /// [`BufferSize::Default`]: BufferSize::Default /// [`BufferSize::Fixed`]: BufferSize::Fixed /// [`BufferSize::Fixed(x)`]: BufferSize::Fixed @@ -274,12 +340,18 @@ pub enum BufferSize { Fixed(FrameCount), } -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] impl wasm_bindgen::describe::WasmDescribe for BufferSize { fn describe() {} } -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] impl wasm_bindgen::convert::IntoWasmAbi for BufferSize { type Abi = as wasm_bindgen::convert::IntoWasmAbi>::Abi; @@ -292,10 +364,33 @@ impl wasm_bindgen::convert::IntoWasmAbi for BufferSize { } } +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] +impl wasm_bindgen::convert::FromWasmAbi for BufferSize { + type Abi = as wasm_bindgen::convert::FromWasmAbi>::Abi; + + unsafe fn from_abi(js: Self::Abi) -> Self { + match Option::::from_abi(js) { + None => Self::Default, + Some(fc) => Self::Fixed(fc), + } + } +} + /// The set of parameters used to describe how to open a stream. /// /// The sample format is omitted in favour of using a sample type. -#[cfg_attr(target_os = "emscripten", wasm_bindgen)] +/// +/// See also [`BufferSize`] for details on buffer size behavior and latency considerations. +#[cfg_attr( + all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") + ), + wasm_bindgen +)] #[derive(Clone, Debug, Eq, PartialEq)] pub struct StreamConfig { pub channels: ChannelCount, @@ -311,7 +406,7 @@ pub enum SupportedBufferSize { max: FrameCount, }, /// In the case that the platform provides no way of getting the default - /// buffersize before starting a stream. + /// buffer size before starting a stream. Unknown, } @@ -320,11 +415,11 @@ pub enum SupportedBufferSize { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SupportedStreamConfigRange { pub(crate) channels: ChannelCount, - /// Minimum value for the samples rate of the supported formats. + /// Minimum value for the sample rate of the supported formats. pub(crate) min_sample_rate: SampleRate, - /// Maximum value for the samples rate of the supported formats. + /// Maximum value for the sample rate of the supported formats. pub(crate) max_sample_rate: SampleRate, - /// Buffersize ranges supported by the device + /// Buffer size ranges supported by the device pub(crate) buffer_size: SupportedBufferSize, /// Type of data expected by the device. pub(crate) sample_format: SampleFormat, @@ -363,8 +458,7 @@ pub struct SupportedStreamConfig { /// A buffer of dynamically typed audio data, passed to raw stream callbacks. /// -/// Raw input stream callbacks receive `&Data`, while raw output stream callbacks expect `&mut -/// Data`. +/// Raw input stream callbacks receive `&Data`, while raw output stream callbacks expect `&mut Data`. #[cfg_attr(target_os = "emscripten", wasm_bindgen)] #[derive(Debug)] pub struct Data { @@ -379,8 +473,9 @@ pub struct Data { /// 2. The same time source used to generate timestamps for a stream's underlying audio data /// callback. /// -/// `StreamInstant` represents a duration since some unspecified origin occurring either before -/// or equal to the moment the stream from which it was created begins. +/// `StreamInstant` represents a duration since an unspecified origin point. The origin +/// is guaranteed to occur at or before the stream starts, and remains consistent for the +/// lifetime of that stream. Different streams may have different origins. /// /// ## Host `StreamInstant` Sources /// @@ -420,14 +515,14 @@ pub struct OutputStreamTimestamp { } /// Information relevant to a single call to the user's input stream data callback. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct InputCallbackInfo { timestamp: InputStreamTimestamp, } /// Information relevant to a single call to the user's output stream data callback. #[cfg_attr(target_os = "emscripten", wasm_bindgen)] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct OutputCallbackInfo { timestamp: OutputStreamTimestamp, } @@ -565,6 +660,8 @@ impl OutputCallbackInfo { } } +// Note: Data does not implement `is_empty()` because it always contains a valid audio buffer +// by design. The buffer may contain silence, but it is never structurally empty. #[allow(clippy::len_without_is_empty)] impl Data { /// Constructor for host implementations to use. @@ -721,7 +818,7 @@ impl SupportedStreamConfigRange { } } - /// Turns this [`SupportedStreamConfigRange`] into a [`SupportedStreamConfig`] corresponding to the maximum samples rate. + /// Turns this [`SupportedStreamConfigRange`] into a [`SupportedStreamConfig`] corresponding to the maximum sample rate. #[inline] pub fn with_max_sample_rate(self) -> SupportedStreamConfig { SupportedStreamConfig { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index baef83db2..bacf70da7 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -84,7 +84,7 @@ macro_rules! impl_platform_host { /// /// Only the hosts supported by the current platform are available as enum variants. /// For cross-platform code that needs to handle hosts from other platforms, - /// use the string representation via [`Display`]/[`FromStr`]. + /// use the string representation via [`std::fmt::Display`]/[`std::str::FromStr`]. /// /// # Available Host Strings /// @@ -104,24 +104,33 @@ macro_rules! impl_platform_host { /// /// # Cross-Platform Example /// - /// ```ignore - /// use cpal::{DeviceId, HostId}; + /// ``` + /// use cpal::HostId; + /// use std::str::FromStr; /// - /// fn handle_device(device_id: DeviceId) { + /// fn handle_host_string(host_string: &str) { /// // String matching works on all platforms - /// match device_id.0.to_string().as_str() { - /// "alsa" => println!("ALSA device"), - /// "coreaudio" => println!("CoreAudio device"), - /// "jack" => println!("JACK device"), - /// "wasapi" => println!("WASAPI device"), + /// match host_string { + /// "alsa" => println!("ALSA host"), + /// "coreaudio" => println!("CoreAudio host"), + /// "jack" => println!("JACK host"), + /// "wasapi" => println!("WASAPI host"), + /// "asio" => println!("ASIO host"), + /// "aaudio" => println!("AAudio host"), /// _ => println!("Other host"), /// } + /// + /// // Parse host string (may fail if host is not available on this platform) + /// if let Ok(host_id) = HostId::from_str(host_string) { + /// println!("Successfully parsed: {}", host_id); + /// } /// } /// ``` #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum HostId { $( $(#[cfg($feat)])? + $(#[cfg_attr(docsrs, doc(cfg($feat)))])? $HostVariant, )* } @@ -680,8 +689,29 @@ macro_rules! impl_platform_host { target_os = "netbsd" ))] mod platform_impl { + #[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd" + ))) + )] pub use crate::host::alsa::Host as AlsaHost; #[cfg(feature = "jack")] + #[cfg_attr( + docsrs, + doc(cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd" + ), + feature = "jack" + ))) + )] pub use crate::host::jack::Host as JackHost; impl_platform_host!( @@ -700,6 +730,7 @@ mod platform_impl { #[cfg(any(target_os = "macos", target_os = "ios"))] mod platform_impl { + #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))] pub use crate::host::coreaudio::Host as CoreAudioHost; impl_platform_host!( CoreAudio => CoreAudioHost, @@ -716,6 +747,7 @@ mod platform_impl { #[cfg(target_os = "emscripten")] mod platform_impl { + #[cfg_attr(docsrs, doc(cfg(target_os = "emscripten")))] pub use crate::host::emscripten::Host as EmscriptenHost; impl_platform_host!( Emscripten => EmscriptenHost, @@ -732,9 +764,21 @@ mod platform_impl { #[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] mod platform_impl { + #[cfg_attr( + docsrs, + doc(cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))) + )] pub use crate::host::webaudio::Host as WebAudioHost; #[cfg(feature = "audioworklet")] + #[cfg_attr( + docsrs, + doc(cfg(all( + target_arch = "wasm32", + feature = "wasm-bindgen", + feature = "audioworklet" + ))) + )] pub use crate::host::audioworklet::Host as AudioWorkletHost; impl_platform_host!( @@ -754,7 +798,9 @@ mod platform_impl { #[cfg(windows)] mod platform_impl { #[cfg(feature = "asio")] + #[cfg_attr(docsrs, doc(cfg(all(windows, feature = "asio"))))] pub use crate::host::asio::Host as AsioHost; + #[cfg_attr(docsrs, doc(cfg(windows)))] pub use crate::host::wasapi::Host as WasapiHost; impl_platform_host!( @@ -773,6 +819,7 @@ mod platform_impl { #[cfg(target_os = "android")] mod platform_impl { + #[cfg_attr(docsrs, doc(cfg(target_os = "android")))] pub use crate::host::aaudio::Host as AAudioHost; impl_platform_host!( AAudio => AAudioHost, @@ -800,6 +847,21 @@ mod platform_impl { all(target_arch = "wasm32", feature = "wasm-bindgen"), )))] mod platform_impl { + #[cfg_attr( + docsrs, + doc(cfg(not(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "emscripten", + target_os = "android", + all(target_arch = "wasm32", feature = "wasm-bindgen") + )))) + )] pub use crate::host::null::Host as NullHost; impl_platform_host!( diff --git a/src/samples_formats.rs b/src/samples_formats.rs index 4b19120d5..58dcc0d20 100644 --- a/src/samples_formats.rs +++ b/src/samples_formats.rs @@ -1,8 +1,40 @@ +//! Audio sample format types and conversions. +//! +//! # Byte Order +//! +//! All multi-byte sample formats use the native endianness of the target platform. +//! CPAL handles any necessary conversions when interfacing with hardware that uses +//! a different byte order. + use std::{fmt::Display, mem}; -#[cfg(target_os = "emscripten")] +#[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") +))] use wasm_bindgen::prelude::*; -pub use dasp_sample::{FromSample, Sample, I24, U24}; +pub use dasp_sample::{FromSample, Sample}; + +/// 24-bit signed integer sample type. +/// +/// Represents 24-bit audio with range `-(1 << 23)..=((1 << 23) - 1)`. +/// +/// **Note:** While representing 24-bit audio, this format uses 4 bytes (i32) of storage +/// with the most significant byte unused. Use [`SampleFormat::bits_per_sample`] to get +/// the actual bit depth (24) vs [`SampleFormat::sample_size`] for storage size (4 bytes). +pub use dasp_sample::I24; + +/// 24-bit unsigned integer sample type. +/// +/// Represents 24-bit audio with range `0..=((1 << 24) - 1)`, with origin at `1 << 23 == 8388608`. +/// +/// **Note:** While representing 24-bit audio, this format uses 4 bytes (u32) of storage +/// with the most significant byte unused. Use [`SampleFormat::bits_per_sample`] to get +/// the actual bit depth (24) vs [`SampleFormat::sample_size`] for storage size (4 bytes). +pub use dasp_sample::U24; + +// I48 and U48 are not currently supported by cpal but available in dasp_sample: +// pub use dasp_sample::{I48, U48}; /// Format that each sample has. Usually, this corresponds to the sampling /// depth of the audio source. For example, 16 bit quantized samples can be @@ -18,8 +50,14 @@ pub use dasp_sample::{FromSample, Sample, I24, U24}; /// music (WAV, MP3) as well as typical audio input devices on most platforms, /// /// [`is_float`]: SampleFormat::is_float -/// [`supported_input_configs`]: crate::Device::supported_input_configs -#[cfg_attr(target_os = "emscripten", wasm_bindgen)] +/// [`supported_input_configs`]: crate::traits::DeviceTrait::supported_input_configs +#[cfg_attr( + all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "wasm-bindgen") + ), + wasm_bindgen +)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum SampleFormat { @@ -29,7 +67,9 @@ pub enum SampleFormat { /// `i16` with a valid range of `i16::MIN..=i16::MAX` with `0` being the origin. I16, - /// `I24` with a valid range of '-(1 << 23)..(1 << 23)' with `0` being the origin + /// `I24` with a valid range of `-(1 << 23)..=((1 << 23) - 1)` with `0` being the origin. + /// + /// This format uses 4 bytes of storage but only 24 bits are significant. I24, /// `i32` with a valid range of `i32::MIN..=i32::MAX` with `0` being the origin. @@ -46,7 +86,9 @@ pub enum SampleFormat { /// `u16` with a valid range of `u16::MIN..=u16::MAX` with `1 << 15 == 32768` being the origin. U16, - /// `U24` with a valid range of '0..16777216' with `1 << 23 == 8388608` being the origin + /// `U24` with a valid range of `0..=((1 << 24) - 1)` with `1 << 23 == 8388608` being the origin. + /// + /// This format uses 4 bytes of storage but only 24 bits are significant. U24, /// `u32` with a valid range of `u32::MIN..=u32::MAX` with `1 << 31` being the origin. @@ -58,10 +100,10 @@ pub enum SampleFormat { /// `u64` with a valid range of `u64::MIN..=u64::MAX` with `1 << 63` being the origin. U64, - /// `f32` with a valid range of `-1.0..1.0` with `0.0` being the origin. + /// `f32` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F32, - /// `f64` with a valid range of `-1.0..1.0` with `0.0` being the origin. + /// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin. F64, } @@ -171,7 +213,21 @@ impl Display for SampleFormat { } } +/// A [`Sample`] type with a known corresponding [`SampleFormat`]. +/// +/// This trait is automatically implemented for all primitive sample types and provides +/// a way to determine the [`SampleFormat`] at compile time. +/// +/// # Example +/// +/// ``` +/// use cpal::SizedSample; +/// +/// assert_eq!(i16::FORMAT, cpal::SampleFormat::I16); +/// assert_eq!(f32::FORMAT, cpal::SampleFormat::F32); +/// ``` pub trait SizedSample: Sample { + /// The corresponding [`SampleFormat`] for this sample type. const FORMAT: SampleFormat; } diff --git a/src/traits.rs b/src/traits.rs index 57c90781e..6e682d59f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,9 @@ //! The suite of traits allowing CPAL to abstract over hosts, devices, event loops and stream IDs. +//! +//! # Custom Host Implementations +//! +//! When implementing custom hosts with the `custom` feature, use the [`assert_stream_send!`](crate::assert_stream_send) +//! and [`assert_stream_sync!`](crate::assert_stream_sync) macros to verify your `Stream` type meets CPAL's requirements. use std::time::Duration; @@ -44,7 +49,7 @@ pub trait HostTrait { /// Can be empty if the system does not support audio in general. fn devices(&self) -> Result; - /// Fetches a [`Device`](DeviceTrait) based on a [`DeviceId`](DeviceId) if available + /// Fetches a [`Device`](DeviceTrait) based on a [`DeviceId`] if available /// /// Returns `None` if no device matching the id is found fn device_by_id(&self, id: &DeviceId) -> Option { @@ -98,7 +103,9 @@ pub trait DeviceTrait { /// The human-readable name of the device. #[deprecated( since = "0.17.0", - note = "Use `id()` to get a unique identifier for the device, or `description().name()` for a human-readable description." + note = "Use `description()` for comprehensive device information including name, \ + manufacturer, and device type. Use `id()` for a unique, stable device identifier \ + that persists across reboots and reconnections." )] fn name(&self) -> Result { self.description().map(|desc| desc.name().to_string()) @@ -155,6 +162,15 @@ pub trait DeviceTrait { fn default_output_config(&self) -> Result; /// Create an input stream. + /// + /// # Parameters + /// + /// * `config` - The stream configuration including sample rate, channels, and buffer size. + /// * `data_callback` - Called periodically with captured audio data. The callback receives + /// a slice of samples in the format `T` and timing information. + /// * `error_callback` - Called when a stream error occurs (e.g., device disconnected). + /// * `timeout` - Optional timeout for backend operations. `None` indicates blocking behavior, + /// `Some(duration)` sets a maximum wait time. Not all backends support timeouts. fn build_input_stream( &self, config: &StreamConfig, @@ -183,6 +199,16 @@ pub trait DeviceTrait { } /// Create an output stream. + /// + /// # Parameters + /// + /// * `config` - The stream configuration including sample rate, channels, and buffer size. + /// * `data_callback` - Called periodically to fill the output buffer. The callback receives + /// a mutable slice of samples in the format `T` to be filled with audio data, along with + /// timing information. + /// * `error_callback` - Called when a stream error occurs (e.g., device disconnected). + /// * `timeout` - Optional timeout for backend operations. `None` indicates blocking behavior, + /// `Some(duration)` sets a maximum wait time. Not all backends support timeouts. fn build_output_stream( &self, config: &StreamConfig, @@ -211,6 +237,19 @@ pub trait DeviceTrait { } /// Create a dynamically typed input stream. + /// + /// This method allows working with sample data as raw bytes, useful when the sample + /// format is determined at runtime. For compile-time known formats, prefer + /// [`build_input_stream`](Self::build_input_stream). + /// + /// # Parameters + /// + /// * `config` - The stream configuration including sample rate, channels, and buffer size. + /// * `sample_format` - The sample format of the audio data. + /// * `data_callback` - Called periodically with captured audio data as a [`Data`] buffer. + /// * `error_callback` - Called when a stream error occurs (e.g., device disconnected). + /// * `timeout` - Optional timeout for backend operations. `None` indicates blocking behavior, + /// `Some(duration)` sets a maximum wait time. Not all backends support timeouts. fn build_input_stream_raw( &self, config: &StreamConfig, @@ -224,6 +263,20 @@ pub trait DeviceTrait { E: FnMut(StreamError) + Send + 'static; /// Create a dynamically typed output stream. + /// + /// This method allows working with sample data as raw bytes, useful when the sample + /// format is determined at runtime. For compile-time known formats, prefer + /// [`build_output_stream`](Self::build_output_stream). + /// + /// # Parameters + /// + /// * `config` - The stream configuration including sample rate, channels, and buffer size. + /// * `sample_format` - The sample format of the audio data. + /// * `data_callback` - Called periodically to fill the output buffer with audio data as + /// a mutable [`Data`] buffer. + /// * `error_callback` - Called when a stream error occurs (e.g., device disconnected). + /// * `timeout` - Optional timeout for backend operations. `None` indicates blocking behavior, + /// `Some(duration)` sets a maximum wait time. Not all backends support timeouts. fn build_output_stream_raw( &self, config: &StreamConfig, @@ -252,3 +305,43 @@ pub trait StreamTrait { /// fail in these cases. fn pause(&self) -> Result<(), PauseStreamError>; } + +/// Compile-time assertion that a stream type implements [`Send`]. +/// +/// Custom host implementations should use this macro to verify their `Stream` type +/// can be safely transferred between threads, as required by CPAL's API. +/// +/// # Example +/// +/// ``` +/// use cpal::assert_stream_send; +/// struct MyStream { /* ... */ } +/// assert_stream_send!(MyStream); +/// ``` +#[macro_export] +macro_rules! assert_stream_send { + ($t:ty) => { + const fn _assert_stream_send() {} + const _: () = _assert_stream_send::<$t>(); + }; +} + +/// Compile-time assertion that a stream type implements [`Sync`]. +/// +/// Custom host implementations should use this macro to verify their `Stream` type +/// can be safely shared between threads, as required by CPAL's API. +/// +/// # Example +/// +/// ``` +/// use cpal::assert_stream_sync; +/// struct MyStream { /* ... */ } +/// assert_stream_sync!(MyStream); +/// ``` +#[macro_export] +macro_rules! assert_stream_sync { + ($t:ty) => { + const fn _assert_stream_sync() {} + const _: () = _assert_stream_sync::<$t>(); + }; +}