chore: upgrade rodio 0.19 to 0.22, remove vendored rodio/cpal

Remove vendored rodio and cpal from src-tauri/vendor/ since rodio 0.22
now includes upstream f32 decoder support (our vendored modification).
Update all Cargo.toml files to use rodio 0.22 from crates.io and remove
the [patch.crates-io] section.
This commit is contained in:
vicrodh committed 2026-02-28 07:48:03 -06:00
1 parent 33e80b0836
commit faf8844584
122 files changed
+5 -22358

No files matched your search

+2 -4
View File
@@ -22,10 +22,8 @@ serde_json = { workspace = true }
# Error handling
thiserror = { workspace = true }
# Audio - Vendored rodio for bit-perfect playback
# This is the same vendored version used by qbz-nix
# CRITICAL: Must use symphonia-all feature for f32 pipeline
rodio = { path = "../../src-tauri/vendor/rodio", features = ["symphonia-all"] }
# Audio playback (f32 pipeline is now upstream in rodio 0.22)
rodio = { version = "0.22", features = ["symphonia-all"] }
# Audio analysis
symphonia = { workspace = true }
+2 -2
View File
@@ -31,8 +31,8 @@ rand = "0.8"
# Audio decoding
symphonia = { workspace = true }
# Vendored audio libraries (same as qbz-audio)
rodio = { path = "../../src-tauri/vendor/rodio", features = ["symphonia-all"] }
# Audio playback
rodio = { version = "0.22", features = ["symphonia-all"] }
# HTTP client for streaming
reqwest = { workspace = true }
+1 -4
View File
@@ -78,7 +78,7 @@ env_logger = "0.11"
rand = "0.8"
# Audio playback
rodio = { version = "0.19", features = ["symphonia-all"] }
rodio = { version = "0.22", features = ["symphonia-all"] }
symphonia = { version = "0.5", features = ["all"] }
# Audio visualization (FFT)
@@ -156,6 +156,3 @@ tauri-plugin-dialog = { version = "2.6.0" }
[dev-dependencies]
tempfile = "3"
[patch.crates-io]
cpal = { path = "vendor/cpal" }
rodio = { path = "vendor/rodio" }
-1
View File
@@ -1 +0,0 @@
{"v":1}
-6
View File
@@ -1,6 +0,0 @@
{
"git": {
"sha1": "ac6cbb2ba55e61665a35ab88ae136a83380d1354"
},
"path_in_vcs": ""
}
-265
View File
@@ -1,265 +0,0 @@
name: cpal
on: [push, pull_request]
jobs:
clippy-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update apt
run: sudo apt update
- name: Install alsa
run: sudo apt-get install libasound2-dev
- name: Install libjack
run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
target: armv7-linux-androideabi
- name: Run clippy
run: cargo clippy --all --all-features
- name: Run clippy for Android target
run: cargo clippy --all --features asio --features oboe/fetch-prebuilt --target armv7-linux-androideabi
rustfmt-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Run rustfmt
run: cargo fmt --all -- --check
cargo-publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install rust
uses: dtolnay/rust-toolchain@stable
- name: Update apt
run: sudo apt update
- name: Install alsa
run: sudo apt-get install libasound2-dev
- name: Verify publish crate
uses: katyo/publish-crates@v2
with:
dry-run: true
ignore-unpublished-changes: true
- name: Publish crate
uses: katyo/publish-crates@v2
with:
ignore-unpublished-changes: true
registry-token: ${{ secrets.CRATESIO_TOKEN }}
ubuntu-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update apt
run: sudo apt update
- name: Install alsa
run: sudo apt-get install libasound2-dev
- name: Install libjack
run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
- name: Install stable
uses: dtolnay/rust-toolchain@stable
- name: Run without features
run: cargo test --all --no-default-features --verbose
- name: Run all features
run: cargo test --all --all-features --verbose
linux-check-and-test-armv7:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
target: armv7-unknown-linux-gnueabihf
- name: Build image
run: docker build -t cross/cpal_armv7:v1 ./
- name: Install cross
run: cargo install cross
- name: Check without features for armv7
run: cross check --target armv7-unknown-linux-gnueabihf --workspace --no-default-features --verbose
- name: Test without features for armv7
run: cross test --target armv7-unknown-linux-gnueabihf --workspace --no-default-features --verbose
- name: Check all features for armv7
run: cross check --target armv7-unknown-linux-gnueabihf --workspace --all-features --verbose
- name: Test all features for armv7
run: cross test --target armv7-unknown-linux-gnueabihf --workspace --all-features --verbose
asmjs-wasm32-test:
strategy:
matrix:
target: [wasm32-unknown-emscripten]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Emscripten toolchain
uses: mymindstorm/setup-emsdk@v14
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Build beep example
run: cargo build --example beep --release --target ${{ matrix.target }}
wasm32-bindgen-test:
strategy:
matrix:
target: [wasm32-unknown-unknown]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Build beep example
run: cargo build --example beep --target ${{ matrix.target }} --features=wasm-bindgen
wasm32-wasi-test:
strategy:
matrix:
target: [wasm32-wasi]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.target }}
- name: Build beep example
run: cargo build --example beep --target ${{ matrix.target }}
windows-test:
strategy:
matrix:
version: [x86_64, i686]
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install ASIO SDK
env:
LINK: https://www.steinberg.net/asiosdk
run: |
curl -L -o asio.zip $env:LINK
7z x -oasio asio.zip
move asio\*\* asio\
- name: Install ASIO4ALL
run: choco install asio4all
- name: Install llvm and clang
run: choco install llvm
- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
target: ${{ matrix.version }}-pc-windows-msvc
- name: Run without features
run: cargo test --all --no-default-features --verbose
- name: Run all features
run: |
$Env:CPAL_ASIO_DIR = "$Env:GITHUB_WORKSPACE\asio"
cargo test --all --all-features --verbose
macos-test:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- name: Install llvm and clang
run: brew install llvm
- name: Install stable
uses: dtolnay/rust-toolchain@stable
- name: Build beep example
run: cargo build --example beep
- name: Run without features
run: cargo test --all --no-default-features --verbose
- name: Run all features
run: cargo test --all --all-features --verbose
android-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable (Android target)
uses: dtolnay/rust-toolchain@stable
with:
target: armv7-linux-androideabi
- name: Check android
run: cargo check --example android --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
- name: Check beep
run: cargo check --example beep --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
- name: Check enumerate
run: cargo check --example enumerate --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
- name: Check feedback
run: cargo check --example feedback --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
- name: Check record_wav
run: cargo check --example record_wav --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
android-apk-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable (Android targets)
uses: dtolnay/rust-toolchain@stable
with:
targets: armv7-linux-androideabi,aarch64-linux-android,i686-linux-android,x86_64-linux-android
- name: Set Up Android tools
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "platforms;android-30"
- name: Install Cargo APK
run: cargo install cargo-apk
- name: Build APK
run: cargo apk build --example android
ios-build:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- name: Install llvm and clang
run: brew install llvm
- name: Install stable (iOS targets)
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,x86_64-apple-ios
- name: Install cargo lipo
run: cargo install cargo-lipo
- name: Build iphonesimulator feedback example
run: cd examples/ios-feedback && xcodebuild -scheme cpal-ios-example -configuration Debug -derivedDataPath build -sdk iphonesimulator
wasm-beep-build:
# this only confirms that the Rust source builds
# and checks to prevent regressions like #721.
#
# It does not test the javascript/web integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable (wasm32 target)
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cargo Build
working-directory: ./examples/wasm-beep
run: cargo build --target wasm32-unknown-unknown
-6
View File
@@ -1,6 +0,0 @@
/target
/Cargo.lock
.cargo/
.DS_Store
recorded.wav
rls*.log
-196
View File
@@ -1,196 +0,0 @@
# Unreleased
# Version 0.15.3 (2024-03-04)
- Add `try_with_sample_rate`, a non-panicking variant of `with_sample_rate`.
- struct `platform::Stream` is now #[must_use].
- enum `SupportedBufferSize` and struct `SupportedStreamConfigRange` are now `Copy`.
- `platform::Device` is now `Clone`.
- Remove `parking_lot` dependency in favor of the std library.
- Fix crash on web/wasm when `atomics` flag is enabled.
- Improve Examples: Migrate wasm example to `trunk`, Improve syth-thones example.
- Improve CI: Update actions, Use Android 30 API level in CI, Remove `asmjs-unknown-emscripten` target.
- Update `windows` dependency to v0.54
- Update `jni` dependency to 0.21
- Update `alsa` dependency to 0.9
- Update `oboe` dependency to 0.6
- Update `ndk` dependency to 0.8 and disable `default-features`.
- Update `wasm-bindgen` to 0.2.89
# Version 0.15.2 (2023-03-30)
- webaudio: support multichannel output streams
- Update `windows` dependency
- wasapi: fix some thread panics
# Version 0.15.1 (2023-03-14)
- Add feature `oboe-shared-stdcxx` to enable `shared-stdcxx` on `oboe` for Android support
- Remove `thiserror` dependency
- Swith `mach` dependency to `mach2`
# Version 0.15.0 (2023-01-29)
- Update `windows-rs`, `jack`, `coreaudio-sys`, `oboe`, `alsa` dependencies
- Switch to the `dasp_sample` crate for the sample trait
- Switch to `web-sys` on the emscripten target
- Adopt edition 2021
- Add disconnection detection on Mac OS
# Version 0.14.1 (2022-10-23)
- Support the 0.6.1 release of `alsa-rs`
- Fix `asio` feature broken in 0.14.0
- `NetBSD` support
- CI improvements
# Version 0.14.0 (2022-08-22)
- Switch to `windows-rs` crate
- Turn `ndk-glue` into a dev-dependency and use `ndk-context` instead
- Update dependencies (ndk, ndk-glue, parking_lot, once_cell, jack)
# Version 0.13.5 (2022-01-28)
- Faster sample format conversion
- Update dependencies (ndk, oboe, ndk-glue, jack, alsa, nix)
# Version 0.13.4 (2021-08-08)
- wasapi: Allow both threading models and switch the default to STA
- Update dependencies (core-foundation-sys, jni, rust-jack)
- Alsa: improve stream setup parameters
# Version 0.13.3 (2021-03-29)
- Give each thread a unique name
- Fix distortion regression on some alsa configs
# Version 0.13.2 (2021-03-16)
- Update dependencies (ndk, nix, oboe, jni, etc)
# Version 0.13.1 (2020-11-08)
- Don't panic when device is plugged out on Windows
- Update `parking_lot` dependency
# Version 0.13.0 (2020-10-28)
- Add Android support via `oboe-rs`.
- Add Android APK build an CI job.
# Version 0.12.1 (2020-07-23)
- Bugfix release to get the asio feature working again.
# Version 0.12.0 (2020-07-09)
- Large refactor removing the blocking EventLoop API.
- Rename many `Format` types to `StreamConfig`:
- `Format` type's `data_type` field renamed to `sample_format`.
- `Shape` -> `StreamConfig` - The configuration input required to build a stream.
- `Format` -> `SupportedStreamConfig` - Describes a single supported stream configuration.
- `SupportedFormat` -> `SupportedStreamConfigRange` - Describes a range of supported configurations.
- `Device::default_input/output_format` -> `Device::default_input/output_config`.
- `Device::supported_input/output_formats` -> `Device::supported_input/output_configs`.
- `Device::SupportedInput/OutputFormats` -> `Device::SupportedInput/OutputConfigs`.
- `SupportedFormatsError` -> `SupportedStreamConfigsError`
- `DefaultFormatError` -> `DefaultStreamConfigError`
- `BuildStreamError::FormatNotSupported` -> `BuildStreamError::StreamConfigNotSupported`
- Address deprecated use of `mem::uninitialized` in WASAPI.
- Removed `UnknownTypeBuffer` in favour of specifying sample type.
- Added `build_input/output_stream_raw` methods allowing for dynamically
handling sample format type.
- Added support for DragonFly platform.
- Add `InputCallbackInfo` and `OutputCallbackInfo` types and update expected
user data callback function signature to provide these.
# Version 0.11.0 (2019-12-11)
- Fix some underruns that could occur in ALSA.
- Add name to `HostId`.
- Use `snd_pcm_hw_params_set_buffer_time_near` rather than `set_buffer_time_max`
in ALSA backend.
- Remove many uses of `std::mem::uninitialized`.
- Fix WASAPI capture logic.
- Panic on stream ID overflow rather than returning an error.
- Use `ringbuffer` crate in feedback example.
- Move errors into a separate module.
- Switch from `failure` to `thiserror` for error handling.
- Add `winbase` winapi feature to solve windows compile error issues.
- Lots of CI improvements.
# Version 0.10.0 (2019-07-05)
- core-foundation-sys and coreaudio-rs version bumps.
- Add an ASIO host, available under Windows.
- Introduce a new Host API, adding support for alternative audio APIs.
- Remove sleep loop on macOS in favour of using a `Condvar`.
- Allow users to handle stream callback errors with a new `StreamEvent` type.
- Overhaul error handling throughout the crate.
- Remove unnecessary Mutex from ALSA and WASAPI backends in favour of channels.
- Remove `panic!` from OutputBuffer Deref impl as it is no longer necessary.
# Version 0.9.0 (2019-06-06)
- Better buffer handling
- Fix logic error in frame/sample size
- Added error handling for unknown ALSA device errors
- Fix resuming a paused stream on Windows (wasapi).
- Implement `default_output_format` for emscripten backend.
# Version 0.8.1 (2018-03-18)
- Fix the handling of non-default sample rates for coreaudio input streams.
# Version 0.8.0 (2018-02-15)
- Add `record_wav.rs` example. Records 3 seconds to
`$CARGO_MANIFEST_DIR/recorded.wav` using default input device.
- Update `enumerate.rs` example to display default input/output devices and
formats.
- Add input stream support to coreaudio, alsa and windows backends.
- Introduce `StreamData` type for handling either input or output streams in
`EventLoop::run` callback.
- Add `Device::supported_{input/output}_formats` methods.
- Add `Device::default_{input/output}_format` methods.
- Add `default_{input/output}_device` functions.
- Replace usage of `Voice` with `Stream` throughout the crate.
- Remove `Endpoint` in favour of `Device` for supporting both input and output
streams.
# Version 0.7.0 (2018-02-04)
- Rename `ChannelsCount` to `ChannelCount`.
- Rename `SamplesRate` to `SampleRate`.
- Rename the `min_samples_rate` field of `SupportedFormat` to `min_sample_rate`
- Rename the `with_max_samples_rate()` method of`SupportedFormat` to `with_max_sample_rate()`
- Rename the `samples_rate` field of `Format` to `sample_rate`
- Changed the type of the `channels` field of the `SupportedFormat` struct from `Vec<ChannelPosition>` to `ChannelCount` (an alias to `u16`)
- Remove unused ChannelPosition API.
- Implement `Endpoint` and `Format` Enumeration for macOS.
- Implement format handling for macos `build_voice` method.
# Version 0.6.0 (2017-12-11)
- Changed the emscripten backend to consume less CPU.
- Added improvements to the crate documentation.
- Implement `pause` and `play` for ALSA backend.
- Reduced the number of allocations in the CoreAudio backend.
- Fixes for macOS build (#186, #189).
# Version 0.5.1 (2017-10-21)
- Added `Sample::to_i16()`, `Sample::to_u16()` and `Sample::from`.
# Version 0.5.0 (2017-10-21)
- Removed the dependency on the `futures` library.
- Removed the `Voice` and `SamplesStream` types.
- Added `EventLoop::build_voice`, `EventLoop::destroy_voice`, `EventLoop::play`,
and `EventLoop::pause` that can be used to create, destroy, play and pause voices.
- Added a `VoiceId` struct that is now used to identify a voice owned by an `EventLoop`.
- Changed `EventLoop::run()` to take a callback that is called whenever a voice requires sound data.
- Changed `supported_formats()` to produce a list of `SupportedFormat` instead of `Format`. A
`SupportedFormat` must then be turned into a `Format` in order to build a voice.
-184
View File
@@ -1,184 +0,0 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.70"
name = "cpal"
version = "0.15.3"
description = "Low-level cross-platform audio I/O library in pure Rust."
documentation = "https://docs.rs/cpal"
readme = "README.md"
keywords = [
"audio",
"sound",
]
license = "Apache-2.0"
repository = "https://github.com/rustaudio/cpal"
[[example]]
name = "android"
crate-type = ["cdylib"]
path = "examples/android.rs"
[[example]]
name = "beep"
[[example]]
name = "enumerate"
[[example]]
name = "feedback"
[[example]]
name = "record_wav"
[[example]]
name = "synth_tones"
[dependencies.dasp_sample]
version = "0.11"
[dev-dependencies.anyhow]
version = "1.0"
[dev-dependencies.clap]
version = "4.0"
features = ["derive"]
[dev-dependencies.hound]
version = "3.5"
[dev-dependencies.ringbuf]
version = "0.3"
[features]
asio = [
"asio-sys",
"num-traits",
]
oboe-shared-stdcxx = ["oboe/shared-stdcxx"]
[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.js-sys]
version = "0.3.35"
[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.wasm-bindgen]
version = "0.2.58"
optional = true
[target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.web-sys]
version = "0.3.35"
features = [
"AudioContext",
"AudioContextOptions",
"AudioBuffer",
"AudioBufferSourceNode",
"AudioNode",
"AudioDestinationNode",
"Window",
"AudioContextState",
]
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.alsa]
version = "0.9"
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.jack]
version = "0.11"
optional = true
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\"))".dependencies.libc]
version = "0.2"
[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation-sys]
version = "0.8.2"
[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.mach2]
version = "0.4"
[target."cfg(target_os = \"android\")".dependencies.jni]
version = "0.21"
[target."cfg(target_os = \"android\")".dependencies.ndk]
version = "0.8"
default-features = false
[target."cfg(target_os = \"android\")".dependencies.ndk-context]
version = "0.1"
[target."cfg(target_os = \"android\")".dependencies.oboe]
version = "0.6"
features = ["java-interface"]
[target."cfg(target_os = \"android\")".dev-dependencies.ndk-glue]
version = "0.7"
[target."cfg(target_os = \"emscripten\")".dependencies.js-sys]
version = "0.3.35"
[target."cfg(target_os = \"emscripten\")".dependencies.wasm-bindgen]
version = "0.2.89"
[target."cfg(target_os = \"emscripten\")".dependencies.wasm-bindgen-futures]
version = "0.4.33"
[target."cfg(target_os = \"emscripten\")".dependencies.web-sys]
version = "0.3.35"
features = [
"AudioContext",
"AudioContextOptions",
"AudioBuffer",
"AudioBufferSourceNode",
"AudioNode",
"AudioDestinationNode",
"Window",
"AudioContextState",
]
[target."cfg(target_os = \"ios\")".dependencies.coreaudio-rs]
version = "0.11"
features = [
"audio_unit",
"core_audio",
"audio_toolbox",
]
default-features = false
[target."cfg(target_os = \"macos\")".dependencies.coreaudio-rs]
version = "0.11"
features = [
"audio_unit",
"core_audio",
]
default-features = false
[target."cfg(target_os = \"windows\")".dependencies.asio-sys]
version = "0.2"
optional = true
[target."cfg(target_os = \"windows\")".dependencies.num-traits]
version = "0.2.6"
optional = true
[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.54.0"
features = [
"Win32_Media_Audio",
"Win32_Foundation",
"Win32_Devices_Properties",
"Win32_Media_KernelStreaming",
"Win32_System_Com_StructuredStorage",
"Win32_System_Threading",
"Win32_Security",
"Win32_System_SystemServices",
"Win32_System_Variant",
"Win32_Media_Multimedia",
"Win32_UI_Shell_PropertiesSystem",
]
-95
View File
@@ -1,95 +0,0 @@
[package]
name = "cpal"
version = "0.15.3"
description = "Low-level cross-platform audio I/O library in pure Rust."
repository = "https://github.com/rustaudio/cpal"
documentation = "https://docs.rs/cpal"
license = "Apache-2.0"
keywords = ["audio", "sound"]
edition = "2021"
rust-version = "1.70"
[features]
asio = ["asio-sys", "num-traits"] # Only available on Windows. See README for setup instructions.
oboe-shared-stdcxx = ["oboe/shared-stdcxx"] # Only available on Android. See README for what it does.
[dependencies]
dasp_sample = "0.11"
[dev-dependencies]
anyhow = "1.0"
hound = "3.5"
ringbuf = "0.3"
clap = { version = "4.0", features = ["derive"] }
[target.'cfg(target_os = "android")'.dev-dependencies]
ndk-glue = "0.7"
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.54.0", features = [
"Win32_Media_Audio",
"Win32_Foundation",
"Win32_Devices_Properties",
"Win32_Media_KernelStreaming",
"Win32_System_Com_StructuredStorage",
"Win32_System_Threading",
"Win32_Security",
"Win32_System_SystemServices",
"Win32_System_Variant",
"Win32_Media_Multimedia",
"Win32_UI_Shell_PropertiesSystem"
]}
asio-sys = { version = "0.2", path = "asio-sys", optional = true }
num-traits = { version = "0.2.6", optional = true }
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
alsa = "0.9"
libc = "0.2"
jack = { version = "0.11", optional = true }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
core-foundation-sys = "0.8.2" # For linking to CoreFoundation.framework and handling device name `CFString`s.
mach2 = "0.4" # For access to mach_timebase type.
[target.'cfg(target_os = "macos")'.dependencies]
coreaudio-rs = { version = "0.11", default-features = false, features = ["audio_unit", "core_audio"] }
[target.'cfg(target_os = "ios")'.dependencies]
coreaudio-rs = { version = "0.11", default-features = false, features = ["audio_unit", "core_audio", "audio_toolbox"] }
[target.'cfg(target_os = "emscripten")'.dependencies]
wasm-bindgen = { version = "0.2.89" }
wasm-bindgen-futures = "0.4.33"
js-sys = { version = "0.3.35" }
web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
wasm-bindgen = { version = "0.2.58", optional = true }
js-sys = { version = "0.3.35" }
web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }
[target.'cfg(target_os = "android")'.dependencies]
oboe = { version = "0.6", features = [ "java-interface" ] }
ndk = { version = "0.8", default-features = false }
ndk-context = "0.1"
jni = "0.21"
[[example]]
name = "android"
path = "examples/android.rs"
crate-type = ["cdylib"]
[[example]]
name = "beep"
[[example]]
name = "enumerate"
[[example]]
name = "feedback"
[[example]]
name = "record_wav"
[[example]]
name = "synth_tones"
-7
View File
@@ -1,7 +0,0 @@
[target.armv7-unknown-linux-gnueabihf]
image = "cross/cpal_armv7:v1"
[target.armv7-unknown-linux-gnueabihf.env]
passthrough = [
"RUSTFLAGS",
]
-9
View File
@@ -1,9 +0,0 @@
FROM rustembedded/cross:armv7-unknown-linux-gnueabihf
ENV PKG_CONFIG_ALLOW_CROSS 1
ENV PKG_CONFIG_PATH /usr/lib/arm-linux-gnueabihf/pkgconfig/
RUN dpkg --add-architecture armhf && \
apt-get update && \
apt-get install libasound2-dev:armhf -y && \
apt-get install libjack-jackd2-dev:armhf libjack-jackd2-0:armhf -y \
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-142
View File
@@ -1,142 +0,0 @@
# CPAL - Cross-Platform Audio Library
[![Actions Status](https://github.com/RustAudio/cpal/workflows/cpal/badge.svg)](https://github.com/RustAudio/cpal/actions)
[![Crates.io](https://img.shields.io/crates/v/cpal.svg)](https://crates.io/crates/cpal) [![docs.rs](https://docs.rs/cpal/badge.svg)](https://docs.rs/cpal/)
Low-level library for audio input and output in pure Rust.
This library currently supports the following:
- Enumerate supported audio hosts.
- Enumerate all available audio devices.
- Get the current default input and output devices.
- Enumerate known supported input and output stream formats for a device.
- Get the current default input and output stream formats for a device.
- Build and run input and output PCM streams on a chosen device with a given stream format.
Currently, supported hosts include:
- Linux (via ALSA or JACK)
- Windows (via WASAPI by default, see ASIO instructions below)
- macOS (via CoreAudio)
- iOS (via CoreAudio)
- Android (via Oboe)
- Emscripten
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
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.
## Feature flags for audio backends
Some audio backends are optional and will only be compiled with a [feature flag](https://doc.rust-lang.org/cargo/reference/features.html).
- JACK (on Linux): `jack`
- ASIO (on Windows): `asio`
Oboe can either use a shared or static runtime. The static runtime is used by default, but activating the
`oboe-shared-stdcxx` feature makes it use the shared runtime, which requires `libc++_shared.so` from the Android NDK to
be present during execution.
## ASIO on Windows
[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.
CPAL allows for using the ASIO SDK as the audio host on Windows instead of
WASAPI.
### Locating the ASIO SDK
The location of ASIO SDK is exposed to CPAL by setting the `CPAL_ASIO_DIR` environment variable.
The build script will try to find the ASIO SDK by following these steps in order:
1. Check if `CPAL_ASIO_DIR` is set and if so use the path to point to the SDK.
2. Check if the ASIO SDK is already installed in the temporary directory, if so use that and set the path of `CPAL_ASIO_DIR` to the output of `std::env::temp_dir().join("asio_sdk")`.
3. If the ASIO SDK is not already installed, download it from <https://www.steinberg.net/asiosdk> and install it in the temporary directory. The path of `CPAL_ASIO_DIR` will be set to the output of `std::env::temp_dir().join("asio_sdk")`.
In an ideal situation you don't need to worry about this step.
### Preparing the build environment
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:
```
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:
```
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
```
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).
5. Select the ASIO host at the start of our program with the following code:
```rust
let host;
#[cfg(target_os = "windows")]
{
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`.
6. Make sure to enable the `asio` feature when building CPAL:
```
cargo build --features "asio"
```
or if you are using CPAL as a dependency in a downstream project, enable the
feature like this:
```toml
cpal = { version = "*", features = ["asio"] }
```
_Updated as of ASIO version 2.3.3._
### Cross compilation
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)
It is also possible to compile Windows applications with ASIO support on Linux and macOS.
For both platforms the common way to do this is to use the [MinGW-w64](https://www.mingw-w64.org/) toolchain.
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.
Example for macOS for the target of `x86_64-pc-windows-gnu` where `mingw-w64` is 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"
```
-14
View File
@@ -1,14 +0,0 @@
use std::env;
const CPAL_ASIO_DIR: &str = "CPAL_ASIO_DIR";
fn main() {
println!("cargo:rerun-if-env-changed={}", CPAL_ASIO_DIR);
// If ASIO directory isn't set silently return early
// otherwise set the asio config flag
match env::var(CPAL_ASIO_DIR) {
Err(_) => {}
Ok(_) => println!("cargo:rustc-cfg=asio"),
};
}
-82
View File
@@ -1,82 +0,0 @@
#![allow(dead_code)]
extern crate anyhow;
extern crate cpal;
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
SizedSample,
};
use cpal::{FromSample, Sample};
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "full"))]
fn main() {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("failed to find output device");
let config = device.default_output_config().unwrap();
match config.sample_format() {
cpal::SampleFormat::I8 => run::<i8>(&device, &config.into()).unwrap(),
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()).unwrap(),
// cpal::SampleFormat::I24 => run::<I24>(&device, &config.into()).unwrap(),
cpal::SampleFormat::I32 => run::<i32>(&device, &config.into()).unwrap(),
// cpal::SampleFormat::I48 => run::<I48>(&device, &config.into()).unwrap(),
cpal::SampleFormat::I64 => run::<i64>(&device, &config.into()).unwrap(),
cpal::SampleFormat::U8 => run::<u8>(&device, &config.into()).unwrap(),
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()).unwrap(),
// cpal::SampleFormat::U24 => run::<U24>(&device, &config.into()).unwrap(),
cpal::SampleFormat::U32 => run::<u32>(&device, &config.into()).unwrap(),
// cpal::SampleFormat::U48 => run::<U48>(&device, &config.into()).unwrap(),
cpal::SampleFormat::U64 => run::<u64>(&device, &config.into()).unwrap(),
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()).unwrap(),
cpal::SampleFormat::F64 => run::<f64>(&device, &config.into()).unwrap(),
sample_format => panic!("Unsupported sample format '{sample_format}'"),
}
}
fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
where
T: SizedSample + FromSample<f32>,
{
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
// Produce a sinusoid of maximum amplitude.
let mut sample_clock = 0f32;
let mut next_value = move || {
sample_clock = (sample_clock + 1.0) % sample_rate;
(sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
};
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
write_data(data, channels, &mut next_value)
},
err_fn,
None,
)?;
stream.play()?;
std::thread::sleep(std::time::Duration::from_millis(1000));
Ok(())
}
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
where
T: Sample + FromSample<f32>,
{
for frame in output.chunks_mut(channels) {
let value: T = T::from_sample(next_sample());
for sample in frame.iter_mut() {
*sample = value;
}
}
}
-138
View File
@@ -1,138 +0,0 @@
use clap::Parser;
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
FromSample, Sample, SizedSample,
};
#[derive(Parser, Debug)]
#[command(version, about = "CPAL beep example", long_about = None)]
struct Opt {
/// The audio device to use
#[arg(short, long, default_value_t = String::from("default"))]
device: String,
/// Use the JACK host
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
#[arg(short, long)]
#[allow(dead_code)]
jack: bool,
}
fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
// Conditionally compile with jack if the feature is specified.
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
cpal::host_from_id(cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect(
"make sure --features jack is specified. only works on OSes where jack is available",
)).expect("jack host unavailable")
} else {
cpal::default_host()
};
#[cfg(any(
not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
)),
not(feature = "jack")
))]
let host = cpal::default_host();
let device = if opt.device == "default" {
host.default_output_device()
} else {
host.output_devices()?
.find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
}
.expect("failed to find output device");
println!("Output device: {}", device.name()?);
let config = device.default_output_config().unwrap();
println!("Default output config: {:?}", config);
match config.sample_format() {
cpal::SampleFormat::I8 => run::<i8>(&device, &config.into()),
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()),
// cpal::SampleFormat::I24 => run::<I24>(&device, &config.into()),
cpal::SampleFormat::I32 => run::<i32>(&device, &config.into()),
// cpal::SampleFormat::I48 => run::<I48>(&device, &config.into()),
cpal::SampleFormat::I64 => run::<i64>(&device, &config.into()),
cpal::SampleFormat::U8 => run::<u8>(&device, &config.into()),
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()),
// cpal::SampleFormat::U24 => run::<U24>(&device, &config.into()),
cpal::SampleFormat::U32 => run::<u32>(&device, &config.into()),
// cpal::SampleFormat::U48 => run::<U48>(&device, &config.into()),
cpal::SampleFormat::U64 => run::<u64>(&device, &config.into()),
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()),
cpal::SampleFormat::F64 => run::<f64>(&device, &config.into()),
sample_format => panic!("Unsupported sample format '{sample_format}'"),
}
}
pub fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
where
T: SizedSample + FromSample<f32>,
{
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
// Produce a sinusoid of maximum amplitude.
let mut sample_clock = 0f32;
let mut next_value = move || {
sample_clock = (sample_clock + 1.0) % sample_rate;
(sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
};
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
write_data(data, channels, &mut next_value)
},
err_fn,
None,
)?;
stream.play()?;
std::thread::sleep(std::time::Duration::from_millis(1000));
Ok(())
}
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
where
T: Sample + FromSample<f32>,
{
for frame in output.chunks_mut(channels) {
let value: T = T::from_sample(next_sample());
for sample in frame.iter_mut() {
*sample = value;
}
}
}
-74
View File
@@ -1,74 +0,0 @@
extern crate anyhow;
extern crate cpal;
use cpal::traits::{DeviceTrait, HostTrait};
fn main() -> Result<(), anyhow::Error> {
println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS);
let available_hosts = cpal::available_hosts();
println!("Available hosts:\n {:?}", available_hosts);
for host_id in available_hosts {
println!("{}", host_id.name());
let host = cpal::host_from_id(host_id)?;
let default_in = host.default_input_device().map(|e| e.name().unwrap());
let default_out = host.default_output_device().map(|e| e.name().unwrap());
println!(" Default Input Device:\n {:?}", default_in);
println!(" Default Output Device:\n {:?}", default_out);
let devices = host.devices()?;
println!(" Devices: ");
for (device_index, device) in devices.enumerate() {
println!(" {}. \"{}\"", device_index + 1, device.name()?);
// Input configs
if let Ok(conf) = device.default_input_config() {
println!(" Default input stream config:\n {:?}", conf);
}
let input_configs = match device.supported_input_configs() {
Ok(f) => f.collect(),
Err(e) => {
println!(" Error getting supported input configs: {:?}", e);
Vec::new()
}
};
if !input_configs.is_empty() {
println!(" All supported input stream configs:");
for (config_index, config) in input_configs.into_iter().enumerate() {
println!(
" {}.{}. {:?}",
device_index + 1,
config_index + 1,
config
);
}
}
// Output configs
if let Ok(conf) = device.default_output_config() {
println!(" Default output stream config:\n {:?}", conf);
}
let output_configs = match device.supported_output_configs() {
Ok(f) => f.collect(),
Err(e) => {
println!(" Error getting supported output configs: {:?}", e);
Vec::new()
}
};
if !output_configs.is_empty() {
println!(" All supported output stream configs:");
for (config_index, config) in output_configs.into_iter().enumerate() {
println!(
" {}.{}. {:?}",
device_index + 1,
config_index + 1,
config
);
}
}
}
}
Ok(())
}
-174
View File
@@ -1,174 +0,0 @@
//! Feeds back the input stream directly into the output stream.
//!
//! Assumes that the input and output devices can use the same stream configuration and that they
//! support the f32 sample format.
//!
//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
//! precisely synchronised.
use clap::Parser;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::HeapRb;
#[derive(Parser, Debug)]
#[command(version, about = "CPAL feedback example", long_about = None)]
struct Opt {
/// The input audio device to use
#[arg(short, long, value_name = "IN", default_value_t = String::from("default"))]
input_device: String,
/// The output audio device to use
#[arg(short, long, value_name = "OUT", default_value_t = String::from("default"))]
output_device: String,
/// Specify the delay between input and output
#[arg(short, long, value_name = "DELAY_MS", default_value_t = 150.0)]
latency: f32,
/// Use the JACK host
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
#[arg(short, long)]
#[allow(dead_code)]
jack: bool,
}
fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
// Conditionally compile with jack if the feature is specified.
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
cpal::host_from_id(cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect(
"make sure --features jack is specified. only works on OSes where jack is available",
)).expect("jack host unavailable")
} else {
cpal::default_host()
};
#[cfg(any(
not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
)),
not(feature = "jack")
))]
let host = cpal::default_host();
// Find devices.
let input_device = if opt.input_device == "default" {
host.default_input_device()
} else {
host.input_devices()?
.find(|x| x.name().map(|y| y == opt.input_device).unwrap_or(false))
}
.expect("failed to find input device");
let output_device = if opt.output_device == "default" {
host.default_output_device()
} else {
host.output_devices()?
.find(|x| x.name().map(|y| y == opt.output_device).unwrap_or(false))
}
.expect("failed to find output device");
println!("Using input device: \"{}\"", input_device.name()?);
println!("Using output device: \"{}\"", output_device.name()?);
// We'll try and use the same configuration between streams to keep it simple.
let config: cpal::StreamConfig = input_device.default_input_config()?.into();
// Create a delay in case the input and output devices aren't synced.
let latency_frames = (opt.latency / 1_000.0) * config.sample_rate.0 as f32;
let latency_samples = latency_frames as usize * config.channels as usize;
// The buffer to share samples
let ring = HeapRb::<f32>::new(latency_samples * 2);
let (mut producer, mut consumer) = ring.split();
// Fill the samples with 0.0 equal to the length of the delay.
for _ in 0..latency_samples {
// The ring buffer has twice as much space as necessary to add latency here,
// so this should never fail
producer.push(0.0).unwrap();
}
let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
let mut output_fell_behind = false;
for &sample in data {
if producer.push(sample).is_err() {
output_fell_behind = true;
}
}
if output_fell_behind {
eprintln!("output stream fell behind: try increasing latency");
}
};
let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let mut input_fell_behind = false;
for sample in data {
*sample = match consumer.pop() {
Some(s) => s,
None => {
input_fell_behind = true;
0.0
}
};
}
if input_fell_behind {
eprintln!("input stream fell behind: try increasing latency");
}
};
// Build streams.
println!(
"Attempting to build both streams with f32 samples and `{:?}`.",
config
);
let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
println!("Successfully built streams.");
// Play the streams.
println!(
"Starting the input and output streams with `{}` milliseconds of latency.",
opt.latency
);
input_stream.play()?;
output_stream.play()?;
// Run for 3 seconds before closing.
println!("Playing for 3 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(3));
drop(input_stream);
drop(output_stream);
println!("Done!");
Ok(())
}
fn err_fn(err: cpal::StreamError) {
eprintln!("an error occurred on stream: {}", err);
}
-177
View File
@@ -1,177 +0,0 @@
//! Records a WAV file (roughly 3 seconds long) using the default input device and config.
//!
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
use clap::Parser;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{FromSample, Sample};
use std::fs::File;
use std::io::BufWriter;
use std::sync::{Arc, Mutex};
#[derive(Parser, Debug)]
#[command(version, about = "CPAL record_wav example", long_about = None)]
struct Opt {
/// The audio device to use
#[arg(short, long, default_value_t = String::from("default"))]
device: String,
/// Use the JACK host
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
#[arg(short, long)]
#[allow(dead_code)]
jack: bool,
}
fn main() -> Result<(), anyhow::Error> {
let opt = Opt::parse();
// Conditionally compile with jack if the feature is specified.
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
cpal::host_from_id(cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect(
"make sure --features jack is specified. only works on OSes where jack is available",
)).expect("jack host unavailable")
} else {
cpal::default_host()
};
#[cfg(any(
not(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
)),
not(feature = "jack")
))]
let host = cpal::default_host();
// Set up the input device and stream with the default input config.
let device = if opt.device == "default" {
host.default_input_device()
} else {
host.input_devices()?
.find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
}
.expect("failed to find input device");
println!("Input device: {}", device.name()?);
let config = device
.default_input_config()
.expect("Failed to get default input config");
println!("Default input config: {:?}", config);
// The WAV file we're recording to.
const PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
let spec = wav_spec_from_config(&config);
let writer = hound::WavWriter::create(PATH, spec)?;
let writer = Arc::new(Mutex::new(Some(writer)));
// A flag to indicate that recording is in progress.
println!("Begin recording...");
// Run the input stream on a separate thread.
let writer_2 = writer.clone();
let err_fn = move |err| {
eprintln!("an error occurred on stream: {}", err);
};
let stream = match config.sample_format() {
cpal::SampleFormat::I8 => device.build_input_stream(
&config.into(),
move |data, _: &_| write_input_data::<i8, i8>(data, &writer_2),
err_fn,
None,
)?,
cpal::SampleFormat::I16 => device.build_input_stream(
&config.into(),
move |data, _: &_| write_input_data::<i16, i16>(data, &writer_2),
err_fn,
None,
)?,
cpal::SampleFormat::I32 => device.build_input_stream(
&config.into(),
move |data, _: &_| write_input_data::<i32, i32>(data, &writer_2),
err_fn,
None,
)?,
cpal::SampleFormat::F32 => device.build_input_stream(
&config.into(),
move |data, _: &_| write_input_data::<f32, f32>(data, &writer_2),
err_fn,
None,
)?,
sample_format => {
return Err(anyhow::Error::msg(format!(
"Unsupported sample format '{sample_format}'"
)))
}
};
stream.play()?;
// Let recording go for roughly three seconds.
std::thread::sleep(std::time::Duration::from_secs(3));
drop(stream);
writer.lock().unwrap().take().unwrap().finalize()?;
println!("Recording {} complete!", PATH);
Ok(())
}
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
if format.is_float() {
hound::SampleFormat::Float
} else {
hound::SampleFormat::Int
}
}
fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec {
hound::WavSpec {
channels: config.channels() as _,
sample_rate: config.sample_rate().0 as _,
bits_per_sample: (config.sample_format().sample_size() * 8) as _,
sample_format: sample_format(config.sample_format()),
}
}
type WavWriterHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>;
fn write_input_data<T, U>(input: &[T], writer: &WavWriterHandle)
where
T: Sample,
U: Sample + hound::Sample + FromSample<T>,
{
if let Ok(mut guard) = writer.try_lock() {
if let Some(writer) = guard.as_mut() {
for &sample in input.iter() {
let sample: U = U::from_sample(sample);
writer.write_sample(sample).ok();
}
}
}
}
-191
View File
@@ -1,191 +0,0 @@
/* This example expose parameter to pass generator of sample.
Good starting point for integration of cpal into your application.
*/
extern crate anyhow;
extern crate clap;
extern crate cpal;
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
SizedSample,
};
use cpal::{FromSample, Sample};
fn main() -> anyhow::Result<()> {
let stream = stream_setup_for()?;
stream.play()?;
std::thread::sleep(std::time::Duration::from_millis(4000));
Ok(())
}
pub enum Waveform {
Sine,
Square,
Saw,
Triangle,
}
pub struct Oscillator {
pub sample_rate: f32,
pub waveform: Waveform,
pub current_sample_index: f32,
pub frequency_hz: f32,
}
impl Oscillator {
fn advance_sample(&mut self) {
self.current_sample_index = (self.current_sample_index + 1.0) % self.sample_rate;
}
fn set_waveform(&mut self, waveform: Waveform) {
self.waveform = waveform;
}
fn calculate_sine_output_from_freq(&self, freq: f32) -> f32 {
let two_pi = 2.0 * std::f32::consts::PI;
(self.current_sample_index * freq * two_pi / self.sample_rate).sin()
}
fn is_multiple_of_freq_above_nyquist(&self, multiple: f32) -> bool {
self.frequency_hz * multiple > self.sample_rate / 2.0
}
fn sine_wave(&mut self) -> f32 {
self.advance_sample();
self.calculate_sine_output_from_freq(self.frequency_hz)
}
fn generative_waveform(&mut self, harmonic_index_increment: i32, gain_exponent: f32) -> f32 {
self.advance_sample();
let mut output = 0.0;
let mut i = 1;
while !self.is_multiple_of_freq_above_nyquist(i as f32) {
let gain = 1.0 / (i as f32).powf(gain_exponent);
output += gain * self.calculate_sine_output_from_freq(self.frequency_hz * i as f32);
i += harmonic_index_increment;
}
output
}
fn square_wave(&mut self) -> f32 {
self.generative_waveform(2, 1.0)
}
fn saw_wave(&mut self) -> f32 {
self.generative_waveform(1, 1.0)
}
fn triangle_wave(&mut self) -> f32 {
self.generative_waveform(2, 2.0)
}
fn tick(&mut self) -> f32 {
match self.waveform {
Waveform::Sine => self.sine_wave(),
Waveform::Square => self.square_wave(),
Waveform::Saw => self.saw_wave(),
Waveform::Triangle => self.triangle_wave(),
}
}
}
pub fn stream_setup_for() -> Result<cpal::Stream, anyhow::Error>
where
{
let (_host, device, config) = host_device_setup()?;
match config.sample_format() {
cpal::SampleFormat::I8 => make_stream::<i8>(&device, &config.into()),
cpal::SampleFormat::I16 => make_stream::<i16>(&device, &config.into()),
cpal::SampleFormat::I32 => make_stream::<i32>(&device, &config.into()),
cpal::SampleFormat::I64 => make_stream::<i64>(&device, &config.into()),
cpal::SampleFormat::U8 => make_stream::<u8>(&device, &config.into()),
cpal::SampleFormat::U16 => make_stream::<u16>(&device, &config.into()),
cpal::SampleFormat::U32 => make_stream::<u32>(&device, &config.into()),
cpal::SampleFormat::U64 => make_stream::<u64>(&device, &config.into()),
cpal::SampleFormat::F32 => make_stream::<f32>(&device, &config.into()),
cpal::SampleFormat::F64 => make_stream::<f64>(&device, &config.into()),
sample_format => Err(anyhow::Error::msg(format!(
"Unsupported sample format '{sample_format}'"
))),
}
}
pub fn host_device_setup(
) -> Result<(cpal::Host, cpal::Device, cpal::SupportedStreamConfig), anyhow::Error> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| anyhow::Error::msg("Default output device is not available"))?;
println!("Output device : {}", device.name()?);
let config = device.default_output_config()?;
println!("Default output config : {:?}", config);
Ok((host, device, config))
}
pub fn make_stream<T>(
device: &cpal::Device,
config: &cpal::StreamConfig,
) -> Result<cpal::Stream, anyhow::Error>
where
T: SizedSample + FromSample<f32>,
{
let num_channels = config.channels as usize;
let mut oscillator = Oscillator {
waveform: Waveform::Sine,
sample_rate: config.sample_rate.0 as f32,
current_sample_index: 0.0,
frequency_hz: 440.0,
};
let err_fn = |err| eprintln!("Error building output sound stream: {}", err);
let time_at_start = std::time::Instant::now();
println!("Time at start: {:?}", time_at_start);
let stream = device.build_output_stream(
config,
move |output: &mut [T], _: &cpal::OutputCallbackInfo| {
// for 0-1s play sine, 1-2s play square, 2-3s play saw, 3-4s play triangle_wave
let time_since_start = std::time::Instant::now()
.duration_since(time_at_start)
.as_secs_f32();
if time_since_start < 1.0 {
oscillator.set_waveform(Waveform::Sine);
} else if time_since_start < 2.0 {
oscillator.set_waveform(Waveform::Triangle);
} else if time_since_start < 3.0 {
oscillator.set_waveform(Waveform::Square);
} else if time_since_start < 4.0 {
oscillator.set_waveform(Waveform::Saw);
} else {
oscillator.set_waveform(Waveform::Sine);
}
process_frame(output, &mut oscillator, num_channels)
},
err_fn,
None,
)?;
Ok(stream)
}
fn process_frame<SampleType>(
output: &mut [SampleType],
oscillator: &mut Oscillator,
num_channels: usize,
) where
SampleType: Sample + FromSample<f32>,
{
for frame in output.chunks_mut(num_channels) {
let value: SampleType = SampleType::from_sample(oscillator.tick());
// copy the same value to all channels
for sample in frame.iter_mut() {
*sample = value;
}
}
}
-292
View File
@@ -1,292 +0,0 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
/// The requested host, although supported on this platform, is unavailable.
#[derive(Copy, Clone, Debug)]
pub struct HostUnavailable;
impl Display for HostUnavailable {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("the requested host is unavailable")
}
}
impl Error for HostUnavailable {}
/// Some error has occurred that is specific to the backend from which it was produced.
///
/// This error is often used as a catch-all in cases where:
///
/// - It is unclear exactly what error might be produced by the backend API.
/// - It does not make sense to add a variant to the enclosing error type.
/// - No error was expected to occur at all, but we return an error to avoid the possibility of a
/// `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.
#[derive(Clone, Debug)]
pub struct BackendSpecificError {
pub description: String,
}
impl Display for BackendSpecificError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"A backend-specific error has occurred: {}",
self.description
)
}
}
impl Error for BackendSpecificError {}
/// An error that might occur while attempting to enumerate the available devices on a system.
#[derive(Clone, Debug)]
pub enum DevicesError {
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for DevicesError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
}
}
}
impl Error for DevicesError {}
impl From<BackendSpecificError> for DevicesError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// An error that may occur while attempting to retrieve a device name.
#[derive(Clone, Debug)]
pub enum DeviceNameError {
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for DeviceNameError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
}
}
}
impl Error for DeviceNameError {}
impl From<BackendSpecificError> for DeviceNameError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// Error that can happen when enumerating the list of supported formats.
#[derive(Debug)]
pub enum SupportedStreamConfigsError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
DeviceNotAvailable,
/// We called something the C-Layer did not understand
InvalidArgument,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for SupportedStreamConfigsError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
Self::DeviceNotAvailable => f.write_str("The requested device is no longer available. For example, it has been unplugged."),
Self::InvalidArgument => f.write_str("Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")
}
}
}
impl Error for SupportedStreamConfigsError {}
impl From<BackendSpecificError> for SupportedStreamConfigsError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// May occur when attempting to request the default input or output stream format from a [`Device`](crate::Device).
#[derive(Debug)]
pub enum DefaultStreamConfigError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
DeviceNotAvailable,
/// Returned if e.g. the default input format was requested on an output-only audio device.
StreamTypeNotSupported,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for DefaultStreamConfigError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
DefaultStreamConfigError::DeviceNotAvailable => f.write_str(
"The requested device is no longer available. For example, it has been unplugged.",
),
DefaultStreamConfigError::StreamTypeNotSupported => {
f.write_str("The requested stream type is not supported by the device.")
}
}
}
}
impl Error for DefaultStreamConfigError {}
impl From<BackendSpecificError> for DefaultStreamConfigError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// Error that can happen when creating a [`Stream`](crate::Stream).
#[derive(Debug)]
pub enum BuildStreamError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
DeviceNotAvailable,
/// The specified stream configuration is not supported.
StreamConfigNotSupported,
/// We called something the C-Layer did not understand
///
/// On ALSA device functions called with a feature they do not support will yield this. E.g.
/// Trying to use capture capabilities on an output only format yields this.
InvalidArgument,
/// Occurs if adding a new Stream ID would cause an integer overflow.
StreamIdOverflow,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for BuildStreamError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
BuildStreamError::DeviceNotAvailable => f.write_str(
"The requested device is no longer available. For example, it has been unplugged.",
),
BuildStreamError::StreamConfigNotSupported => {
f.write_str("The requested stream configuration is not supported by the device.")
}
BuildStreamError::InvalidArgument => f.write_str(
"The requested device does not support this capability (invalid argument)",
),
BuildStreamError::StreamIdOverflow => {
f.write_str("Adding a new stream ID would cause an overflow")
}
}
}
}
impl Error for BuildStreamError {}
impl From<BackendSpecificError> for BuildStreamError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// Errors that might occur when calling [`Stream::play()`](crate::traits::StreamTrait::play).
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug)]
pub enum PlayStreamError {
/// The device associated with the stream is no longer available.
DeviceNotAvailable,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for PlayStreamError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
PlayStreamError::DeviceNotAvailable => {
f.write_str("the device associated with the stream is no longer available")
}
}
}
}
impl Error for PlayStreamError {}
impl From<BackendSpecificError> for PlayStreamError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// Errors that might occur when calling [`Stream::pause()`](crate::traits::StreamTrait::pause).
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug)]
pub enum PauseStreamError {
/// The device associated with the stream is no longer available.
DeviceNotAvailable,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for PauseStreamError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
PauseStreamError::DeviceNotAvailable => {
f.write_str("the device associated with the stream is no longer available")
}
}
}
}
impl Error for PauseStreamError {}
impl From<BackendSpecificError> for PauseStreamError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
/// Errors that might occur while a stream is running.
#[derive(Debug)]
pub enum StreamError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
DeviceNotAvailable,
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific { err: BackendSpecificError },
}
impl Display for StreamError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
StreamError::DeviceNotAvailable => f.write_str(
"The requested device is no longer available. For example, it has been unplugged.",
),
}
}
}
impl Error for StreamError {}
impl From<BackendSpecificError> for StreamError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}
-70
View File
@@ -1,70 +0,0 @@
use super::alsa;
use super::{Device, DeviceHandles};
use crate::{BackendSpecificError, DevicesError};
use std::sync::{Arc, Mutex};
/// ALSA's implementation for `Devices`.
pub struct Devices {
hint_iter: alsa::device_name::HintIter,
}
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices {
hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
})
}
}
unsafe impl Send for Devices {}
unsafe impl Sync for Devices {}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Device> {
loop {
match self.hint_iter.next() {
None => return None,
Some(hint) => {
let name = match hint.name {
None => continue,
// Ignoring the `null` device.
Some(name) if name == "null" => continue,
Some(name) => name,
};
if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name,
handles: Arc::new(Mutex::new(handles)),
});
}
}
}
}
}
}
#[inline]
pub fn default_input_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
#[inline]
pub fn default_output_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
impl From<alsa::Error> for DevicesError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}
File diff suppressed because it is too large. Load diff
-232
View File
@@ -1,232 +0,0 @@
pub type SupportedInputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
use super::sys;
use crate::BackendSpecificError;
use crate::DefaultStreamConfigError;
use crate::DeviceNameError;
use crate::DevicesError;
use crate::SampleFormat;
use crate::SampleRate;
use crate::SupportedBufferSize;
use crate::SupportedStreamConfig;
use crate::SupportedStreamConfigRange;
use crate::SupportedStreamConfigsError;
use std::hash::{Hash, Hasher};
use std::sync::atomic::AtomicI32;
use std::sync::{Arc, Mutex};
/// A ASIO Device
#[derive(Clone)]
pub struct Device {
/// The driver represented by this device.
pub driver: Arc<sys::Driver>,
// Input and/or Output stream.
// A driver can only have one of each.
// They need to be created at the same time.
pub asio_streams: Arc<Mutex<sys::AsioStreams>>,
pub current_buffer_index: Arc<AtomicI32>,
}
/// All available devices.
pub struct Devices {
asio: Arc<sys::Asio>,
drivers: std::vec::IntoIter<String>,
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.driver.name() == other.driver.name()
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
self.driver.name().hash(state);
}
}
impl Device {
pub fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.driver.name().to_string())
}
/// Gets the supported input configs.
/// TODO currently only supports the default.
/// Need to find all possible configs.
pub fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
// Retrieve the default config for the total supported channels and supported sample
// format.
let f = match self.default_input_config() {
Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable),
Ok(f) => f,
};
// Collect a config for every combination of supported sample rate and number of channels.
let mut supported_configs = vec![];
for &rate in crate::COMMON_SAMPLE_RATES {
if !self
.driver
.can_sample_rate(rate.0.into())
.ok()
.unwrap_or(false)
{
continue;
}
for channels in 1..f.channels + 1 {
supported_configs.push(SupportedStreamConfigRange {
channels,
min_sample_rate: rate,
max_sample_rate: rate,
buffer_size: f.buffer_size,
sample_format: f.sample_format,
})
}
}
Ok(supported_configs.into_iter())
}
/// Gets the supported output configs.
/// TODO currently only supports the default.
/// Need to find all possible configs.
pub fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
// Retrieve the default config for the total supported channels and supported sample
// format.
let f = match self.default_output_config() {
Err(_) => return Err(SupportedStreamConfigsError::DeviceNotAvailable),
Ok(f) => f,
};
// Collect a config for every combination of supported sample rate and number of channels.
let mut supported_configs = vec![];
for &rate in crate::COMMON_SAMPLE_RATES {
if !self
.driver
.can_sample_rate(rate.0.into())
.ok()
.unwrap_or(false)
{
continue;
}
for channels in 1..f.channels + 1 {
supported_configs.push(SupportedStreamConfigRange {
channels,
min_sample_rate: rate,
max_sample_rate: rate,
buffer_size: f.buffer_size,
sample_format: f.sample_format,
})
}
}
Ok(supported_configs.into_iter())
}
/// Returns the default input config
pub fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let channels = self.driver.channels().map_err(default_config_err)?.ins as u16;
let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _);
let (min, max) = self.driver.buffersize_range().map_err(default_config_err)?;
let buffer_size = SupportedBufferSize::Range {
min: min as u32,
max: max as u32,
};
// Map th ASIO sample type to a CPAL sample type
let data_type = self.driver.input_data_type().map_err(default_config_err)?;
let sample_format = convert_data_type(&data_type)
.ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?;
Ok(SupportedStreamConfig {
channels,
sample_rate,
buffer_size,
sample_format,
})
}
/// Returns the default output config
pub fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let channels = self.driver.channels().map_err(default_config_err)?.outs as u16;
let sample_rate = SampleRate(self.driver.sample_rate().map_err(default_config_err)? as _);
let (min, max) = self.driver.buffersize_range().map_err(default_config_err)?;
let buffer_size = SupportedBufferSize::Range {
min: min as u32,
max: max as u32,
};
let data_type = self.driver.output_data_type().map_err(default_config_err)?;
let sample_format = convert_data_type(&data_type)
.ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?;
Ok(SupportedStreamConfig {
channels,
sample_rate,
buffer_size,
sample_format,
})
}
}
impl Devices {
pub fn new(asio: Arc<sys::Asio>) -> Result<Self, DevicesError> {
let drivers = asio.driver_names().into_iter();
Ok(Devices { asio, drivers })
}
}
impl Iterator for Devices {
type Item = Device;
/// Load drivers and return device
fn next(&mut self) -> Option<Device> {
loop {
match self.drivers.next() {
Some(name) => match self.asio.load_driver(&name) {
Ok(driver) => {
let driver = Arc::new(driver);
let asio_streams = Arc::new(Mutex::new(sys::AsioStreams {
input: None,
output: None,
}));
return Some(Device {
driver,
asio_streams,
current_buffer_index: Arc::new(AtomicI32::new(-1)),
});
}
Err(_) => continue,
},
None => return None,
}
}
}
}
pub(crate) fn convert_data_type(ty: &sys::AsioSampleType) -> Option<SampleFormat> {
let fmt = match *ty {
sys::AsioSampleType::ASIOSTInt16MSB => SampleFormat::I16,
sys::AsioSampleType::ASIOSTInt16LSB => SampleFormat::I16,
sys::AsioSampleType::ASIOSTFloat32MSB => SampleFormat::F32,
sys::AsioSampleType::ASIOSTFloat32LSB => SampleFormat::F32,
sys::AsioSampleType::ASIOSTInt32MSB => SampleFormat::I32,
sys::AsioSampleType::ASIOSTInt32LSB => SampleFormat::I32,
_ => return None,
};
Some(fmt)
}
fn default_config_err(e: sys::AsioError) -> DefaultStreamConfigError {
match e {
sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => {
DefaultStreamConfigError::DeviceNotAvailable
}
sys::AsioError::NoRate => DefaultStreamConfigError::StreamTypeNotSupported,
err => {
let description = format!("{}", err);
BackendSpecificError { description }.into()
}
}
}
-138
View File
@@ -1,138 +0,0 @@
extern crate asio_sys as sys;
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError,
};
pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs};
pub use self::stream::Stream;
use std::sync::Arc;
use std::time::Duration;
mod device;
mod stream;
/// The host for ASIO.
#[derive(Debug)]
pub struct Host {
asio: Arc<sys::Asio>,
}
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
let asio = Arc::new(sys::Asio::new());
let host = Host { asio };
Ok(host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
true
//unimplemented!("check how to do this using asio-sys")
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new(self.asio.clone())
}
fn default_input_device(&self) -> Option<Self::Device> {
// ASIO has no concept of a default device, so just use the first.
self.input_devices().ok().and_then(|mut ds| ds.next())
}
fn default_output_device(&self) -> Option<Self::Device> {
// ASIO has no concept of a default device, so just use the first.
self.output_devices().ok().and_then(|mut ds| ds.next())
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Device::build_input_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Device::build_output_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
Stream::play(self)
}
fn pause(&self) -> Result<(), PauseStreamError> {
Stream::pause(self)
}
}
-718
View File
@@ -1,718 +0,0 @@
extern crate asio_sys as sys;
extern crate num_traits;
use self::num_traits::PrimInt;
use super::Device;
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, InputCallbackInfo,
OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub struct Stream {
playing: Arc<AtomicBool>,
// Ensure the `Driver` does not terminate until the last stream is dropped.
driver: Arc<sys::Driver>,
#[allow(dead_code)]
asio_streams: Arc<Mutex<sys::AsioStreams>>,
callback_id: sys::CallbackId,
}
impl Stream {
pub fn play(&self) -> Result<(), PlayStreamError> {
self.playing.store(true, Ordering::SeqCst);
Ok(())
}
pub fn pause(&self) -> Result<(), PauseStreamError> {
self.playing.store(false, Ordering::SeqCst);
Ok(())
}
}
impl Device {
pub fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let stream_type = self.driver.input_data_type().map_err(build_stream_err)?;
// Ensure that the desired sample type is supported.
let expected_sample_format = super::device::convert_data_type(&stream_type)
.ok_or(BuildStreamError::StreamConfigNotSupported)?;
if sample_format != expected_sample_format {
return Err(BuildStreamError::StreamConfigNotSupported);
}
let num_channels = config.channels;
let buffer_size = self.get_or_create_input_stream(config, sample_format)?;
let cpal_num_samples = buffer_size * num_channels as usize;
// Create the buffer depending on the size of the data type.
let len_bytes = cpal_num_samples * sample_format.sample_size();
let mut interleaved = vec![0u8; len_bytes];
let stream_playing = Arc::new(AtomicBool::new(false));
let playing = Arc::clone(&stream_playing);
let asio_streams = self.asio_streams.clone();
// Set the input callback.
// This is most performance critical part of the ASIO bindings.
let config = config.clone();
let callback_id = self.driver.add_callback(move |callback_info| unsafe {
// If not playing return early.
if !playing.load(Ordering::SeqCst) {
return;
}
// There is 0% chance of lock contention the host only locks when recreating streams.
let stream_lock = asio_streams.lock().unwrap();
let asio_stream = match stream_lock.input {
Some(ref asio_stream) => asio_stream,
None => return,
};
/// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
/// 2. Deliver the CPAL buffer to the user callback.
unsafe fn process_input_callback<A, D, F>(
data_callback: &mut D,
interleaved: &mut [u8],
asio_stream: &sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: crate::SampleRate,
format: SampleFormat,
from_endianness: F,
) where
A: Copy,
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
F: Fn(A) -> A,
{
// 1. Write the ASIO channels to the CPAL buffer.
let interleaved: &mut [A] = cast_slice_mut(interleaved);
let n_frames = asio_stream.buffer_size as usize;
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
for ch_ix in 0..n_channels {
let asio_channel = asio_channel_slice::<A>(asio_stream, buffer_index, ch_ix);
for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) {
frame[ch_ix] = from_endianness(*s_asio);
}
}
// 2. Deliver the interleaved buffer to the callback.
let data = interleaved.as_mut_ptr() as *mut ();
let len = interleaved.len();
let data = Data::from_parts(data, len, format);
let callback = system_time_to_stream_instant(asio_info.system_time);
let delay = frames_to_duration(n_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
}
match (&stream_type, sample_format) {
(&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => {
process_input_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
from_le,
);
}
(&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => {
process_input_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
from_be,
);
}
(&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) => {
process_input_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
from_le,
);
}
(&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => {
process_input_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
from_be,
);
}
(&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I32) => {
process_input_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
from_le,
);
}
(&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I32) => {
process_input_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
from_be,
);
}
(&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F64) => {
process_input_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
from_le,
);
}
(&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F64) => {
process_input_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
from_be,
);
}
unsupported_format_pair => unreachable!(
"`build_input_stream_raw` should have returned with unsupported \
format {:?}",
unsupported_format_pair
),
}
});
let driver = self.driver.clone();
let asio_streams = self.asio_streams.clone();
// Immediately start the device?
self.driver.start().map_err(build_stream_err)?;
Ok(Stream {
playing: stream_playing,
driver,
asio_streams,
callback_id,
})
}
pub fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let stream_type = self.driver.output_data_type().map_err(build_stream_err)?;
// Ensure that the desired sample type is supported.
let expected_sample_format = super::device::convert_data_type(&stream_type)
.ok_or(BuildStreamError::StreamConfigNotSupported)?;
if sample_format != expected_sample_format {
return Err(BuildStreamError::StreamConfigNotSupported);
}
let num_channels = config.channels;
let buffer_size = self.get_or_create_output_stream(config, sample_format)?;
let cpal_num_samples = buffer_size * num_channels as usize;
// Create buffers depending on data type.
let len_bytes = cpal_num_samples * sample_format.sample_size();
let mut interleaved = vec![0u8; len_bytes];
let current_buffer_index = self.current_buffer_index.clone();
let stream_playing = Arc::new(AtomicBool::new(false));
let playing = Arc::clone(&stream_playing);
let asio_streams = self.asio_streams.clone();
let config = config.clone();
let callback_id = self.driver.add_callback(move |callback_info| unsafe {
// If not playing, return early.
if !playing.load(Ordering::SeqCst) {
return;
}
// There is 0% chance of lock contention the host only locks when recreating streams.
let mut stream_lock = asio_streams.lock().unwrap();
let asio_stream = match stream_lock.output {
Some(ref mut asio_stream) => asio_stream,
None => return,
};
// Silence the ASIO buffer that is about to be used.
//
// This checks if any other callbacks have already silenced the buffer associated with
// the current `buffer_index`.
let silence =
current_buffer_index.load(Ordering::Acquire) != callback_info.buffer_index;
if silence {
current_buffer_index.store(callback_info.buffer_index, Ordering::Release);
}
/// 1. Render the given callback to the given buffer of interleaved samples.
/// 2. If required, silence the ASIO buffer.
/// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
/// performing endianness conversions as necessary.
unsafe fn process_output_callback<A, D, F>(
data_callback: &mut D,
interleaved: &mut [u8],
silence_asio_buffer: bool,
asio_stream: &mut sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: crate::SampleRate,
format: SampleFormat,
mix_samples: F,
) where
A: Copy,
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
F: Fn(A, A) -> A,
{
// 1. Render interleaved buffer from callback.
let interleaved: &mut [A] = cast_slice_mut(interleaved);
let data = interleaved.as_mut_ptr() as *mut ();
let len = interleaved.len();
let mut data = Data::from_parts(data, len, format);
let callback = system_time_to_stream_instant(asio_info.system_time);
let n_frames = asio_stream.buffer_size as usize;
let delay = frames_to_duration(n_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
// 2. Silence ASIO channels if necessary.
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
if silence_asio_buffer {
for ch_ix in 0..n_channels {
let asio_channel =
asio_channel_slice_mut::<A>(asio_stream, buffer_index, ch_ix);
asio_channel.align_to_mut::<u8>().1.fill(0);
}
}
// 3. Write interleaved samples to ASIO channels, one channel at a time.
for ch_ix in 0..n_channels {
let asio_channel =
asio_channel_slice_mut::<A>(asio_stream, buffer_index, ch_ix);
for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) {
*s_asio = mix_samples(*s_asio, frame[ch_ix]);
}
}
}
match (sample_format, &stream_type) {
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => {
process_output_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
|old_sample, new_sample| {
from_le(old_sample).saturating_add(new_sample).to_le()
},
);
}
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => {
process_output_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
|old_sample, new_sample| {
from_be(old_sample).saturating_add(new_sample).to_be()
},
);
}
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) => {
process_output_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
|old_sample, new_sample| {
(f32::from_bits(from_le(old_sample)) + f32::from_bits(new_sample))
.to_bits()
.to_le()
},
);
}
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => {
process_output_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
|old_sample, new_sample| {
(f32::from_bits(from_be(old_sample)) + f32::from_bits(new_sample))
.to_bits()
.to_be()
},
);
}
(SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32LSB) => {
process_output_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
|old_sample, new_sample| {
from_le(old_sample).saturating_add(new_sample).to_le()
},
);
}
(SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32MSB) => {
process_output_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
|old_sample, new_sample| {
from_be(old_sample).saturating_add(new_sample).to_be()
},
);
}
(SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64LSB) => {
process_output_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
|old_sample, new_sample| {
(f64::from_bits(from_le(old_sample)) + f64::from_bits(new_sample))
.to_bits()
.to_le()
},
);
}
(SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64MSB) => {
process_output_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
|old_sample, new_sample| {
(f64::from_bits(from_be(old_sample)) + f64::from_bits(new_sample))
.to_bits()
.to_be()
},
);
}
unsupported_format_pair => unreachable!(
"`build_output_stream_raw` should have returned with unsupported \
format {:?}",
unsupported_format_pair
),
}
});
let driver = self.driver.clone();
let asio_streams = self.asio_streams.clone();
// Immediately start the device?
self.driver.start().map_err(build_stream_err)?;
Ok(Stream {
playing: stream_playing,
driver,
asio_streams,
callback_id,
})
}
/// Create a new CPAL Input Stream.
///
/// If there is no existing ASIO Input Stream it will be created.
///
/// On success, the buffer size of the stream is returned.
fn get_or_create_input_stream(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
) -> Result<usize, BuildStreamError> {
match self.default_input_config() {
Ok(f) => {
let num_asio_channels = f.channels;
check_config(&self.driver, config, sample_format, num_asio_channels)
}
Err(_) => Err(BuildStreamError::StreamConfigNotSupported),
}?;
let num_channels = config.channels as usize;
let mut streams = self.asio_streams.lock().unwrap();
let buffer_size = match config.buffer_size {
BufferSize::Fixed(v) => Some(v as i32),
BufferSize::Default => None,
};
// Either create a stream if thers none or had back the
// size of the current one.
match streams.input {
Some(ref input) => Ok(input.buffer_size as usize),
None => {
let output = streams.output.take();
self.driver
.prepare_input_stream(output, num_channels, buffer_size)
.map(|new_streams| {
let bs = match new_streams.input {
Some(ref inp) => inp.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
})
.map_err(|ref e| {
println!("Error preparing stream: {}", e);
BuildStreamError::DeviceNotAvailable
})
}
}
}
/// Create a new CPAL Output Stream.
///
/// If there is no existing ASIO Output Stream it will be created.
fn get_or_create_output_stream(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
) -> Result<usize, BuildStreamError> {
match self.default_output_config() {
Ok(f) => {
let num_asio_channels = f.channels;
check_config(&self.driver, config, sample_format, num_asio_channels)
}
Err(_) => Err(BuildStreamError::StreamConfigNotSupported),
}?;
let num_channels = config.channels as usize;
let mut streams = self.asio_streams.lock().unwrap();
let buffer_size = match config.buffer_size {
BufferSize::Fixed(v) => Some(v as i32),
BufferSize::Default => None,
};
// Either create a stream if thers none or had back the
// size of the current one.
match streams.output {
Some(ref output) => Ok(output.buffer_size as usize),
None => {
let input = streams.input.take();
self.driver
.prepare_output_stream(input, num_channels, buffer_size)
.map(|new_streams| {
let bs = match new_streams.output {
Some(ref out) => out.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
})
.map_err(|ref e| {
println!("Error preparing stream: {}", e);
BuildStreamError::DeviceNotAvailable
})
}
}
}
}
impl Drop for Stream {
fn drop(&mut self) {
self.driver.remove_callback(self.callback_id);
}
}
fn asio_ns_to_double(val: sys::bindings::asio_import::ASIOTimeStamp) -> f64 {
let two_raised_to_32 = 4294967296.0;
val.lo as f64 + val.hi as f64 * two_raised_to_32
}
/// Asio retrieves system time via `timeGetTime` which returns the time in milliseconds.
fn system_time_to_stream_instant(
system_time: sys::bindings::asio_import::ASIOTimeStamp,
) -> crate::StreamInstant {
let systime_ns = asio_ns_to_double(system_time);
let secs = systime_ns as i64 / 1_000_000_000;
let nanos = (systime_ns as i64 - secs * 1_000_000_000) as u32;
crate::StreamInstant::new(secs, nanos)
}
/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
/// Check whether or not the desired config is supported by the stream.
///
/// Checks sample rate, data type and then finally the number of channels.
fn check_config(
driver: &sys::Driver,
config: &StreamConfig,
sample_format: SampleFormat,
num_asio_channels: u16,
) -> Result<(), BuildStreamError> {
let StreamConfig {
channels,
sample_rate,
buffer_size: _,
} = config;
// Try and set the sample rate to what the user selected.
let sample_rate = sample_rate.0.into();
if sample_rate != driver.sample_rate().map_err(build_stream_err)? {
if driver
.can_sample_rate(sample_rate)
.map_err(build_stream_err)?
{
driver
.set_sample_rate(sample_rate)
.map_err(build_stream_err)?;
} else {
return Err(BuildStreamError::StreamConfigNotSupported);
}
}
// unsigned formats are not supported by asio
match sample_format {
SampleFormat::I16 | SampleFormat::I32 | SampleFormat::F32 => (),
_ => return Err(BuildStreamError::StreamConfigNotSupported),
}
if *channels > num_asio_channels {
return Err(BuildStreamError::StreamConfigNotSupported);
}
Ok(())
}
/// Cast a byte slice into a mutable slice of desired type.
///
/// Safety: it's up to the caller to ensure that the input slice has valid bit representations.
unsafe fn cast_slice_mut<T>(v: &mut [u8]) -> &mut [T] {
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::<T>())
}
/// Helper function to convert from little endianness.
fn from_le<T: PrimInt>(t: T) -> T {
T::from_le(t)
}
/// Helper function to convert from little endianness.
fn from_be<T: PrimInt>(t: T) -> T {
T::from_be(t)
}
/// Shorthand for retrieving the asio buffer slice associated with a channel.
unsafe fn asio_channel_slice<T>(
asio_stream: &sys::AsioStream,
buffer_index: usize,
channel_index: usize,
) -> &[T] {
let buff_ptr: *const T =
asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *const _;
std::slice::from_raw_parts(buff_ptr, asio_stream.buffer_size as usize)
}
/// Shorthand for retrieving the asio buffer slice associated with a channel.
unsafe fn asio_channel_slice_mut<T>(
asio_stream: &mut sys::AsioStream,
buffer_index: usize,
channel_index: usize,
) -> &mut [T] {
let buff_ptr: *mut T =
asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *mut _;
std::slice::from_raw_parts_mut(buff_ptr, asio_stream.buffer_size as usize)
}
fn build_stream_err(e: sys::AsioError) -> BuildStreamError {
match e {
sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => {
BuildStreamError::DeviceNotAvailable
}
sys::AsioError::InvalidInput | sys::AsioError::BadMode => BuildStreamError::InvalidArgument,
err => {
let description = format!("{}", err);
BackendSpecificError { description }.into()
}
}
}
@@ -1,43 +0,0 @@
use std::vec::IntoIter as VecIntoIter;
use crate::DevicesError;
use crate::SupportedStreamConfigRange;
use super::Device;
pub type SupportedInputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
// TODO: Support enumerating earpiece vs headset vs speaker etc?
pub struct Devices(VecIntoIter<Device>);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Self::default())
}
}
impl Default for Devices {
fn default() -> Devices {
Devices(vec![Device].into_iter())
}
}
impl Iterator for Devices {
type Item = Device;
#[inline]
fn next(&mut self) -> Option<Device> {
self.0.next()
}
}
#[inline]
pub fn default_input_device() -> Option<Device> {
Some(Device)
}
#[inline]
pub fn default_output_device() -> Option<Device> {
Some(Device)
}
-434
View File
@@ -1,434 +0,0 @@
//!
//! coreaudio on iOS looks a bit different from macOS. A lot of configuration needs to use
//! the AVAudioSession objc API which doesn't exist on macOS.
//!
//! TODO:
//! - Use AVAudioSession to enumerate buffer size / sample rate / number of channels and set
//! buffer size.
//!
extern crate core_foundation_sys;
extern crate coreaudio;
use std::cell::RefCell;
use self::coreaudio::audio_unit::render_callback::data;
use self::coreaudio::audio_unit::{render_callback, AudioUnit, Element, Scope};
use self::coreaudio::sys::{
kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat, AudioBuffer,
AudioStreamBasicDescription,
};
use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant};
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use self::enumerate::{
default_input_device, default_output_device, Devices, SupportedInputConfigs,
SupportedOutputConfigs,
};
use std::slice;
use std::time::Duration;
pub mod enumerate;
// These days the default of iOS is now F32 and no longer I16
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
pub struct Host;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl Device {
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Ok("Default Device".to_owned())
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
// TODO: query AVAudioSession for parameters, some values like sample rate and buffer size
// probably need to actually be set to see if it works, but channels can be enumerated.
let asbd: AudioStreamBasicDescription = default_input_asbd()?;
let stream_config = stream_config_from_asbd(asbd);
Ok(vec![SupportedStreamConfigRange {
channels: stream_config.channels,
min_sample_rate: stream_config.sample_rate,
max_sample_rate: stream_config.sample_rate,
buffer_size: stream_config.buffer_size.clone(),
sample_format: SUPPORTED_SAMPLE_FORMAT,
}]
.into_iter())
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
// TODO: query AVAudioSession for parameters, some values like sample rate and buffer size
// probably need to actually be set to see if it works, but channels can be enumerated.
let asbd: AudioStreamBasicDescription = default_output_asbd()?;
let stream_config = stream_config_from_asbd(asbd);
let configs: Vec<_> = (1..=asbd.mChannelsPerFrame as u16)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: stream_config.sample_rate,
max_sample_rate: stream_config.sample_rate,
buffer_size: stream_config.buffer_size.clone(),
sample_format: SUPPORTED_SAMPLE_FORMAT,
})
.collect();
Ok(configs.into_iter())
}
#[inline]
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let asbd: AudioStreamBasicDescription = default_input_asbd()?;
let stream_config = stream_config_from_asbd(asbd);
Ok(stream_config)
}
#[inline]
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let asbd: AudioStreamBasicDescription = default_output_asbd()?;
let stream_config = stream_config_from_asbd(asbd);
Ok(stream_config)
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
#[inline]
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
#[inline]
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
mut error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
// The scope and element for working with a device's input stream.
let scope = Scope::Output;
let element = Element::Input;
let mut audio_unit = create_audio_unit()?;
audio_unit.uninitialize()?;
configure_for_recording(&mut audio_unit)?;
audio_unit.initialize()?;
// Set the stream in interleaved mode.
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
// Set the buffersize
match config.buffer_size {
BufferSize::Fixed(_) => {
return Err(BuildStreamError::StreamConfigNotSupported);
}
BufferSize::Default => (),
}
// Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>;
audio_unit.set_input_callback(move |args: Args| unsafe {
let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer;
let len = (*args.data.data).mNumberBuffers as usize;
let buffers: &[AudioBuffer] = slice::from_raw_parts(ptr, len);
// There is only 1 buffer when using interleaved channels
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = buffers[0];
let data = data as *mut ();
let len = (data_byte_size as usize / bytes_per_channel) as usize;
let data = Data::from_parts(data, len, sample_format);
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err.into());
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
Ok(())
})?;
audio_unit.start()?;
Ok(Stream::new(StreamInner {
playing: true,
audio_unit,
}))
}
/// Create an output stream.
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
mut error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
match config.buffer_size {
BufferSize::Fixed(_) => {
return Err(BuildStreamError::StreamConfigNotSupported);
}
BufferSize::Default => (),
};
let mut audio_unit = create_audio_unit()?;
// The scope and element for working with a device's output stream.
let scope = Scope::Input;
let element = Element::Output;
// Set the stream in interleaved mode.
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
// Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>;
audio_unit.set_render_callback(move |args: Args| unsafe {
// If `run()` is currently running, then a callback will be available from this list.
// Otherwise, we just fill the buffer with zeroes and return.
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = (*args.data.data).mBuffers[0];
let data = data as *mut ();
let len = (data_byte_size as usize / bytes_per_channel) as usize;
let mut data = Data::from_parts(data, len, sample_format);
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err.into());
return Err(());
}
Ok(cb) => cb,
};
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
Ok(())
})?;
audio_unit.start()?;
Ok(Stream::new(StreamInner {
playing: true,
audio_unit,
}))
}
}
pub struct Stream {
inner: RefCell<StreamInner>,
}
impl Stream {
fn new(inner: StreamInner) -> Self {
Self {
inner: RefCell::new(inner),
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
let mut stream = self.inner.borrow_mut();
if !stream.playing {
if let Err(e) = stream.audio_unit.start() {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = true;
}
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
let mut stream = self.inner.borrow_mut();
if stream.playing {
if let Err(e) = stream.audio_unit.stop() {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = false;
}
Ok(())
}
}
struct StreamInner {
playing: bool,
audio_unit: AudioUnit,
}
fn create_audio_unit() -> Result<AudioUnit, coreaudio::Error> {
AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)
}
fn configure_for_recording(audio_unit: &mut AudioUnit) -> Result<(), coreaudio::Error> {
// Enable mic recording
let enable_input = 1u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Input,
Element::Input,
Some(&enable_input),
)?;
// Disable output
let disable_output = 0u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Output,
Element::Output,
Some(&disable_output),
)?;
Ok(())
}
fn default_output_asbd() -> Result<AudioStreamBasicDescription, coreaudio::Error> {
let audio_unit = create_audio_unit()?;
let id = kAudioUnitProperty_StreamFormat;
let asbd: AudioStreamBasicDescription =
audio_unit.get_property(id, Scope::Output, Element::Output)?;
Ok(asbd)
}
fn default_input_asbd() -> Result<AudioStreamBasicDescription, coreaudio::Error> {
let mut audio_unit = create_audio_unit()?;
audio_unit.uninitialize()?;
configure_for_recording(&mut audio_unit)?;
audio_unit.initialize()?;
let id = kAudioUnitProperty_StreamFormat;
let asbd: AudioStreamBasicDescription =
audio_unit.get_property(id, Scope::Input, Element::Input)?;
Ok(asbd)
}
fn stream_config_from_asbd(asbd: AudioStreamBasicDescription) -> SupportedStreamConfig {
let buffer_size = SupportedBufferSize::Range { min: 0, max: 0 };
SupportedStreamConfig {
channels: asbd.mChannelsPerFrame as u16,
sample_rate: SampleRate(asbd.mSampleRate as u32),
buffer_size: buffer_size.clone(),
sample_format: SUPPORTED_SAMPLE_FORMAT,
}
}
@@ -1,152 +0,0 @@
extern crate coreaudio;
use self::coreaudio::sys::{
kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice,
kAudioHardwarePropertyDefaultOutputDevice, kAudioHardwarePropertyDevices,
kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject,
AudioDeviceID, AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize,
AudioObjectPropertyAddress, OSStatus,
};
use super::Device;
use crate::{BackendSpecificError, DevicesError, SupportedStreamConfigRange};
use std::mem;
use std::ptr::null;
use std::vec::IntoIter as VecIntoIter;
unsafe fn audio_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
macro_rules! try_status_or_return {
($status:expr) => {
if $status != kAudioHardwareNoError as i32 {
return Err($status);
}
};
}
let data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
);
try_status_or_return!(status);
let device_count = data_size / mem::size_of::<AudioDeviceID>() as u32;
let mut audio_devices = vec![];
audio_devices.reserve_exact(device_count as usize);
let status = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
audio_devices.as_mut_ptr() as *mut _,
);
try_status_or_return!(status);
audio_devices.set_len(device_count as usize);
Ok(audio_devices)
}
pub struct Devices(VecIntoIter<AudioDeviceID>);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
let devices = unsafe {
match audio_devices() {
Ok(devices) => devices,
Err(os_status) => {
let description = format!("{}", os_status);
let err = BackendSpecificError { description };
return Err(err.into());
}
}
};
Ok(Devices(devices.into_iter()))
}
}
unsafe impl Send for Devices {}
unsafe impl Sync for Devices {}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Device> {
self.0.next().map(|id| Device {
audio_device_id: id,
is_default: false,
})
}
}
pub fn default_input_device() -> Option<Device> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultInputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let audio_device_id: AudioDeviceID = 0;
let data_size = mem::size_of::<AudioDeviceID>();
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&audio_device_id as *const _ as *mut _,
)
};
if status != kAudioHardwareNoError as i32 {
return None;
}
let device = Device {
audio_device_id,
is_default: true,
};
Some(device)
}
pub fn default_output_device() -> Option<Device> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let audio_device_id: AudioDeviceID = 0;
let data_size = mem::size_of::<AudioDeviceID>();
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&audio_device_id as *const _ as *mut _,
)
};
if status != kAudioHardwareNoError as i32 {
return None;
}
let device = Device {
audio_device_id,
is_default: true,
};
Some(device)
}
pub type SupportedInputConfigs = VecIntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
-941
View File
@@ -1,941 +0,0 @@
extern crate core_foundation_sys;
extern crate coreaudio;
use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_stream_instant};
use self::core_foundation_sys::string::{CFStringGetCString, CFStringGetCStringPtr, CFStringRef};
use self::coreaudio::audio_unit::render_callback::{self, data};
use self::coreaudio::audio_unit::{AudioUnit, Element, Scope};
use self::coreaudio::sys::{
kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize,
kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceIsAlive,
kAudioDevicePropertyDeviceNameCFString, kAudioDevicePropertyNominalSampleRate,
kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreamConfiguration,
kAudioDevicePropertyStreamFormat, kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
kAudioObjectPropertyScopeOutput, kAudioOutputUnitProperty_CurrentDevice,
kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat, kCFStringEncodingUTF8,
AudioBuffer, AudioBufferList, AudioDeviceID, AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
AudioObjectPropertyScope, AudioObjectSetPropertyData, AudioStreamBasicDescription,
AudioValueRange, OSStatus,
};
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};
use std::ffi::CStr;
use std::fmt;
use std::mem;
use std::os::raw::c_char;
use std::ptr::null;
use std::rc::Rc;
use std::slice;
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub use self::enumerate::{
default_input_device, default_output_device, Devices, SupportedInputConfigs,
SupportedOutputConfigs,
};
use property_listener::AudioObjectPropertyListener;
pub mod enumerate;
mod property_listener;
/// Coreaudio host, the default host on macOS.
#[derive(Debug)]
pub struct Host;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
// Assume coreaudio is always available
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Device::build_input_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Device::build_output_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Device {
pub(crate) audio_device_id: AudioDeviceID,
is_default: bool,
}
impl Device {
fn name(&self) -> Result<String, DeviceNameError> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceNameCFString,
mScope: kAudioDevicePropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster,
};
let device_name: CFStringRef = null();
let data_size = mem::size_of::<CFStringRef>();
let c_str = unsafe {
let status = AudioObjectGetPropertyData(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&device_name as *const _ as *mut _,
);
check_os_status(status)?;
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
if c_string.is_null() {
let status = AudioObjectGetPropertyData(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&device_name as *const _ as *mut _,
);
check_os_status(status)?;
let mut buf: [i8; 255] = [0; 255];
let result = CFStringGetCString(
device_name,
buf.as_mut_ptr(),
buf.len() as _,
kCFStringEncodingUTF8,
);
if result == 0 {
let description =
"core foundation failed to return device name string".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
let name: &CStr = CStr::from_ptr(buf.as_ptr());
return Ok(name.to_str().unwrap().to_owned());
}
CStr::from_ptr(c_string as *mut _)
};
Ok(c_str.to_string_lossy().into_owned())
}
// Logic re-used between `supported_input_configs` and `supported_output_configs`.
#[allow(clippy::cast_ptr_alignment)]
fn supported_configs(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration,
mScope: scope,
mElement: kAudioObjectPropertyElementMaster,
};
unsafe {
// Retrieve the devices audio buffer list.
let data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
);
check_os_status(status)?;
let mut audio_buffer_list: Vec<u8> = vec![];
audio_buffer_list.reserve_exact(data_size as usize);
let status = AudioObjectGetPropertyData(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
audio_buffer_list.as_mut_ptr() as *mut _,
);
check_os_status(status)?;
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
// If there's no buffers, skip.
if (*audio_buffer_list).mNumberBuffers == 0 {
return Ok(vec![].into_iter());
}
// Count the number of channels as the sum of all channels in all output buffers.
let n_buffers = (*audio_buffer_list).mNumberBuffers as usize;
let first: *const AudioBuffer = (*audio_buffer_list).mBuffers.as_ptr();
let buffers: &'static [AudioBuffer] = slice::from_raw_parts(first, n_buffers);
let mut n_channels = 0;
for buffer in buffers {
n_channels += buffer.mNumberChannels as usize;
}
// TODO: macOS should support U8, I16, I32, F32 and F64. This should allow for using
// I16 but just use F32 for now as it's the default anyway.
let sample_format = SampleFormat::F32;
// Get available sample rate ranges.
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
let data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
);
check_os_status(status)?;
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<u8> = vec![];
ranges.reserve_exact(data_size as usize);
let status = AudioObjectGetPropertyData(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
ranges.as_mut_ptr() as *mut _,
);
check_os_status(status)?;
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
let audio_unit = audio_unit_from_device(self, true)?;
let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?;
// Collect the supported formats for the device.
let mut fmts = vec![];
for range in ranges {
let fmt = SupportedStreamConfigRange {
channels: n_channels as ChannelCount,
min_sample_rate: SampleRate(range.mMinimum as _),
max_sample_rate: SampleRate(range.mMaximum as _),
buffer_size,
sample_format,
};
fmts.push(fmt);
}
Ok(fmts.into_iter())
}
}
fn supported_input_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
self.supported_configs(kAudioObjectPropertyScopeInput)
}
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
self.supported_configs(kAudioObjectPropertyScopeOutput)
}
fn default_config(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
fn default_config_error_from_os_status(
status: OSStatus,
) -> Result<(), DefaultStreamConfigError> {
let err = match coreaudio::Error::from_os_status(status) {
Err(err) => err,
Ok(_) => return Ok(()),
};
match err {
coreaudio::Error::AudioUnit(
coreaudio::error::AudioUnitError::FormatNotSupported,
)
| coreaudio::Error::AudioCodec(_)
| coreaudio::Error::AudioFormat(_) => {
Err(DefaultStreamConfigError::StreamTypeNotSupported)
}
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => {
Err(DefaultStreamConfigError::DeviceNotAvailable)
}
err => {
let description = format!("{}", err);
let err = BackendSpecificError { description };
Err(err.into())
}
}
}
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamFormat,
mScope: scope,
mElement: kAudioObjectPropertyElementMaster,
};
unsafe {
let asbd: AudioStreamBasicDescription = mem::zeroed();
let data_size = mem::size_of::<AudioStreamBasicDescription>() as u32;
let status = AudioObjectGetPropertyData(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&asbd as *const _ as *mut _,
);
default_config_error_from_os_status(status)?;
let sample_format = {
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
asbd.mFormatID,
Some(asbd.mFormatFlags),
);
let flags = match audio_format {
Some(coreaudio::audio_unit::AudioFormat::LinearPCM(flags)) => flags,
_ => return Err(DefaultStreamConfigError::StreamTypeNotSupported),
};
let maybe_sample_format =
coreaudio::audio_unit::SampleFormat::from_flags_and_bits_per_sample(
flags,
asbd.mBitsPerChannel,
);
match maybe_sample_format {
Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32,
Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16,
_ => return Err(DefaultStreamConfigError::StreamTypeNotSupported),
}
};
let audio_unit = audio_unit_from_device(self, true)?;
let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?;
let config = SupportedStreamConfig {
sample_rate: SampleRate(asbd.mSampleRate as _),
channels: asbd.mChannelsPerFrame as _,
buffer_size,
sample_format,
};
Ok(config)
}
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(kAudioObjectPropertyScopeInput)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(kAudioObjectPropertyScopeOutput)
}
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Device")
.field("audio_device_id", &self.audio_device_id)
.field("name", &self.name())
.finish()
}
}
struct StreamInner {
playing: bool,
audio_unit: AudioUnit,
/// Manage the lifetime of the closure that handles device disconnection.
_disconnect_listener: Option<AudioObjectPropertyListener>,
// Track the device with which the audio unit was spawned.
//
// We must do this so that we can avoid changing the device sample rate if there is already
// a stream associated with the device.
#[allow(dead_code)]
device_id: AudioDeviceID,
}
/// Register the on-disconnect callback.
/// This will both stop the stream and call the error callback with DeviceNotAvailable.
/// This function should only be called once per stream.
fn add_disconnect_listener<E>(
stream: &Stream,
error_callback: Arc<Mutex<E>>,
) -> Result<(), BuildStreamError>
where
E: FnMut(StreamError) + Send + 'static,
{
let stream_copy = stream.clone();
let mut stream_inner = stream.inner.lock().unwrap();
stream_inner._disconnect_listener = Some(AudioObjectPropertyListener::new(
stream_inner.device_id,
AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceIsAlive,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
},
move || {
let _ = stream_copy.pause();
(error_callback.lock().unwrap())(StreamError::DeviceNotAvailable);
},
)?);
Ok(())
}
fn audio_unit_from_device(device: &Device, input: bool) -> Result<AudioUnit, coreaudio::Error> {
let output_type = if device.is_default && !input {
coreaudio::audio_unit::IOType::DefaultOutput
} else {
coreaudio::audio_unit::IOType::HalOutput
};
let mut audio_unit = AudioUnit::new(output_type)?;
if input {
// Enable input processing.
let enable_input = 1u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Input,
Element::Input,
Some(&enable_input),
)?;
// Disable output processing.
let disable_output = 0u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Output,
Element::Output,
Some(&disable_output),
)?;
}
audio_unit.set_property(
kAudioOutputUnitProperty_CurrentDevice,
Scope::Global,
Element::Output,
Some(&device.audio_device_id),
)?;
Ok(audio_unit)
}
impl Device {
#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::while_immutable_condition)]
#[allow(clippy::float_cmp)]
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
// The scope and element for working with a device's input stream.
let scope = Scope::Output;
let element = Element::Input;
// Potentially change the device sample rate to match the config.
set_sample_rate(self.audio_device_id, config.sample_rate)?;
let mut audio_unit = audio_unit_from_device(self, true)?;
// Set the stream in interleaved mode.
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
// Set the buffersize
match config.buffer_size {
BufferSize::Fixed(v) => {
let buffer_size_range = get_io_buffer_frame_size_range(&audio_unit)?;
match buffer_size_range {
SupportedBufferSize::Range { min, max } => {
if v >= min && v <= max {
audio_unit.set_property(
kAudioDevicePropertyBufferFrameSize,
scope,
element,
Some(&v),
)?
} else {
return Err(BuildStreamError::StreamConfigNotSupported);
}
}
SupportedBufferSize::Unknown => (),
}
}
BufferSize::Default => (),
}
let error_callback = Arc::new(Mutex::new(error_callback));
let error_callback_disconnect = error_callback.clone();
// Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>;
audio_unit.set_input_callback(move |args: Args| unsafe {
let ptr = (*args.data.data).mBuffers.as_ptr();
let len = (*args.data.data).mNumberBuffers as usize;
let buffers: &[AudioBuffer] = slice::from_raw_parts(ptr, len);
// TODO: Perhaps loop over all buffers instead?
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = buffers[0];
let data = data as *mut ();
let len = data_byte_size as usize / bytes_per_channel;
let data = Data::from_parts(data, len, sample_format);
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
(error_callback.lock().unwrap())(err.into());
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
Ok(())
})?;
let stream = Stream::new(StreamInner {
playing: true,
_disconnect_listener: None,
audio_unit,
device_id: self.audio_device_id,
});
// If we didn't request the default device, stop the stream if the
// device disconnects.
if !self.is_default {
add_disconnect_listener(&stream, error_callback_disconnect)?;
}
stream.inner.lock().unwrap().audio_unit.start()?;
Ok(stream)
}
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let mut audio_unit = audio_unit_from_device(self, false)?;
// The scope and element for working with a device's output stream.
let scope = Scope::Input;
let element = Element::Output;
// Set the stream in interleaved mode.
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
// Set the buffersize
match config.buffer_size {
BufferSize::Fixed(v) => {
let buffer_size_range = get_io_buffer_frame_size_range(&audio_unit)?;
match buffer_size_range {
SupportedBufferSize::Range { min, max } => {
if v >= min && v <= max {
audio_unit.set_property(
kAudioDevicePropertyBufferFrameSize,
scope,
element,
Some(&v),
)?
} else {
return Err(BuildStreamError::StreamConfigNotSupported);
}
}
SupportedBufferSize::Unknown => (),
}
}
BufferSize::Default => (),
}
let error_callback = Arc::new(Mutex::new(error_callback));
let error_callback_disconnect = error_callback.clone();
// Register the callback that is being called by coreaudio whenever it needs data to be
// fed to the audio buffer.
let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
type Args = render_callback::Args<data::Raw>;
audio_unit.set_render_callback(move |args: Args| unsafe {
// If `run()` is currently running, then a callback will be available from this list.
// Otherwise, we just fill the buffer with zeroes and return.
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = (*args.data.data).mBuffers[0];
let data = data as *mut ();
let len = data_byte_size as usize / bytes_per_channel;
let mut data = Data::from_parts(data, len, sample_format);
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
(error_callback.lock().unwrap())(err.into());
return Err(());
}
Ok(cb) => cb,
};
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
let buffer_frames = len / channels as usize;
let delay = frames_to_duration(buffer_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
Ok(())
})?;
let stream = Stream::new(StreamInner {
playing: true,
_disconnect_listener: None,
audio_unit,
device_id: self.audio_device_id,
});
// If we didn't request the default device, stop the stream if the
// device disconnects.
if !self.is_default {
add_disconnect_listener(&stream, error_callback_disconnect)?;
}
stream.inner.lock().unwrap().audio_unit.start()?;
Ok(stream)
}
}
/// Attempt to set the device sample rate to the provided rate.
/// Return an error if the requested sample rate is not supported by the device.
fn set_sample_rate(
audio_device_id: AudioObjectID,
target_sample_rate: SampleRate,
) -> Result<(), BuildStreamError> {
// Get the current sample rate.
let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let sample_rate: f64 = 0.0;
let data_size = mem::size_of::<f64>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&sample_rate as *const _ as *mut _,
)
};
coreaudio::Error::from_os_status(status)?;
// If the requested sample rate is different to the device sample rate, update the device.
if sample_rate as u32 != target_sample_rate.0 {
// Get available sample rate ranges.
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
let data_size = 0u32;
let status = unsafe {
AudioObjectGetPropertyDataSize(
audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
)
};
coreaudio::Error::from_os_status(status)?;
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<u8> = vec![];
ranges.reserve_exact(data_size as usize);
let status = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
ranges.as_mut_ptr() as *mut _,
)
};
coreaudio::Error::from_os_status(status)?;
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
let ranges: &'static [AudioValueRange] = unsafe { slice::from_raw_parts(ranges, n_ranges) };
// Now that we have the available ranges, pick the one matching the desired rate.
let sample_rate = target_sample_rate.0;
let maybe_index = ranges
.iter()
.position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate);
let range_index = match maybe_index {
None => return Err(BuildStreamError::StreamConfigNotSupported),
Some(i) => i,
};
let (send, recv) = channel::<Result<f64, coreaudio::Error>>();
let sample_rate_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
// Send sample rate updates back on a channel.
let sample_rate_handler = move || {
let mut rate: f64 = 0.0;
let data_size = mem::size_of::<f64>();
let result = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
&sample_rate_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&mut rate as *const _ as *mut _,
)
};
send.send(coreaudio::Error::from_os_status(result).map(|_| rate))
.ok();
};
let listener = AudioObjectPropertyListener::new(
audio_device_id,
sample_rate_address,
sample_rate_handler,
)?;
// Finally, set the sample rate.
property_address.mSelector = kAudioDevicePropertyNominalSampleRate;
let status = unsafe {
AudioObjectSetPropertyData(
audio_device_id,
&property_address as *const _,
0,
null(),
data_size,
&ranges[range_index] as *const _ as *const _,
)
};
coreaudio::Error::from_os_status(status)?;
// Wait for the reported_rate to change.
//
// This should not take longer than a few ms, but we timeout after 1 sec just in case.
// We loop over potentially several events from the channel to ensure
// that we catch the expected change in sample rate.
let mut timeout = Duration::from_secs(1);
let start = Instant::now();
loop {
match recv.recv_timeout(timeout) {
Err(err) => {
let description = match err {
RecvTimeoutError::Disconnected => {
"sample rate listener channel disconnected unexpectedly"
}
RecvTimeoutError::Timeout => {
"timeout waiting for sample rate update for device"
}
}
.to_string();
return Err(BackendSpecificError { description }.into());
}
Ok(Ok(reported_sample_rate)) => {
if reported_sample_rate == target_sample_rate.0 as f64 {
break;
}
}
Ok(Err(_)) => {
// TODO: should we consider collecting this error?
}
};
timeout = timeout
.checked_sub(start.elapsed())
.unwrap_or(Duration::ZERO);
}
listener.remove()?;
}
Ok(())
}
#[derive(Clone)]
pub struct Stream {
inner: Arc<Mutex<StreamInner>>,
}
impl Stream {
fn new(inner: StreamInner) -> Self {
Self {
inner: Arc::new(Mutex::new(inner)),
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
let mut stream = self.inner.lock().unwrap();
if !stream.playing {
if let Err(e) = stream.audio_unit.start() {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = true;
}
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
let mut stream = self.inner.lock().unwrap();
if stream.playing {
if let Err(e) = stream.audio_unit.stop() {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = false;
}
Ok(())
}
}
fn get_io_buffer_frame_size_range(
audio_unit: &AudioUnit,
) -> Result<SupportedBufferSize, coreaudio::Error> {
let buffer_size_range: AudioValueRange = audio_unit.get_property(
kAudioDevicePropertyBufferFrameSizeRange,
Scope::Global,
Element::Output,
)?;
Ok(SupportedBufferSize::Range {
min: buffer_size_range.mMinimum as u32,
max: buffer_size_range.mMaximum as u32,
})
}
@@ -1,85 +0,0 @@
//! Helper code for registering audio object property listeners.
use super::coreaudio::sys::{
AudioObjectAddPropertyListener, AudioObjectID, AudioObjectPropertyAddress,
AudioObjectRemovePropertyListener, OSStatus,
};
use crate::BuildStreamError;
/// A double-indirection to be able to pass a closure (a fat pointer)
/// via a single c_void.
struct PropertyListenerCallbackWrapper(Box<dyn FnMut()>);
/// Maintain an audio object property listener.
/// The listener will be removed when this type is dropped.
pub struct AudioObjectPropertyListener {
callback: Box<PropertyListenerCallbackWrapper>,
property_address: AudioObjectPropertyAddress,
audio_object_id: AudioObjectID,
removed: bool,
}
impl AudioObjectPropertyListener {
/// Attach the provided callback as a audio object property listener.
pub fn new<F: FnMut() + 'static>(
audio_object_id: AudioObjectID,
property_address: AudioObjectPropertyAddress,
callback: F,
) -> Result<Self, BuildStreamError> {
let callback = Box::new(PropertyListenerCallbackWrapper(Box::new(callback)));
unsafe {
coreaudio::Error::from_os_status(AudioObjectAddPropertyListener(
audio_object_id,
&property_address as *const _,
Some(property_listener_handler_shim),
&*callback as *const _ as *mut _,
))?;
};
Ok(Self {
callback,
audio_object_id,
property_address,
removed: false,
})
}
/// Explicitly remove the property listener.
/// Use this method if you need to explicitly handle failure to remove
/// the property listener.
pub fn remove(mut self) -> Result<(), BuildStreamError> {
self.remove_inner()
}
fn remove_inner(&mut self) -> Result<(), BuildStreamError> {
unsafe {
coreaudio::Error::from_os_status(AudioObjectRemovePropertyListener(
self.audio_object_id,
&self.property_address as *const _,
Some(property_listener_handler_shim),
&*self.callback as *const _ as *mut _,
))?;
}
self.removed = true;
Ok(())
}
}
impl Drop for AudioObjectPropertyListener {
fn drop(&mut self) {
if !self.removed {
let _ = self.remove_inner();
}
}
}
/// Callback used to call user-provided closure as a property listener.
unsafe extern "C" fn property_listener_handler_shim(
_: AudioObjectID,
_: u32,
_: *const AudioObjectPropertyAddress,
callback: *mut ::std::os::raw::c_void,
) -> OSStatus {
let wrapper = callback as *mut PropertyListenerCallbackWrapper;
(*wrapper).0();
0
}
-121
View File
@@ -1,121 +0,0 @@
extern crate coreaudio;
use self::coreaudio::sys::{
kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatLinearPCM,
AudioStreamBasicDescription, OSStatus,
};
use crate::DefaultStreamConfigError;
use crate::{BuildStreamError, SupportedStreamConfigsError};
use crate::{BackendSpecificError, SampleFormat, StreamConfig};
#[cfg(target_os = "ios")]
mod ios;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "ios")]
pub use self::ios::{
enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs},
Device, Host, Stream,
};
#[cfg(target_os = "macos")]
pub use self::macos::{
enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs},
Device, Host, Stream,
};
/// Common helper methods used by both macOS and iOS
fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> {
match coreaudio::Error::from_os_status(os_status) {
Ok(()) => Ok(()),
Err(err) => {
let description = err.to_string();
Err(BackendSpecificError { description })
}
}
}
// Create a coreaudio AudioStreamBasicDescription from a CPAL Format.
fn asbd_from_config(
config: &StreamConfig,
sample_format: SampleFormat,
) -> AudioStreamBasicDescription {
let n_channels = config.channels as usize;
let sample_rate = config.sample_rate.0;
let bytes_per_channel = sample_format.sample_size();
let bits_per_channel = bytes_per_channel * 8;
let bytes_per_frame = n_channels * bytes_per_channel;
let frames_per_packet = 1;
let bytes_per_packet = frames_per_packet * bytes_per_frame;
let format_flags = match sample_format {
SampleFormat::F32 => kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked,
_ => kAudioFormatFlagIsPacked,
};
AudioStreamBasicDescription {
mBitsPerChannel: bits_per_channel as _,
mBytesPerFrame: bytes_per_frame as _,
mChannelsPerFrame: n_channels as _,
mBytesPerPacket: bytes_per_packet as _,
mFramesPerPacket: frames_per_packet as _,
mFormatFlags: format_flags,
mFormatID: kAudioFormatLinearPCM,
mSampleRate: sample_rate as _,
..Default::default()
}
}
fn host_time_to_stream_instant(
m_host_time: u64,
) -> Result<crate::StreamInstant, BackendSpecificError> {
let mut info: mach2::mach_time::mach_timebase_info = Default::default();
let res = unsafe { mach2::mach_time::mach_timebase_info(&mut info) };
check_os_status(res)?;
let nanos = m_host_time * info.numer as u64 / info.denom as u64;
let secs = nanos / 1_000_000_000;
let subsec_nanos = nanos - secs * 1_000_000_000;
Ok(crate::StreamInstant::new(secs as i64, subsec_nanos as u32))
}
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
// TODO need stronger error identification
impl From<coreaudio::Error> for BuildStreamError {
fn from(err: coreaudio::Error) -> BuildStreamError {
match err {
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat
| coreaudio::Error::NoKnownSubtype
| coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported)
| coreaudio::Error::AudioCodec(_)
| coreaudio::Error::AudioFormat(_) => BuildStreamError::StreamConfigNotSupported,
_ => BuildStreamError::DeviceNotAvailable,
}
}
}
impl From<coreaudio::Error> for SupportedStreamConfigsError {
fn from(err: coreaudio::Error) -> SupportedStreamConfigsError {
let description = format!("{}", err);
let err = BackendSpecificError { description };
// Check for possible DeviceNotAvailable variant
SupportedStreamConfigsError::BackendSpecific { err }
}
}
impl From<coreaudio::Error> for DefaultStreamConfigError {
fn from(err: coreaudio::Error) -> DefaultStreamConfigError {
let description = format!("{}", err);
let err = BackendSpecificError { description };
// Check for possible DeviceNotAvailable variant
DefaultStreamConfigError::BackendSpecific { err }
}
}
-422
View File
@@ -1,422 +0,0 @@
use js_sys::Float32Array;
use std::time::Duration;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::AudioContext;
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
};
// The emscripten backend currently works by instantiating an `AudioContext` object per `Stream`.
// Creating a stream creates a new `AudioContext`. Destroying a stream destroys it. Creation of a
// `Host` instance initializes the `stdweb` context.
/// The default emscripten host type.
#[derive(Debug)]
pub struct Host;
/// Content is false if the iterator is empty.
pub struct Devices(bool);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
#[wasm_bindgen]
#[derive(Clone)]
pub struct Stream {
// A reference to an `AudioContext` object.
audio_ctxt: AudioContext,
}
// Index within the `streams` array of the events loop.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StreamId(usize);
pub type SupportedInputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
const MIN_CHANNELS: u16 = 1;
const MAX_CHANNELS: u16 = 32;
const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000);
const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000);
const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100);
const MIN_BUFFER_SIZE: u32 = 1;
const MAX_BUFFER_SIZE: u32 = u32::MAX;
const DEFAULT_BUFFER_SIZE: usize = 2048;
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl Devices {
fn new() -> Result<Self, DevicesError> {
Ok(Self::default())
}
}
impl Device {
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Ok("Default Device".to_owned())
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
unimplemented!();
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
let buffer_size = SupportedBufferSize::Range {
min: MIN_BUFFER_SIZE,
max: MAX_BUFFER_SIZE,
};
let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: MIN_SAMPLE_RATE,
max_sample_rate: MAX_SAMPLE_RATE,
buffer_size: buffer_size.clone(),
sample_format: SUPPORTED_SAMPLE_FORMAT,
})
.collect();
Ok(configs.into_iter())
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
unimplemented!();
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
const EXPECT: &str = "expected at least one valid webaudio stream config";
let config = self
.supported_output_configs()
.expect(EXPECT)
.max_by(|a, b| a.cmp_default_heuristics(b))
.unwrap()
.with_sample_rate(DEFAULT_SAMPLE_RATE);
Ok(config)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
// Assume this host is always available on emscripten.
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
_config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
unimplemented!()
}
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
if !valid_config(config, sample_format) {
return Err(BuildStreamError::StreamConfigNotSupported);
}
let buffer_size_frames = match config.buffer_size {
BufferSize::Fixed(v) => {
if v == 0 {
return Err(BuildStreamError::StreamConfigNotSupported);
} else {
v as usize
}
}
BufferSize::Default => DEFAULT_BUFFER_SIZE,
};
// Create the stream.
let audio_ctxt = AudioContext::new().expect("webaudio is not present on this system");
let stream = Stream { audio_ctxt };
// Use `set_timeout` to invoke a Rust callback repeatedly.
//
// The job of this callback is to fill the content of the audio buffers.
//
// See also: The call to `set_timeout` at the end of the `audio_callback_fn` which creates
// the loop.
set_timeout(
10,
stream.clone(),
data_callback,
config,
sample_format,
buffer_size_frames as u32,
);
Ok(stream)
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
let future = JsFuture::from(
self.audio_ctxt
.resume()
.expect("Could not resume the stream"),
);
spawn_local(async {
match future.await {
Ok(value) => assert!(value.is_undefined()),
Err(value) => panic!("AudioContext.resume() promise was rejected: {:?}", value),
}
});
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
let future = JsFuture::from(
self.audio_ctxt
.suspend()
.expect("Could not suspend the stream"),
);
spawn_local(async {
match future.await {
Ok(value) => assert!(value.is_undefined()),
Err(value) => panic!("AudioContext.suspend() promise was rejected: {:?}", value),
}
});
Ok(())
}
}
fn audio_callback_fn<D>(
mut data_callback: D,
) -> impl FnOnce(Stream, StreamConfig, SampleFormat, u32)
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
{
|stream, config, sample_format, buffer_size_frames| {
let sample_rate = config.sample_rate.0;
let buffer_size_samples = buffer_size_frames * config.channels as u32;
let audio_ctxt = &stream.audio_ctxt;
// TODO: We should be re-using a buffer.
let mut temporary_buffer = vec![0f32; buffer_size_samples as usize];
{
let len = temporary_buffer.len();
let data = temporary_buffer.as_mut_ptr() as *mut ();
let mut data = unsafe { Data::from_parts(data, len, sample_format) };
let now_secs: f64 = audio_ctxt.current_time();
let callback = crate::StreamInstant::from_secs_f64(now_secs);
// TODO: Use proper latency instead. Currently, unsupported on most browsers though, so
// we estimate based on buffer size instead. Probably should use this, but it's only
// supported by firefox (2020-04-28).
// let latency_secs: f64 = audio_ctxt.outputLatency.try_into().unwrap();
let buffer_duration = frames_to_duration(len, sample_rate as usize);
let playback = callback
.add(buffer_duration)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
}
let typed_array: Float32Array = temporary_buffer.as_slice().into();
debug_assert_eq!(temporary_buffer.len() % config.channels as usize, 0);
let src_buffer = Float32Array::new(typed_array.buffer().as_ref());
let context = audio_ctxt;
let buffer = context
.create_buffer(
config.channels as u32,
buffer_size_frames as u32,
sample_rate as f32,
)
.expect("Buffer could not be created");
for channel in 0..config.channels {
let mut buffer_content = buffer
.get_channel_data(channel as u32)
.expect("Should be impossible");
for (i, buffer_content_item) in buffer_content.iter_mut().enumerate() {
*buffer_content_item =
src_buffer.get_index(i as u32 * config.channels as u32 + channel as u32);
}
}
let node = context
.create_buffer_source()
.expect("The buffer source node could not be created");
node.set_buffer(Some(&buffer));
context
.destination()
.connect_with_audio_node(&node)
.expect("Could not connect the audio node to the destination");
node.start().expect("Could not start the audio node");
// TODO: handle latency better ; right now we just use setInterval with the amount of sound
// data that is in each buffer ; this is obviously bad, and also the schedule is too tight
// and there may be underflows
set_timeout(
1000 * buffer_size_frames as i32 / sample_rate as i32,
stream.clone().clone(),
data_callback,
&config,
sample_format,
buffer_size_frames as u32,
);
}
}
fn set_timeout<D>(
time: i32,
stream: Stream,
data_callback: D,
config: &StreamConfig,
sample_format: SampleFormat,
buffer_size_frames: u32,
) where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
{
let window = web_sys::window().expect("Not in a window somehow?");
window
.set_timeout_with_callback_and_timeout_and_arguments_4(
&Closure::once_into_js(audio_callback_fn(data_callback))
.dyn_ref::<js_sys::Function>()
.expect("The function was somehow not a function"),
time,
&stream.into(),
&((*config).clone()).into(),
&Closure::once_into_js(move || sample_format),
&buffer_size_frames.into(),
)
.expect("The timeout could not be set");
}
impl Default for Devices {
fn default() -> Devices {
// We produce an empty iterator if the WebAudio API isn't available.
Devices(is_webaudio_available())
}
}
impl Iterator for Devices {
type Item = Device;
#[inline]
fn next(&mut self) -> Option<Device> {
if self.0 {
self.0 = false;
Some(Device)
} else {
None
}
}
}
#[inline]
fn default_input_device() -> Option<Device> {
unimplemented!();
}
#[inline]
fn default_output_device() -> Option<Device> {
if is_webaudio_available() {
Some(Device)
} else {
None
}
}
// Detects whether the `AudioContext` global variable is available.
fn is_webaudio_available() -> bool {
AudioContext::new().is_ok()
}
// Whether or not the given stream configuration is valid for building a stream.
fn valid_config(conf: &StreamConfig, sample_format: SampleFormat) -> bool {
conf.channels <= MAX_CHANNELS
&& conf.channels >= MIN_CHANNELS
&& conf.sample_rate <= MAX_SAMPLE_RATE
&& conf.sample_rate >= MIN_SAMPLE_RATE
&& sample_format == SUPPORTED_SAMPLE_FORMAT
}
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: usize) -> std::time::Duration {
let secsf = frames as f64 / rate as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
-268
View File
@@ -1,268 +0,0 @@
use crate::traits::DeviceTrait;
use crate::{
BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError,
InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};
use std::hash::{Hash, Hasher};
use std::time::Duration;
use super::stream::Stream;
use super::JACK_SAMPLE_FORMAT;
pub type SupportedInputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
const DEFAULT_NUM_CHANNELS: u16 = 2;
const DEFAULT_SUPPORTED_CHANNELS: [u16; 10] = [1, 2, 4, 6, 8, 16, 24, 32, 48, 64];
/// If a device is for input or output.
/// Until we have duplex stream support JACK clients and CPAL devices for JACK will be either input or output.
#[derive(Clone, Debug)]
pub enum DeviceType {
InputDevice,
OutputDevice,
}
#[derive(Clone, Debug)]
pub struct Device {
name: String,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
device_type: DeviceType,
start_server_automatically: bool,
connect_ports_automatically: bool,
}
impl Device {
fn new_device(
name: String,
connect_ports_automatically: bool,
start_server_automatically: bool,
device_type: DeviceType,
) -> Result<Self, String> {
// ClientOptions are bit flags that you can set with the constants provided
let client_options = super::get_client_options(start_server_automatically);
// Create a dummy client to find out the sample rate of the server to be able to provide it as a possible config.
// This client will be dropped, and a new one will be created when making the stream.
// This is a hack due to the fact that the Client must be moved to create the AsyncClient.
match super::get_client(&name, client_options) {
Ok(client) => Ok(Device {
// The name given to the client by JACK, could potentially be different from the name supplied e.g.if there is a name collision
name: client.name().to_string(),
sample_rate: SampleRate(client.sample_rate() as u32),
buffer_size: SupportedBufferSize::Range {
min: client.buffer_size(),
max: client.buffer_size(),
},
device_type,
start_server_automatically,
connect_ports_automatically,
}),
Err(e) => Err(e),
}
}
pub fn default_output_device(
name: &str,
connect_ports_automatically: bool,
start_server_automatically: bool,
) -> Result<Self, String> {
let output_client_name = format!("{}_out", name);
Device::new_device(
output_client_name,
connect_ports_automatically,
start_server_automatically,
DeviceType::OutputDevice,
)
}
pub fn default_input_device(
name: &str,
connect_ports_automatically: bool,
start_server_automatically: bool,
) -> Result<Self, String> {
let input_client_name = format!("{}_in", name);
Device::new_device(
input_client_name,
connect_ports_automatically,
start_server_automatically,
DeviceType::InputDevice,
)
}
pub fn default_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let channels = DEFAULT_NUM_CHANNELS;
let sample_rate = self.sample_rate;
let buffer_size = self.buffer_size.clone();
// The sample format for JACK audio ports is always "32-bit float mono audio" in the current implementation.
// Custom formats are allowed within JACK, but this is of niche interest.
// The format can be found programmatically by calling jack::PortSpec::port_type() on a created port.
let sample_format = JACK_SAMPLE_FORMAT;
Ok(SupportedStreamConfig {
channels,
sample_rate,
buffer_size,
sample_format,
})
}
pub fn supported_configs(&self) -> Vec<SupportedStreamConfigRange> {
let f = match self.default_config() {
Err(_) => return vec![],
Ok(f) => f,
};
let mut supported_configs = vec![];
for &channels in DEFAULT_SUPPORTED_CHANNELS.iter() {
supported_configs.push(SupportedStreamConfigRange {
channels,
min_sample_rate: f.sample_rate,
max_sample_rate: f.sample_rate,
buffer_size: f.buffer_size.clone(),
sample_format: f.sample_format,
});
}
supported_configs
}
pub fn is_input(&self) -> bool {
matches!(self.device_type, DeviceType::InputDevice)
}
pub fn is_output(&self) -> bool {
matches!(self.device_type, DeviceType::OutputDevice)
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.name.clone())
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Ok(self.supported_configs().into_iter())
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Ok(self.supported_configs().into_iter())
}
/// Returns the default input config
/// The sample format for JACK audio ports is always "32-bit float mono audio" unless using a custom type.
/// The sample rate is set by the JACK server.
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config()
}
/// Returns the default output config
/// The sample format for JACK audio ports is always "32-bit float mono audio" unless using a custom type.
/// The sample rate is set by the JACK server.
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config()
}
fn build_input_stream_raw<D, E>(
&self,
conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
if let DeviceType::OutputDevice = &self.device_type {
// Trying to create an input stream from an output device
return Err(BuildStreamError::StreamConfigNotSupported);
}
if conf.sample_rate != self.sample_rate || sample_format != JACK_SAMPLE_FORMAT {
return Err(BuildStreamError::StreamConfigNotSupported);
}
// The settings should be fine, create a Client
let client_options = super::get_client_options(self.start_server_automatically);
let client;
match super::get_client(&self.name, client_options) {
Ok(c) => client = c,
Err(e) => {
return Err(BuildStreamError::BackendSpecific {
err: BackendSpecificError { description: e },
})
}
};
let mut stream = Stream::new_input(client, conf.channels, data_callback, error_callback);
if self.connect_ports_automatically {
stream.connect_to_system_inputs();
}
Ok(stream)
}
fn build_output_stream_raw<D, E>(
&self,
conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
if let DeviceType::InputDevice = &self.device_type {
// Trying to create an output stream from an input device
return Err(BuildStreamError::StreamConfigNotSupported);
}
if conf.sample_rate != self.sample_rate || sample_format != JACK_SAMPLE_FORMAT {
return Err(BuildStreamError::StreamConfigNotSupported);
}
// The settings should be fine, create a Client
let client_options = super::get_client_options(self.start_server_automatically);
let client;
match super::get_client(&self.name, client_options) {
Ok(c) => client = c,
Err(e) => {
return Err(BuildStreamError::BackendSpecific {
err: BackendSpecificError { description: e },
})
}
};
let mut stream = Stream::new_output(client, conf.channels, data_callback, error_callback);
if self.connect_ports_automatically {
stream.connect_to_system_outputs();
}
Ok(stream)
}
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
// Device::name() can never fail in this implementation
self.name().unwrap() == other.name().unwrap()
}
}
impl Eq for Device {}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().unwrap().hash(state);
}
}
-180
View File
@@ -1,180 +0,0 @@
extern crate jack;
use crate::traits::HostTrait;
use crate::{DevicesError, SampleFormat, SupportedStreamConfigRange};
mod device;
pub use self::device::Device;
pub use self::stream::Stream;
mod stream;
const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
pub type SupportedInputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type Devices = std::vec::IntoIter<Device>;
/// The JACK Host type
#[derive(Debug)]
pub struct Host {
/// The name that the client will have in JACK.
/// Until we have duplex streams two clients will be created adding "out" or "in" to the name
/// since names have to be unique.
name: String,
/// If ports are to be connected to the system (soundcard) ports automatically (default is true).
connect_ports_automatically: bool,
/// If the JACK server should be started automatically if it isn't already when creating a Client (default is false).
start_server_automatically: bool,
/// A list of the devices that have been created from this Host.
devices_created: Vec<Device>,
}
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
let mut host = Host {
name: "cpal_client".to_owned(),
connect_ports_automatically: true,
start_server_automatically: false,
devices_created: vec![],
};
// Devices don't exist for JACK, they have to be created
host.initialize_default_devices();
Ok(host)
}
/// Set whether the ports should automatically be connected to system
/// (default is 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)
pub fn set_start_server_automatically(&mut self, do_start_server: bool) {
self.start_server_automatically = do_start_server;
}
pub fn input_device_with_name(&mut self, name: &str) -> Option<Device> {
self.name = name.to_owned();
self.default_input_device()
}
pub fn output_device_with_name(&mut self, name: &str) -> Option<Device> {
self.name = name.to_owned();
self.default_output_device()
}
fn initialize_default_devices(&mut self) {
let in_device_res = Device::default_input_device(
&self.name,
self.connect_ports_automatically,
self.start_server_automatically,
);
match in_device_res {
Ok(device) => self.devices_created.push(device),
Err(err) => {
println!("{}", err);
}
}
let out_device_res = Device::default_output_device(
&self.name,
self.connect_ports_automatically,
self.start_server_automatically,
);
match out_device_res {
Ok(device) => self.devices_created.push(device),
Err(err) => {
println!("{}", err);
}
}
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
/// JACK is available if
/// - the jack feature flag is set
/// - libjack is installed (wouldn't compile without it)
/// - the JACK server can be started
///
/// If the code compiles the necessary jack libraries are installed.
/// There is no way to know if the user has set up a correct JACK configuration e.g. with qjackctl.
/// Users can choose to automatically start the server if it isn't already started when creating a client
/// so checking if the server is running could give a false negative in some use cases.
/// For these reasons this function should always return true.
fn is_available() -> bool {
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Ok(self.devices_created.clone().into_iter())
}
fn default_input_device(&self) -> Option<Self::Device> {
for device in &self.devices_created {
if device.is_input() {
return Some(device.clone());
}
}
None
}
fn default_output_device(&self) -> Option<Self::Device> {
for device in &self.devices_created {
if device.is_output() {
return Some(device.clone());
}
}
None
}
}
fn get_client_options(start_server_automatically: bool) -> jack::ClientOptions {
let mut client_options = jack::ClientOptions::empty();
client_options.set(
jack::ClientOptions::NO_START_SERVER,
!start_server_automatically,
);
client_options
}
fn get_client(name: &str, client_options: jack::ClientOptions) -> Result<jack::Client, String> {
let c_res = jack::Client::new(name, client_options);
match c_res {
Ok((client, status)) => {
// The ClientStatus can tell us many things
if status.intersects(jack::ClientStatus::SERVER_ERROR) {
return Err(String::from(
"There was an error communicating with the JACK server!",
));
} else if status.intersects(jack::ClientStatus::SERVER_FAILED) {
return Err(String::from("Could not connect to the JACK server!"));
} else if status.intersects(jack::ClientStatus::VERSION_ERROR) {
return Err(String::from(
"Error connecting to JACK server: Client's protocol version does not match!",
));
} else if status.intersects(jack::ClientStatus::INIT_FAILURE) {
return Err(String::from(
"Error connecting to JACK server: Unable to initialize client!",
));
} else if status.intersects(jack::ClientStatus::SHM_FAILURE) {
return Err(String::from(
"Error connecting to JACK server: Unable to access shared memory!",
));
} else if status.intersects(jack::ClientStatus::NO_SUCH_CLIENT) {
return Err(String::from(
"Error connecting to JACK server: Requested client does not exist!",
));
} else if status.intersects(jack::ClientStatus::INVALID_OPTION) {
return Err(String::from("Error connecting to JACK server: The operation contained an invalid or unsupported option!"));
}
return Ok(client);
}
Err(e) => {
return Err(format!("Failed to open client because of error: {:?}", e));
}
}
}
-463
View File
@@ -1,463 +0,0 @@
use crate::traits::StreamTrait;
use crate::ChannelCount;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use crate::{
BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleRate, StreamError,
};
use super::JACK_SAMPLE_FORMAT;
type ErrorCallbackPtr = Arc<Mutex<dyn FnMut(StreamError) + Send + 'static>>;
pub struct Stream {
// TODO: It might be faster to send a message when playing/pausing than to check this every iteration
playing: Arc<AtomicBool>,
async_client: jack::AsyncClient<JackNotificationHandler, LocalProcessHandler>,
// Port names are stored in order to connect them to other ports in jack automatically
input_port_names: Vec<String>,
output_port_names: Vec<String>,
}
impl Stream {
// TODO: Return error messages
pub fn new_input<D, E>(
client: jack::Client,
channels: ChannelCount,
data_callback: D,
mut error_callback: E,
) -> Stream
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let mut ports = vec![];
let mut port_names: Vec<String> = vec![];
// Create ports
for i in 0..channels {
let port_try = client.register_port(&format!("in_{}", i), jack::AudioIn::default());
match port_try {
Ok(port) => {
// Get the port name in order to later connect it automatically
if let Ok(port_name) = port.name() {
port_names.push(port_name);
}
// Store the port into a Vec to move to the ProcessHandler
ports.push(port);
}
Err(e) => {
// If port creation failed, send the error back via the error_callback
error_callback(
BackendSpecificError {
description: e.to_string(),
}
.into(),
);
}
}
}
let playing = Arc::new(AtomicBool::new(true));
let error_callback_ptr = Arc::new(Mutex::new(error_callback)) as ErrorCallbackPtr;
let input_process_handler = LocalProcessHandler::new(
vec![],
ports,
SampleRate(client.sample_rate() as u32),
client.buffer_size() as usize,
Some(Box::new(data_callback)),
None,
playing.clone(),
Arc::clone(&error_callback_ptr),
);
let notification_handler = JackNotificationHandler::new(error_callback_ptr);
let async_client = client
.activate_async(notification_handler, input_process_handler)
.unwrap();
Stream {
playing,
async_client,
input_port_names: port_names,
output_port_names: vec![],
}
}
pub fn new_output<D, E>(
client: jack::Client,
channels: ChannelCount,
data_callback: D,
mut error_callback: E,
) -> Stream
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let mut ports = vec![];
let mut port_names: Vec<String> = vec![];
// Create ports
for i in 0..channels {
let port_try = client.register_port(&format!("out_{}", i), jack::AudioOut::default());
match port_try {
Ok(port) => {
// Get the port name in order to later connect it automatically
if let Ok(port_name) = port.name() {
port_names.push(port_name);
}
// Store the port into a Vec to move to the ProcessHandler
ports.push(port);
}
Err(e) => {
// If port creation failed, send the error back via the error_callback
error_callback(
BackendSpecificError {
description: e.to_string(),
}
.into(),
);
}
}
}
let playing = Arc::new(AtomicBool::new(true));
let error_callback_ptr = Arc::new(Mutex::new(error_callback)) as ErrorCallbackPtr;
let output_process_handler = LocalProcessHandler::new(
ports,
vec![],
SampleRate(client.sample_rate() as u32),
client.buffer_size() as usize,
None,
Some(Box::new(data_callback)),
playing.clone(),
Arc::clone(&error_callback_ptr),
);
let notification_handler = JackNotificationHandler::new(error_callback_ptr);
let async_client = client
.activate_async(notification_handler, output_process_handler)
.unwrap();
Stream {
playing,
async_client,
input_port_names: vec![],
output_port_names: port_names,
}
}
/// Connect to the standard system outputs in jack, system:playback_1 and system:playback_2
/// This has to be done after the client is activated, doing it just after creating the ports doesn't work.
pub fn connect_to_system_outputs(&mut self) {
// Get the system ports
let system_ports = self.async_client.as_client().ports(
Some("system:playback_.*"),
None,
jack::PortFlags::empty(),
);
// Connect outputs from this client to the system playback inputs
for i in 0..self.output_port_names.len() {
if i >= system_ports.len() {
break;
}
match self
.async_client
.as_client()
.connect_ports_by_name(&self.output_port_names[i], &system_ports[i])
{
Ok(_) => (),
Err(e) => println!("Unable to connect to port with error {}", e),
}
}
}
/// Connect to the standard system outputs in jack, system:capture_1 and system:capture_2
/// This has to be done after the client is activated, doing it just after creating the ports doesn't work.
pub fn connect_to_system_inputs(&mut self) {
// Get the system ports
let system_ports = self.async_client.as_client().ports(
Some("system:capture_.*"),
None,
jack::PortFlags::empty(),
);
// Connect outputs from this client to the system playback inputs
for i in 0..self.input_port_names.len() {
if i >= system_ports.len() {
break;
}
match self
.async_client
.as_client()
.connect_ports_by_name(&system_ports[i], &self.input_port_names[i])
{
Ok(_) => (),
Err(e) => println!("Unable to connect to port with error {}", e),
}
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
self.playing.store(true, Ordering::SeqCst);
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
self.playing.store(false, Ordering::SeqCst);
Ok(())
}
}
struct LocalProcessHandler {
/// No new ports are allowed to be created after the creation of the LocalProcessHandler as that would invalidate the buffer sizes
out_ports: Vec<jack::Port<jack::AudioOut>>,
in_ports: Vec<jack::Port<jack::AudioIn>>,
sample_rate: SampleRate,
buffer_size: usize,
input_data_callback: Option<Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>>,
output_data_callback: Option<Box<dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static>>,
// JACK audio samples are 32-bit float (unless you do some custom dark magic)
temp_input_buffer: Vec<f32>,
temp_output_buffer: Vec<f32>,
playing: Arc<AtomicBool>,
creation_timestamp: std::time::Instant,
/// This should not be called on `process`, only on `buffer_size` because it can block.
error_callback_ptr: ErrorCallbackPtr,
}
impl LocalProcessHandler {
fn new(
out_ports: Vec<jack::Port<jack::AudioOut>>,
in_ports: Vec<jack::Port<jack::AudioIn>>,
sample_rate: SampleRate,
buffer_size: usize,
input_data_callback: Option<Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>>,
output_data_callback: Option<
Box<dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static>,
>,
playing: Arc<AtomicBool>,
error_callback_ptr: ErrorCallbackPtr,
) -> Self {
// These may be reallocated in the `buffer_size` callback.
let temp_input_buffer = vec![0.0; in_ports.len() * buffer_size];
let temp_output_buffer = vec![0.0; out_ports.len() * buffer_size];
LocalProcessHandler {
out_ports,
in_ports,
sample_rate,
buffer_size,
input_data_callback,
output_data_callback,
temp_input_buffer,
temp_output_buffer,
playing,
creation_timestamp: std::time::Instant::now(),
error_callback_ptr,
}
}
}
fn temp_buffer_to_data(temp_input_buffer: &mut Vec<f32>, total_buffer_size: usize) -> Data {
let slice = &temp_input_buffer[0..total_buffer_size];
let data = slice.as_ptr() as *mut ();
let len = total_buffer_size;
let data = unsafe { Data::from_parts(data, len, JACK_SAMPLE_FORMAT) };
data
}
impl jack::ProcessHandler for LocalProcessHandler {
fn process(&mut self, _: &jack::Client, process_scope: &jack::ProcessScope) -> jack::Control {
if !self.playing.load(Ordering::SeqCst) {
return jack::Control::Continue;
}
// This should be equal to self.buffer_size, but the implementation will
// work even if it is less. Will panic in `temp_buffer_to_data` if greater.
let current_frame_count = process_scope.n_frames() as usize;
// Get timestamp data
let cycle_times = process_scope.cycle_times();
let current_start_usecs = match cycle_times {
Ok(times) => times.current_usecs,
Err(_) => {
// jack was unable to get the current time information
// Fall back to using Instants
let now = std::time::Instant::now();
let duration = now.duration_since(self.creation_timestamp);
duration.as_micros() as u64
}
};
let start_cycle_instant = micros_to_stream_instant(current_start_usecs);
let start_callback_instant = start_cycle_instant
.add(frames_to_duration(
process_scope.frames_since_cycle_start() as usize,
self.sample_rate,
))
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
if let Some(input_callback) = &mut self.input_data_callback {
// Let's get the data from the input ports and run the callback
let num_in_channels = self.in_ports.len();
// Read the data from the input ports into the temporary buffer
// Go through every channel and store its data in the temporary input buffer
for ch_ix in 0..num_in_channels {
let input_channel = &self.in_ports[ch_ix].as_slice(process_scope);
for i in 0..current_frame_count {
self.temp_input_buffer[ch_ix + i * num_in_channels] = input_channel[i];
}
}
// Create a slice of exactly current_frame_count frames
let data = temp_buffer_to_data(
&mut self.temp_input_buffer,
current_frame_count * num_in_channels,
);
// Create timestamp
let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize;
let duration_since_cycle_start =
frames_to_duration(frames_since_cycle_start, self.sample_rate);
let callback = start_callback_instant
.add(duration_since_cycle_start)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let capture = start_callback_instant;
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = crate::InputCallbackInfo { timestamp };
input_callback(&data, &info);
}
if let Some(output_callback) = &mut self.output_data_callback {
let num_out_channels = self.out_ports.len();
// Create a slice of exactly current_frame_count frames
let mut data = temp_buffer_to_data(
&mut self.temp_output_buffer,
current_frame_count * num_out_channels,
);
// Create timestamp
let frames_since_cycle_start = process_scope.frames_since_cycle_start() as usize;
let duration_since_cycle_start =
frames_to_duration(frames_since_cycle_start, self.sample_rate);
let callback = start_callback_instant
.add(duration_since_cycle_start)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let buffer_duration = frames_to_duration(current_frame_count, self.sample_rate);
let playback = start_cycle_instant
.add(buffer_duration)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = crate::OutputCallbackInfo { timestamp };
output_callback(&mut data, &info);
// Deinterlace
for ch_ix in 0..num_out_channels {
let output_channel = &mut self.out_ports[ch_ix].as_mut_slice(process_scope);
for i in 0..current_frame_count {
output_channel[i] = self.temp_output_buffer[ch_ix + i * num_out_channels];
}
}
}
// Continue as normal
jack::Control::Continue
}
fn buffer_size(&mut self, _: &jack::Client, size: jack::Frames) -> jack::Control {
// The `buffer_size` callback is actually called on the process thread, but
// it does not need to be suitable for real-time use. Thus we can simply allocate
// new buffers here. It is also fine to call the error callback.
// Details: https://github.com/RustAudio/rust-jack/issues/137
let new_size = size as usize;
if new_size != self.buffer_size {
self.buffer_size = new_size;
self.temp_input_buffer = vec![0.0; self.in_ports.len() * new_size];
self.temp_output_buffer = vec![0.0; self.out_ports.len() * new_size];
let description = format!("buffer size changed to: {}", new_size);
if let Ok(mut mutex_guard) = self.error_callback_ptr.lock() {
let err = &mut *mutex_guard;
err(BackendSpecificError { description }.into());
}
}
jack::Control::Continue
}
}
fn micros_to_stream_instant(micros: u64) -> crate::StreamInstant {
let nanos = micros * 1000;
let secs = micros / 1_000_000;
let subsec_nanos = nanos - secs * 1_000_000_000;
crate::StreamInstant::new(secs as i64, subsec_nanos as u32)
}
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
/// Receives notifications from the JACK server. It is unclear if this may be run concurrent with itself under JACK2 specs
/// so it needs to be Sync.
struct JackNotificationHandler {
error_callback_ptr: ErrorCallbackPtr,
init_sample_rate_flag: Arc<AtomicBool>,
}
impl JackNotificationHandler {
pub fn new(error_callback_ptr: ErrorCallbackPtr) -> Self {
JackNotificationHandler {
error_callback_ptr,
init_sample_rate_flag: Arc::new(AtomicBool::new(false)),
}
}
fn send_error(&mut self, description: String) {
// This thread isn't the audio thread, it's fine to block
if let Ok(mut mutex_guard) = self.error_callback_ptr.lock() {
let err = &mut *mutex_guard;
err(BackendSpecificError { description }.into());
}
}
}
impl jack::NotificationHandler for JackNotificationHandler {
fn shutdown(&mut self, _status: jack::ClientStatus, reason: &str) {
self.send_error(format!("JACK was shut down for reason: {}", reason));
}
fn sample_rate(&mut self, _: &jack::Client, srate: jack::Frames) -> jack::Control {
match self.init_sample_rate_flag.load(Ordering::SeqCst) {
false => {
// One of these notifications is sent every time a client is started.
self.init_sample_rate_flag.store(true, Ordering::SeqCst);
jack::Control::Continue
}
true => {
self.send_error(format!("sample rate changed to: {}", srate));
// Since CPAL currently has no way of signaling a sample rate change in order to make
// all necessary changes that would bring we choose to quit.
jack::Control::Quit
}
}
}
fn xrun(&mut self, _: &jack::Client) -> jack::Control {
self.send_error(String::from("xrun (buffer over or under run)"));
jack::Control::Continue
}
}
-30
View File
@@ -1,30 +0,0 @@
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
))]
pub(crate) mod alsa;
#[cfg(all(windows, feature = "asio"))]
pub(crate) mod asio;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub(crate) mod coreaudio;
#[cfg(target_os = "emscripten")]
pub(crate) mod emscripten;
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
),
feature = "jack"
))]
pub(crate) mod jack;
pub(crate) mod null;
#[cfg(target_os = "android")]
pub(crate) mod oboe;
#[cfg(windows)]
pub(crate) mod wasapi;
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
pub(crate) mod webaudio;
-160
View File
@@ -1,160 +0,0 @@
use std::time::Duration;
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};
#[derive(Default)]
pub struct Devices;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
pub struct Host;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Stream;
pub struct SupportedInputConfigs;
pub struct SupportedOutputConfigs;
impl Host {
#[allow(dead_code)]
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices)
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Ok("null".to_owned())
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
unimplemented!()
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
unimplemented!()
}
#[inline]
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
unimplemented!()
}
#[inline]
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
unimplemented!()
}
fn build_input_stream_raw<D, E>(
&self,
_config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
unimplemented!()
}
/// Create an output stream.
fn build_output_stream_raw<D, E>(
&self,
_config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
unimplemented!()
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
false
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Device> {
None
}
fn default_output_device(&self) -> Option<Device> {
None
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
unimplemented!()
}
fn pause(&self) -> Result<(), PauseStreamError> {
unimplemented!()
}
}
impl Iterator for Devices {
type Item = Device;
#[inline]
fn next(&mut self) -> Option<Device> {
None
}
}
impl Iterator for SupportedInputConfigs {
type Item = SupportedStreamConfigRange;
#[inline]
fn next(&mut self) -> Option<SupportedStreamConfigRange> {
None
}
}
impl Iterator for SupportedOutputConfigs {
type Item = SupportedStreamConfigRange;
#[inline]
fn next(&mut self) -> Option<SupportedStreamConfigRange> {
None
}
}
-62
View File
@@ -1,62 +0,0 @@
use std::sync::Arc;
extern crate jni;
use self::jni::Executor;
use self::jni::{errors::Result as JResult, JNIEnv, JavaVM};
// constants from android.media.AudioFormat
pub const ENCODING_PCM_16BIT: i32 = 2;
pub const ENCODING_PCM_FLOAT: i32 = 4;
pub const CHANNEL_OUT_MONO: i32 = 4;
pub const CHANNEL_OUT_STEREO: i32 = 12;
fn with_attached<F, R>(closure: F) -> JResult<R>
where
F: FnOnce(&mut JNIEnv) -> JResult<R>,
{
let android_context = ndk_context::android_context();
let vm = Arc::new(unsafe { JavaVM::from_raw(android_context.vm().cast())? });
Executor::new(vm).with_attached(|env| closure(env))
}
fn get_min_buffer_size(
class: &'static str,
sample_rate: i32,
channel_mask: i32,
format: i32,
) -> i32 {
// Unwrapping everything because these operations are not expected to fail
// or throw exceptions. Android returns negative values for invalid parameters,
// which is what we expect.
with_attached(|env| {
let class = env.find_class(class).unwrap();
env.call_static_method(
class,
"getMinBufferSize",
"(III)I",
&[sample_rate.into(), channel_mask.into(), format.into()],
)
.unwrap()
.i()
})
.unwrap()
}
pub fn get_audio_track_min_buffer_size(sample_rate: i32, channel_mask: i32, format: i32) -> i32 {
get_min_buffer_size(
"android/media/AudioTrack",
sample_rate,
channel_mask,
format,
)
}
pub fn get_audio_record_min_buffer_size(sample_rate: i32, channel_mask: i32, format: i32) -> i32 {
get_min_buffer_size(
"android/media/AudioRecord",
sample_rate,
channel_mask,
format,
)
}
-82
View File
@@ -1,82 +0,0 @@
use std::convert::TryInto;
use std::time::Duration;
extern crate oboe;
use crate::{
BackendSpecificError, BuildStreamError, PauseStreamError, PlayStreamError, StreamError,
StreamInstant,
};
pub fn to_stream_instant(duration: Duration) -> StreamInstant {
StreamInstant::new(
duration.as_secs().try_into().unwrap(),
duration.subsec_nanos(),
)
}
pub fn stream_instant<T: oboe::AudioStreamSafe + ?Sized>(stream: &mut T) -> StreamInstant {
const CLOCK_MONOTONIC: i32 = 1;
let ts = stream
.get_timestamp(CLOCK_MONOTONIC)
.unwrap_or(oboe::FrameTimestamp {
position: 0,
timestamp: 0,
});
to_stream_instant(Duration::from_nanos(ts.timestamp as u64))
}
impl From<oboe::Error> for StreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
.into(),
}
}
}
impl From<oboe::Error> for PlayStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
.into(),
}
}
}
impl From<oboe::Error> for PauseStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
.into(),
}
}
}
impl From<oboe::Error> for BuildStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
NoFreeHandles => Self::StreamIdOverflow,
InvalidFormat | InvalidRate => Self::StreamConfigNotSupported,
IllegalArgument => Self::InvalidArgument,
e => (BackendSpecificError {
description: e.to_string(),
})
.into(),
}
}
}
-90
View File
@@ -1,90 +0,0 @@
use std::marker::PhantomData;
use std::time::Instant;
extern crate oboe;
use super::convert::{stream_instant, to_stream_instant};
use crate::{Data, InputCallbackInfo, InputStreamTimestamp, SizedSample, StreamError};
pub struct CpalInputCallback<I, C> {
data_cb: Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>,
error_cb: Box<dyn FnMut(StreamError) + Send + 'static>,
created: Instant,
phantom_channel: PhantomData<C>,
phantom_input: PhantomData<I>,
}
impl<I, C> CpalInputCallback<I, C> {
pub fn new<D, E>(data_cb: D, error_cb: E) -> Self
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Self {
data_cb: Box::new(data_cb),
error_cb: Box::new(error_cb),
created: Instant::now(),
phantom_channel: PhantomData,
phantom_input: PhantomData,
}
}
fn make_callback_info(
&self,
audio_stream: &mut dyn oboe::AudioInputStreamSafe,
) -> InputCallbackInfo {
InputCallbackInfo {
timestamp: InputStreamTimestamp {
callback: to_stream_instant(self.created.elapsed()),
capture: stream_instant(audio_stream),
},
}
}
}
impl<T: SizedSample, C: oboe::IsChannelCount> oboe::AudioInputCallback for CpalInputCallback<T, C>
where
(T, C): oboe::IsFrameType,
{
type FrameType = (T, C);
fn on_error_before_close(
&mut self,
_audio_stream: &mut dyn oboe::AudioInputStreamSafe,
error: oboe::Error,
) {
(self.error_cb)(StreamError::from(error))
}
fn on_error_after_close(
&mut self,
_audio_stream: &mut dyn oboe::AudioInputStreamSafe,
error: oboe::Error,
) {
(self.error_cb)(StreamError::from(error))
}
fn on_audio_ready(
&mut self,
audio_stream: &mut dyn oboe::AudioInputStreamSafe,
audio_data: &[<<Self as oboe::AudioInputCallback>::FrameType as oboe::IsFrameType>::Type],
) -> oboe::DataCallbackResult {
let cb_info = self.make_callback_info(audio_stream);
let channel_count = if C::CHANNEL_COUNT == oboe::ChannelCount::Mono {
1
} else {
2
};
(self.data_cb)(
&unsafe {
Data::from_parts(
audio_data.as_ptr() as *mut _,
audio_data.len() * channel_count,
T::FORMAT,
)
},
&cb_info,
);
oboe::DataCallbackResult::Continue
}
}
-499
View File
@@ -1,499 +0,0 @@
use std::cell::RefCell;
use std::cmp;
use std::convert::TryInto;
use std::time::Duration;
use std::vec::IntoIter as VecIntoIter;
extern crate oboe;
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, SampleRate, SizedSample, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};
mod android_media;
mod convert;
mod input_callback;
mod output_callback;
use self::android_media::{get_audio_record_min_buffer_size, get_audio_track_min_buffer_size};
use self::input_callback::CpalInputCallback;
use self::oboe::{AudioInputStream, AudioOutputStream};
use self::output_callback::CpalOutputCallback;
// Android Java API supports up to 8 channels, but oboe API
// only exposes mono and stereo.
const CHANNEL_MASKS: [i32; 2] = [
android_media::CHANNEL_OUT_MONO,
android_media::CHANNEL_OUT_STEREO,
];
const SAMPLE_RATES: [i32; 13] = [
5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176_400, 192_000,
];
pub struct Host;
#[derive(Clone)]
pub struct Device(Option<oboe::AudioDeviceInfo>);
pub enum Stream {
Input(Box<RefCell<dyn AudioInputStream>>),
Output(Box<RefCell<dyn AudioOutputStream>>),
}
pub type SupportedInputConfigs = VecIntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
pub type Devices = VecIntoIter<Device>;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
if let Ok(devices) = oboe::AudioDeviceInfo::request(oboe::AudioDeviceDirection::InputOutput)
{
Ok(devices
.into_iter()
.map(|d| Device(Some(d)))
.collect::<Vec<_>>()
.into_iter())
} else {
Ok(vec![Device(None)].into_iter())
}
}
fn default_input_device(&self) -> Option<Self::Device> {
Some(Device(None))
}
fn default_output_device(&self) -> Option<Self::Device> {
Some(Device(None))
}
}
fn buffer_size_range_for_params(
is_output: bool,
sample_rate: i32,
channel_mask: i32,
android_format: i32,
) -> SupportedBufferSize {
let min_buffer_size = if is_output {
get_audio_track_min_buffer_size(sample_rate, channel_mask, android_format)
} else {
get_audio_record_min_buffer_size(sample_rate, channel_mask, android_format)
};
if min_buffer_size > 0 {
SupportedBufferSize::Range {
min: min_buffer_size as u32,
max: i32::MAX as u32,
}
} else {
SupportedBufferSize::Unknown
}
}
fn default_supported_configs(is_output: bool) -> VecIntoIter<SupportedStreamConfigRange> {
// Have to "brute force" the parameter combinations with getMinBufferSize
const FORMATS: [SampleFormat; 2] = [SampleFormat::I16, SampleFormat::F32];
let mut output = Vec::with_capacity(SAMPLE_RATES.len() * CHANNEL_MASKS.len() * FORMATS.len());
for sample_format in &FORMATS {
let android_format = if *sample_format == SampleFormat::I16 {
android_media::ENCODING_PCM_16BIT
} else {
android_media::ENCODING_PCM_FLOAT
};
for (mask_idx, channel_mask) in CHANNEL_MASKS.iter().enumerate() {
let channel_count = mask_idx + 1;
for sample_rate in &SAMPLE_RATES {
if let SupportedBufferSize::Range { min, max } = buffer_size_range_for_params(
is_output,
*sample_rate,
*channel_mask,
android_format,
) {
output.push(SupportedStreamConfigRange {
channels: channel_count as u16,
min_sample_rate: SampleRate(*sample_rate as u32),
max_sample_rate: SampleRate(*sample_rate as u32),
buffer_size: SupportedBufferSize::Range { min, max },
sample_format: *sample_format,
});
}
}
}
}
output.into_iter()
}
fn device_supported_configs(
device: &oboe::AudioDeviceInfo,
is_output: bool,
) -> VecIntoIter<SupportedStreamConfigRange> {
let sample_rates = if !device.sample_rates.is_empty() {
device.sample_rates.as_slice()
} else {
&SAMPLE_RATES
};
const ALL_CHANNELS: [i32; 2] = [1, 2];
let channel_counts = if !device.channel_counts.is_empty() {
device.channel_counts.as_slice()
} else {
&ALL_CHANNELS
};
const ALL_FORMATS: [oboe::AudioFormat; 2] = [oboe::AudioFormat::I16, oboe::AudioFormat::F32];
let formats = if !device.formats.is_empty() {
device.formats.as_slice()
} else {
&ALL_FORMATS
};
let mut output = Vec::with_capacity(sample_rates.len() * channel_counts.len() * formats.len());
for sample_rate in sample_rates {
for channel_count in channel_counts {
assert!(*channel_count > 0);
if *channel_count > 2 {
// could be supported by the device, but oboe does not support more than 2 channels
continue;
}
let channel_mask = CHANNEL_MASKS[*channel_count as usize - 1];
for format in formats {
let (android_format, sample_format) = match format {
oboe::AudioFormat::I16 => {
(android_media::ENCODING_PCM_16BIT, SampleFormat::I16)
}
oboe::AudioFormat::F32 => {
(android_media::ENCODING_PCM_FLOAT, SampleFormat::F32)
}
_ => panic!("Unexpected format"),
};
let buffer_size = buffer_size_range_for_params(
is_output,
*sample_rate,
channel_mask,
android_format,
);
output.push(SupportedStreamConfigRange {
channels: cmp::min(*channel_count as u16, 2u16),
min_sample_rate: SampleRate(*sample_rate as u32),
max_sample_rate: SampleRate(*sample_rate as u32),
buffer_size,
sample_format,
});
}
}
}
output.into_iter()
}
fn configure_for_device<D, C, I>(
builder: oboe::AudioStreamBuilder<D, C, I>,
device: &Device,
config: &StreamConfig,
) -> oboe::AudioStreamBuilder<D, C, I> {
let mut builder = if let Some(info) = &device.0 {
builder.set_device_id(info.id)
} else {
builder
};
builder = builder.set_sample_rate(config.sample_rate.0.try_into().unwrap());
match &config.buffer_size {
BufferSize::Default => builder,
BufferSize::Fixed(size) => builder.set_buffer_capacity_in_frames(*size as i32),
}
}
fn build_input_stream<D, E, C, T>(
device: &Device,
config: &StreamConfig,
data_callback: D,
error_callback: E,
builder: oboe::AudioStreamBuilder<oboe::Input, C, T>,
) -> Result<Stream, BuildStreamError>
where
T: SizedSample + oboe::IsFormat + Send + 'static,
C: oboe::IsChannelCount + Send + 'static,
(T, C): oboe::IsFrameType,
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let builder = configure_for_device(builder, device, config);
let stream = builder
.set_callback(CpalInputCallback::<T, C>::new(
data_callback,
error_callback,
))
.open_stream()?;
Ok(Stream::Input(Box::new(RefCell::new(stream))))
}
fn build_output_stream<D, E, C, T>(
device: &Device,
config: &StreamConfig,
data_callback: D,
error_callback: E,
builder: oboe::AudioStreamBuilder<oboe::Output, C, T>,
) -> Result<Stream, BuildStreamError>
where
T: SizedSample + oboe::IsFormat + Send + 'static,
C: oboe::IsChannelCount + Send + 'static,
(T, C): oboe::IsFrameType,
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let builder = configure_for_device(builder, device, config);
let stream = builder
.set_callback(CpalOutputCallback::<T, C>::new(
data_callback,
error_callback,
))
.open_stream()?;
Ok(Stream::Output(Box::new(RefCell::new(stream))))
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, DeviceNameError> {
match &self.0 {
None => Ok("default".to_owned()),
Some(info) => Ok(info.product_name.clone()),
}
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
if let Some(info) = &self.0 {
Ok(device_supported_configs(info, false))
} else {
Ok(default_supported_configs(false))
}
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
if let Some(info) = &self.0 {
Ok(device_supported_configs(info, true))
} else {
Ok(default_supported_configs(true))
}
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let mut configs: Vec<_> = self.supported_input_configs().unwrap().collect();
configs.sort_by(|a, b| b.cmp_default_heuristics(a));
let config = configs
.into_iter()
.next()
.ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?
.with_max_sample_rate();
Ok(config)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let mut configs: Vec<_> = self.supported_output_configs().unwrap().collect();
configs.sort_by(|a, b| b.cmp_default_heuristics(a));
let config = configs
.into_iter()
.next()
.ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?
.with_max_sample_rate();
Ok(config)
}
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
match sample_format {
SampleFormat::I16 => {
let builder = oboe::AudioStreamBuilder::default()
.set_input()
.set_format::<i16>();
if config.channels == 1 {
build_input_stream(
self,
config,
data_callback,
error_callback,
builder.set_mono(),
)
} else if config.channels == 2 {
build_input_stream(
self,
config,
data_callback,
error_callback,
builder.set_stereo(),
)
} else {
Err(BackendSpecificError {
description: "More than 2 channels are not supported by Oboe.".to_owned(),
}
.into())
}
}
SampleFormat::F32 => {
let builder = oboe::AudioStreamBuilder::default()
.set_input()
.set_format::<f32>();
if config.channels == 1 {
build_input_stream(
self,
config,
data_callback,
error_callback,
builder.set_mono(),
)
} else if config.channels == 2 {
build_input_stream(
self,
config,
data_callback,
error_callback,
builder.set_stereo(),
)
} else {
Err(BackendSpecificError {
description: "More than 2 channels are not supported by Oboe.".to_owned(),
}
.into())
}
}
sample_format => Err(BackendSpecificError {
description: format!("{} format is not supported on Android.", sample_format),
}
.into()),
}
}
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
match sample_format {
SampleFormat::I16 => {
let builder = oboe::AudioStreamBuilder::default()
.set_output()
.set_format::<i16>();
if config.channels == 1 {
build_output_stream(
self,
config,
data_callback,
error_callback,
builder.set_mono(),
)
} else if config.channels == 2 {
build_output_stream(
self,
config,
data_callback,
error_callback,
builder.set_stereo(),
)
} else {
Err(BackendSpecificError {
description: "More than 2 channels are not supported by Oboe.".to_owned(),
}
.into())
}
}
SampleFormat::F32 => {
let builder = oboe::AudioStreamBuilder::default()
.set_output()
.set_format::<f32>();
if config.channels == 1 {
build_output_stream(
self,
config,
data_callback,
error_callback,
builder.set_mono(),
)
} else if config.channels == 2 {
build_output_stream(
self,
config,
data_callback,
error_callback,
builder.set_stereo(),
)
} else {
Err(BackendSpecificError {
description: "More than 2 channels are not supported by Oboe.".to_owned(),
}
.into())
}
}
sample_format => Err(BackendSpecificError {
description: format!("{} format is not supported on Android.", sample_format),
}
.into()),
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
match self {
Self::Input(stream) => stream
.borrow_mut()
.request_start()
.map_err(PlayStreamError::from),
Self::Output(stream) => stream
.borrow_mut()
.request_start()
.map_err(PlayStreamError::from),
}
}
fn pause(&self) -> Result<(), PauseStreamError> {
match self {
Self::Input(_) => Err(BackendSpecificError {
description: "Pause called on the input stream.".to_owned(),
}
.into()),
Self::Output(stream) => stream
.borrow_mut()
.request_pause()
.map_err(PauseStreamError::from),
}
}
}
-90
View File
@@ -1,90 +0,0 @@
use std::marker::PhantomData;
use std::time::Instant;
extern crate oboe;
use super::convert::{stream_instant, to_stream_instant};
use crate::{Data, OutputCallbackInfo, OutputStreamTimestamp, SizedSample, StreamError};
pub struct CpalOutputCallback<I, C> {
data_cb: Box<dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static>,
error_cb: Box<dyn FnMut(StreamError) + Send + 'static>,
created: Instant,
phantom_channel: PhantomData<C>,
phantom_input: PhantomData<I>,
}
impl<I, C> CpalOutputCallback<I, C> {
pub fn new<D, E>(data_cb: D, error_cb: E) -> Self
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Self {
data_cb: Box::new(data_cb),
error_cb: Box::new(error_cb),
created: Instant::now(),
phantom_channel: PhantomData,
phantom_input: PhantomData,
}
}
fn make_callback_info(
&self,
audio_stream: &mut dyn oboe::AudioOutputStreamSafe,
) -> OutputCallbackInfo {
OutputCallbackInfo {
timestamp: OutputStreamTimestamp {
callback: to_stream_instant(self.created.elapsed()),
playback: stream_instant(audio_stream),
},
}
}
}
impl<T: SizedSample, C: oboe::IsChannelCount> oboe::AudioOutputCallback for CpalOutputCallback<T, C>
where
(T, C): oboe::IsFrameType,
{
type FrameType = (T, C);
fn on_error_before_close(
&mut self,
_audio_stream: &mut dyn oboe::AudioOutputStreamSafe,
error: oboe::Error,
) {
(self.error_cb)(StreamError::from(error))
}
fn on_error_after_close(
&mut self,
_audio_stream: &mut dyn oboe::AudioOutputStreamSafe,
error: oboe::Error,
) {
(self.error_cb)(StreamError::from(error))
}
fn on_audio_ready(
&mut self,
audio_stream: &mut dyn oboe::AudioOutputStreamSafe,
audio_data: &mut [<<Self as oboe::AudioOutputCallback>::FrameType as oboe::IsFrameType>::Type],
) -> oboe::DataCallbackResult {
let cb_info = self.make_callback_info(audio_stream);
let channel_count = if C::CHANNEL_COUNT == oboe::ChannelCount::Mono {
1
} else {
2
};
(self.data_cb)(
&mut unsafe {
Data::from_parts(
audio_data.as_mut_ptr() as *mut _,
audio_data.len() * channel_count,
T::FORMAT,
)
},
&cb_info,
);
oboe::DataCallbackResult::Continue
}
}
-57
View File
@@ -1,57 +0,0 @@
//! Handles COM initialization and cleanup.
use super::IoError;
use std::marker::PhantomData;
use windows::Win32::Foundation::RPC_E_CHANGED_MODE;
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED};
thread_local!(static COM_INITIALIZED: ComInitialized = {
unsafe {
// Try to initialize COM with STA by default to avoid compatibility issues with the ASIO
// backend (where CoInitialize() is called by the ASIO SDK) or winit (where drag and drop
// requires STA).
// This call can fail with RPC_E_CHANGED_MODE if another library initialized COM with MTA.
// That's OK though since COM ensures thread-safety/compatibility through marshalling when
// necessary.
let result = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
if result.is_ok() || result == RPC_E_CHANGED_MODE {
ComInitialized {
result,
_ptr: PhantomData,
}
} else {
// COM initialization failed in another way, something is really wrong.
panic!(
"Failed to initialize COM: {}",
IoError::from_raw_os_error(result.0)
);
}
}
});
/// RAII object that guards the fact that COM is initialized.
///
// We store a raw pointer because it's the only way at the moment to remove `Send`/`Sync` from the
// object.
struct ComInitialized {
result: windows::core::HRESULT,
_ptr: PhantomData<*mut ()>,
}
impl Drop for ComInitialized {
#[inline]
fn drop(&mut self) {
// Need to avoid calling CoUninitialize() if CoInitializeEx failed since it may have
// returned RPC_E_MODE_CHANGED - which is OK, see above.
if self.result.is_ok() {
unsafe { CoUninitialize() };
}
}
}
/// Ensures that COM is initialized in this thread.
#[inline]
pub fn com_initialized() {
COM_INITIALIZED.with(|_| {});
}
File diff suppressed because it is too large. Load diff
-104
View File
@@ -1,104 +0,0 @@
pub use self::device::{
default_input_device, default_output_device, Device, Devices, SupportedInputConfigs,
SupportedOutputConfigs,
};
pub use self::stream::Stream;
use crate::traits::HostTrait;
use crate::BackendSpecificError;
use crate::DevicesError;
use std::io::Error as IoError;
use windows::Win32::Media::Audio;
mod com;
mod device;
mod stream;
/// The WASAPI host, the default windows host type.
///
/// Note: If you use a WASAPI output device as an input device it will
/// transparently enable loopback mode (see
/// https://docs.microsoft.com/en-us/windows/win32/coreaudio/loopback-recording).
#[derive(Debug)]
pub struct Host;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
// Assume WASAPI is always available on Windows.
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl From<windows::core::Error> for BackendSpecificError {
fn from(error: windows::core::Error) -> Self {
BackendSpecificError {
description: format!("{}", IoError::from(error)),
}
}
}
trait ErrDeviceNotAvailable: From<BackendSpecificError> {
fn device_not_available() -> Self;
}
impl ErrDeviceNotAvailable for crate::BuildStreamError {
fn device_not_available() -> Self {
Self::DeviceNotAvailable
}
}
impl ErrDeviceNotAvailable for crate::SupportedStreamConfigsError {
fn device_not_available() -> Self {
Self::DeviceNotAvailable
}
}
impl ErrDeviceNotAvailable for crate::DefaultStreamConfigError {
fn device_not_available() -> Self {
Self::DeviceNotAvailable
}
}
impl ErrDeviceNotAvailable for crate::StreamError {
fn device_not_available() -> Self {
Self::DeviceNotAvailable
}
}
fn windows_err_to_cpal_err<E: ErrDeviceNotAvailable>(e: windows::core::Error) -> E {
windows_err_to_cpal_err_message::<E>(e, "")
}
fn windows_err_to_cpal_err_message<E: ErrDeviceNotAvailable>(
e: windows::core::Error,
message: &str,
) -> E {
match e.code() {
Audio::AUDCLNT_E_DEVICE_INVALIDATED => E::device_not_available(),
_ => {
let description = format!("{}{}", message, e);
let err = BackendSpecificError { description };
err.into()
}
}
}
-559
View File
@@ -1,559 +0,0 @@
use super::windows_err_to_cpal_err;
use crate::traits::StreamTrait;
use crate::{
BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, StreamError,
};
use std::mem;
use std::ptr;
use std::sync::mpsc::{channel, Receiver, SendError, Sender};
use std::thread::{self, JoinHandle};
use windows::Win32::Foundation;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Foundation::WAIT_OBJECT_0;
use windows::Win32::Media::Audio;
use windows::Win32::System::SystemServices;
use windows::Win32::System::Threading;
pub struct Stream {
/// The high-priority audio processing thread calling callbacks.
/// Option used for moving out in destructor.
///
/// TODO: Actually set the thread priority.
thread: Option<JoinHandle<()>>,
// Commands processed by the `run()` method that is currently running.
// `pending_scheduled_event` must be signalled whenever a command is added here, so that it
// will get picked up.
commands: Sender<Command>,
// This event is signalled after a new entry is added to `commands`, so that the `run()`
// method can be notified.
pending_scheduled_event: Foundation::HANDLE,
}
struct RunContext {
// Streams that have been created in this event loop.
stream: StreamInner,
// Handles corresponding to the `event` field of each element of `voices`. Must always be in
// sync with `voices`, except that the first element is always `pending_scheduled_event`.
handles: Vec<Foundation::HANDLE>,
commands: Receiver<Command>,
}
// Once we start running the eventloop, the RunContext will not be moved.
unsafe impl Send for RunContext {}
pub enum Command {
PlayStream,
PauseStream,
Terminate,
}
pub enum AudioClientFlow {
Render {
render_client: Audio::IAudioRenderClient,
},
Capture {
capture_client: Audio::IAudioCaptureClient,
},
}
pub struct StreamInner {
pub audio_client: Audio::IAudioClient,
pub audio_clock: Audio::IAudioClock,
pub client_flow: AudioClientFlow,
// Event that is signalled by WASAPI whenever audio data must be written.
pub event: Foundation::HANDLE,
// True if the stream is currently playing. False if paused.
pub playing: bool,
// Number of frames of audio data in the underlying buffer allocated by WASAPI.
pub max_frames_in_buffer: u32,
// Number of bytes that each frame occupies.
pub bytes_per_frame: u16,
// The configuration with which the stream was created.
pub config: crate::StreamConfig,
// The sample format with which the stream was created.
pub sample_format: SampleFormat,
}
impl Stream {
pub(crate) fn new_input<D, E>(
stream_inner: StreamInner,
mut data_callback: D,
mut error_callback: E,
) -> Stream
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let pending_scheduled_event = unsafe {
Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null()))
}
.expect("cpal: could not create input stream event");
let (tx, rx) = channel();
let run_context = RunContext {
handles: vec![pending_scheduled_event, stream_inner.event],
stream: stream_inner,
commands: rx,
};
let thread = thread::Builder::new()
.name("cpal_wasapi_in".to_owned())
.spawn(move || run_input(run_context, &mut data_callback, &mut error_callback))
.unwrap();
Stream {
thread: Some(thread),
commands: tx,
pending_scheduled_event,
}
}
pub(crate) fn new_output<D, E>(
stream_inner: StreamInner,
mut data_callback: D,
mut error_callback: E,
) -> Stream
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let pending_scheduled_event = unsafe {
Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null()))
}
.expect("cpal: could not create output stream event");
let (tx, rx) = channel();
let run_context = RunContext {
handles: vec![pending_scheduled_event, stream_inner.event],
stream: stream_inner,
commands: rx,
};
let thread = thread::Builder::new()
.name("cpal_wasapi_out".to_owned())
.spawn(move || run_output(run_context, &mut data_callback, &mut error_callback))
.unwrap();
Stream {
thread: Some(thread),
commands: tx,
pending_scheduled_event,
}
}
#[inline]
fn push_command(&self, command: Command) -> Result<(), SendError<Command>> {
self.commands.send(command)?;
unsafe {
Threading::SetEvent(self.pending_scheduled_event).unwrap();
}
Ok(())
}
}
impl Drop for Stream {
#[inline]
fn drop(&mut self) {
if self.push_command(Command::Terminate).is_ok() {
self.thread.take().unwrap().join().unwrap();
unsafe {
let _ = Foundation::CloseHandle(self.pending_scheduled_event);
}
}
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream)
.map_err(|_| crate::error::PlayStreamError::DeviceNotAvailable)?;
Ok(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream)
.map_err(|_| crate::error::PauseStreamError::DeviceNotAvailable)?;
Ok(())
}
}
impl Drop for StreamInner {
#[inline]
fn drop(&mut self) {
unsafe {
let _ = Foundation::CloseHandle(self.event);
}
}
}
// Process any pending commands that are queued within the `RunContext`.
// Returns `true` if the loop should continue running, `false` if it should terminate.
fn process_commands(run_context: &mut RunContext) -> Result<bool, StreamError> {
// Process the pending commands.
for command in run_context.commands.try_iter() {
match command {
Command::PlayStream => unsafe {
if !run_context.stream.playing {
run_context
.stream
.audio_client
.Start()
.map_err(windows_err_to_cpal_err::<StreamError>)?;
run_context.stream.playing = true;
}
},
Command::PauseStream => unsafe {
if run_context.stream.playing {
run_context
.stream
.audio_client
.Stop()
.map_err(windows_err_to_cpal_err::<StreamError>)?;
run_context.stream.playing = false;
}
},
Command::Terminate => {
return Ok(false);
}
}
}
Ok(true)
}
// Wait for any of the given handles to be signalled.
//
// Returns the index of the `handle` that was signalled, or an `Err` if
// `WaitForMultipleObjectsEx` fails.
//
// This is called when the `run` thread is ready to wait for the next event. The
// next event might be some command submitted by the user (the first handle) or
// might indicate that one of the streams is ready to deliver or receive audio.
fn wait_for_handle_signal(handles: &[Foundation::HANDLE]) -> Result<usize, BackendSpecificError> {
debug_assert!(handles.len() <= SystemServices::MAXIMUM_WAIT_OBJECTS as usize);
let result = unsafe {
Threading::WaitForMultipleObjectsEx(
handles,
false, // Don't wait for all, just wait for the first
Threading::INFINITE, // TODO: allow setting a timeout
false, // irrelevant parameter here
)
};
if result == Foundation::WAIT_FAILED {
let err = unsafe { Foundation::GetLastError() };
let description = format!("`WaitForMultipleObjectsEx failed: {:?}", err);
let err = BackendSpecificError { description };
return Err(err);
}
// Notifying the corresponding task handler.
let handle_idx = (result.0 - WAIT_OBJECT_0.0) as usize;
Ok(handle_idx)
}
// Get the number of available frames that are available for writing/reading.
fn get_available_frames(stream: &StreamInner) -> Result<u32, StreamError> {
unsafe {
let padding = stream
.audio_client
.GetCurrentPadding()
.map_err(windows_err_to_cpal_err::<StreamError>)?;
Ok(stream.max_frames_in_buffer - padding)
}
}
fn run_input(
mut run_ctxt: RunContext,
data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo),
error_callback: &mut dyn FnMut(StreamError),
) {
boost_current_thread_priority();
loop {
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
Some(ControlFlow::Break) => break,
Some(ControlFlow::Continue) => continue,
None => (),
}
let capture_client = match run_ctxt.stream.client_flow {
AudioClientFlow::Capture { ref capture_client } => capture_client.clone(),
_ => unreachable!(),
};
match process_input(
&run_ctxt.stream,
capture_client,
data_callback,
error_callback,
) {
ControlFlow::Break => break,
ControlFlow::Continue => continue,
}
}
}
fn run_output(
mut run_ctxt: RunContext,
data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo),
error_callback: &mut dyn FnMut(StreamError),
) {
boost_current_thread_priority();
loop {
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
Some(ControlFlow::Break) => break,
Some(ControlFlow::Continue) => continue,
None => (),
}
let render_client = match run_ctxt.stream.client_flow {
AudioClientFlow::Render { ref render_client } => render_client.clone(),
_ => unreachable!(),
};
match process_output(
&run_ctxt.stream,
render_client,
data_callback,
error_callback,
) {
ControlFlow::Break => break,
ControlFlow::Continue => continue,
}
}
}
fn boost_current_thread_priority() {
unsafe {
let thread_id = Threading::GetCurrentThreadId();
let _ = Threading::SetThreadPriority(
HANDLE(thread_id as isize),
Threading::THREAD_PRIORITY_TIME_CRITICAL,
);
}
}
enum ControlFlow {
Break,
Continue,
}
fn process_commands_and_await_signal(
run_context: &mut RunContext,
error_callback: &mut dyn FnMut(StreamError),
) -> Option<ControlFlow> {
// Process queued commands.
match process_commands(run_context) {
Ok(true) => (),
Ok(false) => return Some(ControlFlow::Break),
Err(err) => {
error_callback(err);
return Some(ControlFlow::Break);
}
};
// Wait for any of the handles to be signalled.
let handle_idx = match wait_for_handle_signal(&run_context.handles) {
Ok(idx) => idx,
Err(err) => {
error_callback(err.into());
return Some(ControlFlow::Break);
}
};
// If `handle_idx` is 0, then it's `pending_scheduled_event` that was signalled in
// order for us to pick up the pending commands. Otherwise, a stream needs data.
if handle_idx == 0 {
return Some(ControlFlow::Continue);
}
None
}
// The loop for processing pending input data.
fn process_input(
stream: &StreamInner,
capture_client: Audio::IAudioCaptureClient,
data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo),
error_callback: &mut dyn FnMut(StreamError),
) -> ControlFlow {
unsafe {
// Get the available data in the shared buffer.
let mut buffer: *mut u8 = ptr::null_mut();
let mut flags = mem::MaybeUninit::uninit();
loop {
let mut frames_available = match capture_client.GetNextPacketSize() {
Ok(0) => return ControlFlow::Continue,
Ok(f) => f,
Err(err) => {
error_callback(windows_err_to_cpal_err(err));
return ControlFlow::Break;
}
};
let mut qpc_position: u64 = 0;
let result = capture_client.GetBuffer(
&mut buffer,
&mut frames_available,
flags.as_mut_ptr(),
None,
Some(&mut qpc_position),
);
match result {
// TODO: Can this happen?
Err(e) if e.code() == Audio::AUDCLNT_S_BUFFER_EMPTY => continue,
Err(e) => {
error_callback(windows_err_to_cpal_err(e));
return ControlFlow::Break;
}
Ok(_) => (),
}
debug_assert!(!buffer.is_null());
let data = buffer as *mut ();
let len = frames_available as usize * stream.bytes_per_frame as usize
/ stream.sample_format.sample_size();
let data = Data::from_parts(data, len, stream.sample_format);
// The `qpc_position` is in 100 nanosecond units. Convert it to nanoseconds.
let timestamp = match input_timestamp(stream, qpc_position) {
Ok(ts) => ts,
Err(err) => {
error_callback(err);
return ControlFlow::Break;
}
};
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
// Release the buffer.
let result = capture_client
.ReleaseBuffer(frames_available)
.map_err(windows_err_to_cpal_err);
if let Err(err) = result {
error_callback(err);
return ControlFlow::Break;
}
}
}
}
// The loop for writing output data.
fn process_output(
stream: &StreamInner,
render_client: Audio::IAudioRenderClient,
data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo),
error_callback: &mut dyn FnMut(StreamError),
) -> ControlFlow {
// The number of frames available for writing.
let frames_available = match get_available_frames(stream) {
Ok(0) => return ControlFlow::Continue, // TODO: Can this happen?
Ok(n) => n,
Err(err) => {
error_callback(err);
return ControlFlow::Break;
}
};
unsafe {
let buffer = match render_client.GetBuffer(frames_available) {
Ok(b) => b,
Err(e) => {
error_callback(windows_err_to_cpal_err(e));
return ControlFlow::Break;
}
};
debug_assert!(!buffer.is_null());
let data = buffer as *mut ();
let len = frames_available as usize * stream.bytes_per_frame as usize
/ stream.sample_format.sample_size();
let mut data = Data::from_parts(data, len, stream.sample_format);
let sample_rate = stream.config.sample_rate;
let timestamp = match output_timestamp(stream, frames_available, sample_rate) {
Ok(ts) => ts,
Err(err) => {
error_callback(err);
return ControlFlow::Break;
}
};
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
if let Err(err) = render_client.ReleaseBuffer(frames_available, 0) {
error_callback(windows_err_to_cpal_err(err));
return ControlFlow::Break;
}
}
ControlFlow::Continue
}
/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: u32, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
/// Use the stream's `IAudioClock` to produce the current stream instant.
///
/// Uses the QPC position produced via the `GetPosition` method.
fn stream_instant(stream: &StreamInner) -> Result<crate::StreamInstant, StreamError> {
let mut position: u64 = 0;
let mut qpc_position: u64 = 0;
unsafe {
stream
.audio_clock
.GetPosition(&mut position, Some(&mut qpc_position))
.map_err(windows_err_to_cpal_err::<StreamError>)?;
};
// The `qpc_position` is in 100 nanosecond units. Convert it to nanoseconds.
let qpc_nanos = qpc_position as i128 * 100;
let instant = crate::StreamInstant::from_nanos_i128(qpc_nanos)
.expect("performance counter out of range of `StreamInstant` representation");
Ok(instant)
}
/// Produce the input stream timestamp.
///
/// `buffer_qpc_position` is the `qpc_position` returned via the `GetBuffer` call on the capture
/// client. It represents the instant at which the first sample of the retrieved buffer was
/// captured.
fn input_timestamp(
stream: &StreamInner,
buffer_qpc_position: u64,
) -> Result<crate::InputStreamTimestamp, StreamError> {
// The `qpc_position` is in 100 nanosecond units. Convert it to nanoseconds.
let qpc_nanos = buffer_qpc_position as i128 * 100;
let capture = crate::StreamInstant::from_nanos_i128(qpc_nanos)
.expect("performance counter out of range of `StreamInstant` representation");
let callback = stream_instant(stream)?;
Ok(crate::InputStreamTimestamp { capture, callback })
}
/// Produce the output stream timestamp.
///
/// `frames_available` is the number of frames available for writing as reported by subtracting the
/// result of `GetCurrentPadding` from the maximum buffer size.
///
/// `sample_rate` is the rate at which audio frames are processed by the device.
///
/// TODO: The returned `playback` is an estimate that assumes audio is delivered immediately after
/// `frames_available` are consumed. The reality is that there is likely a tiny amount of latency
/// after this, but not sure how to determine this.
fn output_timestamp(
stream: &StreamInner,
frames_available: u32,
sample_rate: crate::SampleRate,
) -> Result<crate::OutputStreamTimestamp, StreamError> {
let callback = stream_instant(stream)?;
let buffer_duration = frames_to_duration(frames_available, sample_rate);
let playback = callback
.add(buffer_duration)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
Ok(crate::OutputStreamTimestamp { callback, playback })
}
-528
View File
@@ -1,528 +0,0 @@
extern crate js_sys;
extern crate wasm_bindgen;
extern crate web_sys;
use self::js_sys::eval;
use self::wasm_bindgen::prelude::*;
use self::wasm_bindgen::JsCast;
use self::web_sys::{AudioContext, AudioContextOptions};
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
/// Content is false if the iterator is empty.
pub struct Devices(bool);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
pub struct Host;
pub struct Stream {
ctx: Arc<AudioContext>,
on_ended_closures: Vec<Arc<RwLock<Option<Closure<dyn FnMut()>>>>>,
config: StreamConfig,
buffer_size_frames: usize,
}
pub type SupportedInputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
const MIN_CHANNELS: u16 = 1;
const MAX_CHANNELS: u16 = 32;
const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000);
const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000);
const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100);
const MIN_BUFFER_SIZE: u32 = 1;
const MAX_BUFFER_SIZE: u32 = u32::MAX;
const DEFAULT_BUFFER_SIZE: usize = 2048;
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
// Assume this host is always available on webaudio.
true
}
fn devices(&self) -> Result<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl Devices {
fn new() -> Result<Self, DevicesError> {
Ok(Self::default())
}
}
impl Device {
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Ok("Default Device".to_owned())
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
// TODO
Ok(Vec::new().into_iter())
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
let buffer_size = SupportedBufferSize::Range {
min: MIN_BUFFER_SIZE,
max: MAX_BUFFER_SIZE,
};
let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: MIN_SAMPLE_RATE,
max_sample_rate: MAX_SAMPLE_RATE,
buffer_size: buffer_size.clone(),
sample_format: SUPPORTED_SAMPLE_FORMAT,
})
.collect();
Ok(configs.into_iter())
}
#[inline]
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
// TODO
Err(DefaultStreamConfigError::StreamTypeNotSupported)
}
#[inline]
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
const EXPECT: &str = "expected at least one valid webaudio stream config";
let config = self
.supported_output_configs()
.expect(EXPECT)
.max_by(|a, b| a.cmp_default_heuristics(b))
.unwrap()
.with_sample_rate(DEFAULT_SAMPLE_RATE);
Ok(config)
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Device::name(self)
}
#[inline]
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
#[inline]
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
#[inline]
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
#[inline]
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
_config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
// TODO
Err(BuildStreamError::StreamConfigNotSupported)
}
/// Create an output stream.
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
_error_callback: E,
_timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
if !valid_config(config, sample_format) {
return Err(BuildStreamError::StreamConfigNotSupported);
}
let n_channels = config.channels as usize;
let buffer_size_frames = match config.buffer_size {
BufferSize::Fixed(v) => {
if v == 0 {
return Err(BuildStreamError::StreamConfigNotSupported);
} else {
v as usize
}
}
BufferSize::Default => DEFAULT_BUFFER_SIZE,
};
let buffer_size_samples = buffer_size_frames * n_channels;
let buffer_time_step_secs = buffer_time_step_secs(buffer_size_frames, config.sample_rate);
let data_callback = Arc::new(Mutex::new(Box::new(data_callback)));
// Create the WebAudio stream.
let mut stream_opts = AudioContextOptions::new();
stream_opts.sample_rate(config.sample_rate.0 as f32);
let ctx = AudioContext::new_with_context_options(&stream_opts).map_err(
|err| -> BuildStreamError {
let description = format!("{:?}", err);
let err = BackendSpecificError { description };
err.into()
},
)?;
let destination = ctx.destination();
// If possible, set the destination's channel_count to the given config.channel.
// If not, fallback on the default destination channel_count to keep previous behavior
// and do not return an error.
if config.channels as u32 <= destination.max_channel_count() {
destination.set_channel_count(config.channels as u32);
}
let ctx = Arc::new(ctx);
// A container for managing the lifecycle of the audio callbacks.
let mut on_ended_closures: Vec<Arc<RwLock<Option<Closure<dyn FnMut()>>>>> = Vec::new();
// A cursor keeping track of the current time at which new frames should be scheduled.
let time = Arc::new(RwLock::new(0f64));
// Create a set of closures / callbacks which will continuously fetch and schedule sample
// playback. Starting with two workers, e.g. a front and back buffer so that audio frames
// can be fetched in the background.
for _i in 0..2 {
let data_callback_handle = data_callback.clone();
let ctx_handle = ctx.clone();
let time_handle = time.clone();
// A set of temporary buffers to be used for intermediate sample transformation steps.
let mut temporary_buffer = vec![0f32; buffer_size_samples];
let mut temporary_channel_buffer = vec![0f32; buffer_size_frames];
#[cfg(target_feature = "atomics")]
let temporary_channel_array_view: js_sys::Float32Array;
#[cfg(target_feature = "atomics")]
{
let temporary_channel_array = js_sys::ArrayBuffer::new(
(std::mem::size_of::<f32>() * buffer_size_frames) as u32,
);
temporary_channel_array_view = js_sys::Float32Array::new(&temporary_channel_array);
}
// Create a webaudio buffer which will be reused to avoid allocations.
let ctx_buffer = ctx
.create_buffer(
config.channels as u32,
buffer_size_frames as u32,
config.sample_rate.0 as f32,
)
.map_err(|err| -> BuildStreamError {
let description = format!("{:?}", err);
let err = BackendSpecificError { description };
err.into()
})?;
// A self reference to this closure for passing to future audio event calls.
let on_ended_closure: Arc<RwLock<Option<Closure<dyn FnMut()>>>> =
Arc::new(RwLock::new(None));
let on_ended_closure_handle = on_ended_closure.clone();
on_ended_closure
.write()
.unwrap()
.replace(Closure::wrap(Box::new(move || {
let now = ctx_handle.current_time();
let time_at_start_of_buffer = {
let time_at_start_of_buffer = time_handle
.read()
.expect("Unable to get a read lock on the time cursor");
// Synchronise first buffer as necessary (eg. keep the time value
// referenced to the context clock).
if *time_at_start_of_buffer > 0.001 {
*time_at_start_of_buffer
} else {
// 25ms of time to fetch the first sample data, increase to avoid
// initial underruns.
now + 0.025
}
};
// Populate the sample data into an interleaved temporary buffer.
{
let len = temporary_buffer.len();
let data = temporary_buffer.as_mut_ptr() as *mut ();
let mut data = unsafe { Data::from_parts(data, len, sample_format) };
let mut data_callback = data_callback_handle.lock().unwrap();
let callback = crate::StreamInstant::from_secs_f64(now);
let playback = crate::StreamInstant::from_secs_f64(time_at_start_of_buffer);
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
(data_callback.deref_mut())(&mut data, &info);
}
// Deinterleave the sample data and copy into the audio context buffer.
// We do not reference the audio context buffer directly e.g. getChannelData.
// As wasm-bindgen only gives us a copy, not a direct reference.
for channel in 0..n_channels {
for i in 0..buffer_size_frames {
temporary_channel_buffer[i] =
temporary_buffer[n_channels * i + channel];
}
#[cfg(not(target_feature = "atomics"))]
{
ctx_buffer
.copy_to_channel(&mut temporary_channel_buffer, channel as i32)
.expect(
"Unable to write sample data into the audio context buffer",
);
}
// copyToChannel cannot be directly copied into from a SharedArrayBuffer,
// which WASM memory is backed by if the 'atomics' flag is enabled.
// This workaround copies the data into an intermediary buffer first.
// There's a chance browsers may eventually relax that requirement.
// 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);
ctx_buffer
.unchecked_ref::<ExternalArrayAudioBuffer>()
.copy_to_channel(&temporary_channel_array_view, channel as i32)
.expect(
"Unable to write sample data into the audio context buffer",
);
}
}
// Create an AudioBufferSourceNode, schedule it to playback the reused buffer
// in the future.
let source = ctx_handle
.create_buffer_source()
.expect("Unable to create a webaudio buffer source");
source.set_buffer(Some(&ctx_buffer));
source
.connect_with_audio_node(&ctx_handle.destination())
.expect(
"Unable to connect the web audio buffer source to the context destination",
);
source.set_onended(Some(
on_ended_closure_handle
.read()
.unwrap()
.as_ref()
.unwrap()
.as_ref()
.unchecked_ref(),
));
source
.start_with_when(time_at_start_of_buffer)
.expect("Unable to start the webaudio buffer source");
// Keep track of when the next buffer worth of samples should be played.
*time_handle.write().unwrap() = time_at_start_of_buffer + buffer_time_step_secs;
}) as Box<dyn FnMut()>));
on_ended_closures.push(on_ended_closure);
}
Ok(Stream {
ctx,
on_ended_closures,
config: config.clone(),
buffer_size_frames,
})
}
}
impl Stream {
/// Return the [`AudioContext`](https://developer.mozilla.org/docs/Web/API/AudioContext) used
/// by this stream.
pub fn audio_context(&self) -> &AudioContext {
&*self.ctx
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> {
let window = web_sys::window().unwrap();
match self.ctx.resume() {
Ok(_) => {
// Begin webaudio playback, initially scheduling the closures to fire on a timeout
// event.
let mut offset_ms = 10;
let time_step_secs =
buffer_time_step_secs(self.buffer_size_frames, self.config.sample_rate);
let time_step_ms = (time_step_secs * 1_000.0) as i32;
for on_ended_closure in self.on_ended_closures.iter() {
window
.set_timeout_with_callback_and_timeout_and_arguments_0(
on_ended_closure
.read()
.unwrap()
.as_ref()
.unwrap()
.as_ref()
.unchecked_ref(),
offset_ms,
)
.unwrap();
offset_ms += time_step_ms;
}
Ok(())
}
Err(err) => {
let description = format!("{:?}", err);
let err = BackendSpecificError { description };
Err(err.into())
}
}
}
fn pause(&self) -> Result<(), PauseStreamError> {
match self.ctx.suspend() {
Ok(_) => Ok(()),
Err(err) => {
let description = format!("{:?}", err);
let err = BackendSpecificError { description };
Err(err.into())
}
}
}
}
impl Drop for Stream {
fn drop(&mut self) {
let _ = self.ctx.close();
}
}
impl Default for Devices {
fn default() -> Devices {
// We produce an empty iterator if the WebAudio API isn't available.
Devices(is_webaudio_available())
}
}
impl Iterator for Devices {
type Item = Device;
#[inline]
fn next(&mut self) -> Option<Device> {
if self.0 {
self.0 = false;
Some(Device)
} else {
None
}
}
}
#[inline]
fn default_input_device() -> Option<Device> {
// TODO
None
}
#[inline]
fn default_output_device() -> Option<Device> {
if is_webaudio_available() {
Some(Device)
} else {
None
}
}
// Detects whether the `AudioContext` global variable is available.
fn is_webaudio_available() -> bool {
if let Ok(audio_context_is_defined) = eval("typeof AudioContext !== 'undefined'") {
audio_context_is_defined.as_bool().unwrap()
} else {
false
}
}
// Whether or not the given stream configuration is valid for building a stream.
fn valid_config(conf: &StreamConfig, sample_format: SampleFormat) -> bool {
conf.channels <= MAX_CHANNELS
&& conf.channels >= MIN_CHANNELS
&& conf.sample_rate <= MAX_SAMPLE_RATE
&& conf.sample_rate >= MIN_SAMPLE_RATE
&& sample_format == SUPPORTED_SAMPLE_FORMAT
}
fn buffer_time_step_secs(buffer_size_frames: usize, sample_rate: SampleRate) -> f64 {
buffer_size_frames as f64 / sample_rate.0 as f64
}
#[cfg(target_feature = "atomics")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = AudioBuffer)]
type ExternalArrayAudioBuffer;
# [wasm_bindgen(catch, method, structural, js_class = "AudioBuffer", js_name = copyToChannel)]
pub fn copy_to_channel(
this: &ExternalArrayAudioBuffer,
source: &js_sys::Float32Array,
channel_number: i32,
) -> Result<(), JsValue>;
}
-871
View File
@@ -1,871 +0,0 @@
//! # How to use cpal
//!
//! Here are some concepts cpal exposes:
//!
//! - A [`Host`] provides access to the available audio devices on the system.
//! Some platforms have more than one host available, but every platform supported by CPAL has at
//! least one [default_host] that is guaranteed to be available.
//! - A [`Device`] is an audio device that may have any number of input and
//! output streams.
//! - A [`Stream`] is an open flow of audio data. Input streams allow you to
//! receive audio data, output streams allow you to play audio data. You must choose which
//! [Device] will run your stream before you can create one. Often, a default device can be
//! retrieved via the [Host].
//!
//! The first step is to initialise the [`Host`]:
//!
//! ```
//! use cpal::traits::HostTrait;
//! let host = cpal::default_host();
//! ```
//!
//! Then choose an available [`Device`]. The easiest way is to use the default input or output
//! `Device` via the [`default_input_device()`] or [`default_output_device()`] methods on `host`.
//!
//! Alternatively, you can enumerate all the available devices with the [`devices()`] method.
//! Beware that the `default_*_device()` functions return an `Option<Device>` in case no device
//! is available for that stream type on the system.
//!
//! ```no_run
//! # use cpal::traits::HostTrait;
//! # let host = cpal::default_host();
//! let device = host.default_output_device().expect("no output device available");
//! ```
//!
//! Before we can create a stream, we must decide what the configuration of the audio stream is
//! going to be.
//! You can query all the supported configurations with the
//! [`supported_input_configs()`] and [`supported_output_configs()`] methods.
//! These produce a list of [`SupportedStreamConfigRange`] structs which can later be turned into
//! actual [`SupportedStreamConfig`] structs.
//!
//! If you don't want to query the list of configs,
//! you can also build your own [`StreamConfig`] manually, but doing so could lead to an error when
//! building the stream if the config is not supported by the device.
//!
//! > **Note**: the `supported_input/output_configs()` methods
//! > could return an error for example if the device has been disconnected.
//!
//! ```no_run
//! use cpal::traits::{DeviceTrait, HostTrait};
//! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap();
//! let mut supported_configs_range = device.supported_output_configs()
//! .expect("error while querying configs");
//! let supported_config = supported_configs_range.next()
//! .expect("no supported config?!")
//! .with_max_sample_rate();
//! ```
//!
//! Now that we have everything for the stream, we are ready to create it from our selected device:
//!
//! ```no_run
//! use cpal::Data;
//! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
//! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap();
//! # let config = device.default_output_config().unwrap().into();
//! let stream = device.build_output_stream(
//! &config,
//! move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
//! // react to stream events and read or write stream data here.
//! },
//! move |err| {
//! // react to errors here.
//! },
//! None // None=blocking, Some(Duration)=timeout
//! );
//! ```
//!
//! 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.
//!
//! > **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
//! > audio data to the system's audio device in a timely manner. On older platforms that only
//! > provide a blocking API (e.g. ALSA), CPAL will create a thread in order to consistently
//! > provide non-blocking behaviour (currently this is a thread per stream, but this may change to
//! > use a single thread for all streams). *If this is an issue for your platform or design,
//! > please share your issue and use-case with the CPAL team on the GitHub issue tracker for
//! > consideration.*
//!
//! In this example, we simply fill the given output buffer with silence.
//!
//! ```no_run
//! use cpal::{Data, Sample, SampleFormat, FromSample};
//! use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
//! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap();
//! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err);
//! let sample_format = supported_config.sample_format();
//! let config = supported_config.into();
//! let stream = match sample_format {
//! SampleFormat::F32 => device.build_output_stream(&config, write_silence::<f32>, err_fn, None),
//! SampleFormat::I16 => device.build_output_stream(&config, write_silence::<i16>, err_fn, None),
//! SampleFormat::U16 => device.build_output_stream(&config, write_silence::<u16>, err_fn, None),
//! sample_format => panic!("Unsupported sample format '{sample_format}'")
//! }.unwrap();
//!
//! fn write_silence<T: Sample>(data: &mut [T], _: &cpal::OutputCallbackInfo) {
//! for sample in data.iter_mut() {
//! *sample = Sample::EQUILIBRIUM;
//! }
//! }
//! ```
//!
//! Not all platforms automatically run the stream upon creation. To ensure the stream has started,
//! we can use [`Stream::play`](traits::StreamTrait::play).
//!
//! ```no_run
//! # use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
//! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap();
//! # let sample_format = supported_config.sample_format();
//! # let config = supported_config.into();
//! # let data_fn = move |_data: &mut cpal::Data, _: &cpal::OutputCallbackInfo| {};
//! # let err_fn = move |_err| {};
//! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn, None).unwrap();
//! stream.play().unwrap();
//! ```
//!
//! Some devices support pausing the audio stream. This can be useful for saving energy in moments
//! of silence.
//!
//! ```no_run
//! # use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
//! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap();
//! # let sample_format = supported_config.sample_format();
//! # let config = supported_config.into();
//! # let data_fn = move |_data: &mut cpal::Data, _: &cpal::OutputCallbackInfo| {};
//! # let err_fn = move |_err| {};
//! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn, None).unwrap();
//! stream.pause().unwrap();
//! ```
//!
//! [`default_input_device()`]: traits::HostTrait::default_input_device
//! [`default_output_device()`]: traits::HostTrait::default_output_device
//! [`devices()`]: traits::HostTrait::devices
//! [`supported_input_configs()`]: traits::DeviceTrait::supported_input_configs
//! [`supported_output_configs()`]: traits::DeviceTrait::supported_output_configs
#![recursion_limit = "2048"]
// Extern crate declarations with `#[macro_use]` must unfortunately be at crate root.
#[cfg(target_os = "emscripten")]
#[macro_use]
extern crate wasm_bindgen;
#[cfg(target_os = "emscripten")]
extern crate js_sys;
#[cfg(target_os = "emscripten")]
extern crate web_sys;
pub use error::*;
pub use platform::{
available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream,
SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS,
};
pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, I48, U24, U48};
use std::convert::TryInto;
use std::ops::{Div, Mul};
use std::time::Duration;
#[cfg(target_os = "emscripten")]
use wasm_bindgen::prelude::*;
mod error;
mod host;
pub mod platform;
mod samples_formats;
pub mod traits;
/// A host's device iterator yielding only *input* devices.
pub type InputDevices<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;
/// A host's device iterator yielding only *output* devices.
pub type OutputDevices<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;
/// Number of channels.
pub type ChannelCount = u16;
/// The number of samples processed per second for a single channel of audio.
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SampleRate(pub u32);
impl<T> Mul<T> for SampleRate
where
u32: Mul<T, Output = u32>,
{
type Output = Self;
fn mul(self, rhs: T) -> Self {
SampleRate(self.0 * rhs)
}
}
impl<T> Div<T> for SampleRate
where
u32: Div<T, Output = u32>,
{
type Output = Self;
fn div(self, rhs: T) -> Self {
SampleRate(self.0 / rhs)
}
}
/// The desired number of frames for the hardware buffer.
pub type FrameCount = u32;
/// The buffer size used by the device.
///
/// [`Default`] is used when no specific buffer size is set and uses the default
/// behavior of the given host. Note, the default buffer size may be surprisingly
/// large, leading to latency issues. If low latency is desired, [`Fixed(FrameCount)`]
/// should be used in accordance with the [`SupportedBufferSize`] range produced by
/// the [`SupportedStreamConfig`] API.
///
/// [`Default`]: BufferSize::Default
/// [`Fixed(FrameCount)`]: BufferSize::Fixed
/// [`SupportedStreamConfig`]: SupportedStreamConfig::buffer_size
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BufferSize {
Default,
Fixed(FrameCount),
}
#[cfg(target_os = "emscripten")]
impl wasm_bindgen::describe::WasmDescribe for BufferSize {
fn describe() {}
}
#[cfg(target_os = "emscripten")]
impl wasm_bindgen::convert::IntoWasmAbi for BufferSize {
type Abi = Option<u32>;
fn into_abi(self) -> Self::Abi {
match self {
Self::Default => None,
Self::Fixed(fc) => Some(fc),
}
.into_abi()
}
}
/// 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)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StreamConfig {
pub channels: ChannelCount,
pub sample_rate: SampleRate,
pub buffer_size: BufferSize,
}
/// Describes the minimum and maximum supported buffer size for the device
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SupportedBufferSize {
Range {
min: FrameCount,
max: FrameCount,
},
/// In the case that the platform provides no way of getting the default
/// buffersize before starting a stream.
Unknown,
}
/// Describes a range of supported stream configurations, retrieved via the
/// [`Device::supported_input/output_configs`](traits::DeviceTrait#required-methods) method.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedStreamConfigRange {
pub(crate) channels: ChannelCount,
/// Minimum value for the samples rate of the supported formats.
pub(crate) min_sample_rate: SampleRate,
/// Maximum value for the samples rate of the supported formats.
pub(crate) max_sample_rate: SampleRate,
/// Buffersize ranges supported by the device
pub(crate) buffer_size: SupportedBufferSize,
/// Type of data expected by the device.
pub(crate) sample_format: SampleFormat,
}
/// Describes a single supported stream configuration, retrieved via either a
/// [`SupportedStreamConfigRange`] instance or one of the
/// [`Device::default_input/output_config`](traits::DeviceTrait#required-methods) methods.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SupportedStreamConfig {
channels: ChannelCount,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
}
/// 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`.
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Debug)]
pub struct Data {
data: *mut (),
len: usize,
sample_format: SampleFormat,
}
/// A monotonic time instance associated with a stream, retrieved from either:
///
/// 1. A timestamp provided to the stream's underlying audio data callback or
/// 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.
///
/// ## Host `StreamInstant` Sources
///
/// | Host | Source |
/// | ---- | ------ |
/// | alsa | `snd_pcm_status_get_htstamp` |
/// | coreaudio | `mach_absolute_time` |
/// | wasapi | `QueryPerformanceCounter` |
/// | asio | `timeGetTime` |
/// | emscripten | `AudioContext.getOutputTimestamp` |
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct StreamInstant {
secs: i64,
nanos: u32,
}
/// A timestamp associated with a call to an input stream's data callback.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct InputStreamTimestamp {
/// The instant the stream's data callback was invoked.
pub callback: StreamInstant,
/// The instant that data was captured from the device.
///
/// E.g. The instant data was read from an ADC.
pub capture: StreamInstant,
}
/// A timestamp associated with a call to an output stream's data callback.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct OutputStreamTimestamp {
/// The instant the stream's data callback was invoked.
pub callback: StreamInstant,
/// The predicted instant that data written will be delivered to the device for playback.
///
/// E.g. The instant data will be played by a DAC.
pub playback: StreamInstant,
}
/// Information relevant to a single call to the user's input stream data callback.
#[derive(Debug, Clone, PartialEq, Eq)]
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)]
pub struct OutputCallbackInfo {
timestamp: OutputStreamTimestamp,
}
impl SupportedStreamConfig {
pub fn new(
channels: ChannelCount,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
) -> Self {
Self {
channels,
sample_rate,
buffer_size,
sample_format,
}
}
pub fn channels(&self) -> ChannelCount {
self.channels
}
pub fn sample_rate(&self) -> SampleRate {
self.sample_rate
}
pub fn buffer_size(&self) -> &SupportedBufferSize {
&self.buffer_size
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
pub fn config(&self) -> StreamConfig {
StreamConfig {
channels: self.channels,
sample_rate: self.sample_rate,
buffer_size: BufferSize::Default,
}
}
}
impl StreamInstant {
/// The amount of time elapsed from another instant to this one.
///
/// Returns `None` if `earlier` is later than self.
pub fn duration_since(&self, earlier: &Self) -> Option<Duration> {
if self < earlier {
None
} else {
(self.as_nanos() - earlier.as_nanos())
.try_into()
.ok()
.map(Duration::from_nanos)
}
}
/// Returns the instant in time after the given duration has passed.
///
/// Returns `None` if the resulting instant would exceed the bounds of the underlying data
/// structure.
pub fn add(&self, duration: Duration) -> Option<Self> {
self.as_nanos()
.checked_add(duration.as_nanos() as i128)
.and_then(Self::from_nanos_i128)
}
/// Returns the instant in time one `duration` ago.
///
/// Returns `None` if the resulting instant would underflow. As a result, it is important to
/// consider that on some platforms the [`StreamInstant`] may begin at `0` from the moment the
/// source stream is created.
pub fn sub(&self, duration: Duration) -> Option<Self> {
self.as_nanos()
.checked_sub(duration.as_nanos() as i128)
.and_then(Self::from_nanos_i128)
}
fn as_nanos(&self) -> i128 {
(self.secs as i128 * 1_000_000_000) + self.nanos as i128
}
#[allow(dead_code)]
fn from_nanos(nanos: i64) -> Self {
let secs = nanos / 1_000_000_000;
let subsec_nanos = nanos - secs * 1_000_000_000;
Self::new(secs, subsec_nanos as u32)
}
#[allow(dead_code)]
fn from_nanos_i128(nanos: i128) -> Option<Self> {
let secs = nanos / 1_000_000_000;
if secs > i64::MAX as i128 || secs < i64::MIN as i128 {
None
} else {
let subsec_nanos = nanos - secs * 1_000_000_000;
debug_assert!(subsec_nanos < u32::MAX as i128);
Some(Self::new(secs as i64, subsec_nanos as u32))
}
}
#[allow(dead_code)]
fn from_secs_f64(secs: f64) -> crate::StreamInstant {
let s = secs.floor() as i64;
let ns = ((secs - s as f64) * 1_000_000_000.0) as u32;
Self::new(s, ns)
}
fn new(secs: i64, nanos: u32) -> Self {
StreamInstant { secs, nanos }
}
}
impl InputCallbackInfo {
/// The timestamp associated with the call to an input stream's data callback.
pub fn timestamp(&self) -> InputStreamTimestamp {
self.timestamp
}
}
impl OutputCallbackInfo {
/// The timestamp associated with the call to an output stream's data callback.
pub fn timestamp(&self) -> OutputStreamTimestamp {
self.timestamp
}
}
#[allow(clippy::len_without_is_empty)]
impl Data {
// Internal constructor for host implementations to use.
//
// The following requirements must be met in order for the safety of `Data`'s public API.
//
// - The `data` pointer must point to the first sample in the slice containing all samples.
// - The `len` must describe the length of the buffer as a number of samples in the expected
// format specified via the `sample_format` argument.
// - The `sample_format` must correctly represent the underlying sample data delivered/expected
// by the stream.
pub(crate) unsafe fn from_parts(
data: *mut (),
len: usize,
sample_format: SampleFormat,
) -> Self {
Data {
data,
len,
sample_format,
}
}
/// The sample format of the internal audio data.
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
/// The full length of the buffer in samples.
///
/// The returned length is the same length as the slice of type `T` that would be returned via
/// [`as_slice`](Self::as_slice) given a sample type that matches the inner sample format.
pub fn len(&self) -> usize {
self.len
}
/// The raw slice of memory representing the underlying audio data as a slice of bytes.
///
/// It is up to the user to interpret the slice of memory based on [`Data::sample_format`].
pub fn bytes(&self) -> &[u8] {
let len = self.len * self.sample_format.sample_size();
// The safety of this block relies on correct construction of the `Data` instance.
// See the unsafe `from_parts` constructor for these requirements.
unsafe { std::slice::from_raw_parts(self.data as *const u8, len) }
}
/// The raw slice of memory representing the underlying audio data as a slice of bytes.
///
/// It is up to the user to interpret the slice of memory based on [`Data::sample_format`].
pub fn bytes_mut(&mut self) -> &mut [u8] {
let len = self.len * self.sample_format.sample_size();
// The safety of this block relies on correct construction of the `Data` instance. See
// the unsafe `from_parts` constructor for these requirements.
unsafe { std::slice::from_raw_parts_mut(self.data as *mut u8, len) }
}
/// Access the data as a slice of sample type `T`.
///
/// Returns `None` if the sample type does not match the expected sample format.
pub fn as_slice<T>(&self) -> Option<&[T]>
where
T: SizedSample,
{
if T::FORMAT == self.sample_format {
// The safety of this block relies on correct construction of the `Data` instance. See
// the unsafe `from_parts` constructor for these requirements.
unsafe { Some(std::slice::from_raw_parts(self.data as *const T, self.len)) }
} else {
None
}
}
/// Access the data as a slice of sample type `T`.
///
/// Returns `None` if the sample type does not match the expected sample format.
pub fn as_slice_mut<T>(&mut self) -> Option<&mut [T]>
where
T: SizedSample,
{
if T::FORMAT == self.sample_format {
// The safety of this block relies on correct construction of the `Data` instance. See
// the unsafe `from_parts` constructor for these requirements.
unsafe {
Some(std::slice::from_raw_parts_mut(
self.data as *mut T,
self.len,
))
}
} else {
None
}
}
}
impl SupportedStreamConfigRange {
pub fn new(
channels: ChannelCount,
min_sample_rate: SampleRate,
max_sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
sample_format: SampleFormat,
) -> Self {
Self {
channels,
min_sample_rate,
max_sample_rate,
buffer_size,
sample_format,
}
}
pub fn channels(&self) -> ChannelCount {
self.channels
}
pub fn min_sample_rate(&self) -> SampleRate {
self.min_sample_rate
}
pub fn max_sample_rate(&self) -> SampleRate {
self.max_sample_rate
}
pub fn buffer_size(&self) -> &SupportedBufferSize {
&self.buffer_size
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
/// Retrieve a [`SupportedStreamConfig`] with the given sample rate and buffer size.
///
/// # Panics
///
/// Panics if the given `sample_rate` is outside the range specified within
/// this [`SupportedStreamConfigRange`] instance. For a non-panicking
/// variant, use [`try_with_sample_rate`](#method.try_with_sample_rate).
pub fn with_sample_rate(self, sample_rate: SampleRate) -> SupportedStreamConfig {
self.try_with_sample_rate(sample_rate)
.expect("sample rate out of range")
}
/// Retrieve a [`SupportedStreamConfig`] with the given sample rate and buffer size.
///
/// Returns `None` if the given sample rate is outside the range specified
/// within this [`SupportedStreamConfigRange`] instance.
pub fn try_with_sample_rate(self, sample_rate: SampleRate) -> Option<SupportedStreamConfig> {
if self.min_sample_rate <= sample_rate && sample_rate <= self.max_sample_rate {
Some(SupportedStreamConfig {
channels: self.channels,
sample_rate,
sample_format: self.sample_format,
buffer_size: self.buffer_size,
})
} else {
None
}
}
/// Turns this [`SupportedStreamConfigRange`] into a [`SupportedStreamConfig`] corresponding to the maximum samples rate.
#[inline]
pub fn with_max_sample_rate(self) -> SupportedStreamConfig {
SupportedStreamConfig {
channels: self.channels,
sample_rate: self.max_sample_rate,
sample_format: self.sample_format,
buffer_size: self.buffer_size,
}
}
/// A comparison function which compares two [`SupportedStreamConfigRange`]s in terms of their priority of
/// use as a default stream format.
///
/// Some backends do not provide a default stream format for their audio devices. In these
/// cases, CPAL attempts to decide on a reasonable default format for the user. To do this we
/// use the "greatest" of all supported stream formats when compared with this method.
///
/// SupportedStreamConfigs are prioritised by the following heuristics:
///
/// **Channels**:
///
/// - Stereo
/// - Mono
/// - Max available channels
///
/// **Sample format**:
/// - f32
/// - i16
/// - u16
///
/// **Sample rate**:
///
/// - 44100 (cd quality)
/// - Max sample rate
pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
use SampleFormat::{F32, I16, U16};
let cmp_stereo = (self.channels == 2).cmp(&(other.channels == 2));
if cmp_stereo != Equal {
return cmp_stereo;
}
let cmp_mono = (self.channels == 1).cmp(&(other.channels == 1));
if cmp_mono != Equal {
return cmp_mono;
}
let cmp_channels = self.channels.cmp(&other.channels);
if cmp_channels != Equal {
return cmp_channels;
}
let cmp_f32 = (self.sample_format == F32).cmp(&(other.sample_format == F32));
if cmp_f32 != Equal {
return cmp_f32;
}
let cmp_i16 = (self.sample_format == I16).cmp(&(other.sample_format == I16));
if cmp_i16 != Equal {
return cmp_i16;
}
let cmp_u16 = (self.sample_format == U16).cmp(&(other.sample_format == U16));
if cmp_u16 != Equal {
return cmp_u16;
}
const HZ_44100: SampleRate = SampleRate(44_100);
let r44100_in_self = self.min_sample_rate <= HZ_44100 && HZ_44100 <= self.max_sample_rate;
let r44100_in_other =
other.min_sample_rate <= HZ_44100 && HZ_44100 <= other.max_sample_rate;
let cmp_r44100 = r44100_in_self.cmp(&r44100_in_other);
if cmp_r44100 != Equal {
return cmp_r44100;
}
self.max_sample_rate.cmp(&other.max_sample_rate)
}
}
#[test]
fn test_cmp_default_heuristics() {
let mut formats = [
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: SampleRate(1),
max_sample_rate: SampleRate(96000),
sample_format: SampleFormat::F32,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 1,
min_sample_rate: SampleRate(1),
max_sample_rate: SampleRate(96000),
sample_format: SampleFormat::F32,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: SampleRate(1),
max_sample_rate: SampleRate(96000),
sample_format: SampleFormat::I16,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: SampleRate(1),
max_sample_rate: SampleRate(96000),
sample_format: SampleFormat::U16,
},
SupportedStreamConfigRange {
buffer_size: SupportedBufferSize::Range { min: 256, max: 512 },
channels: 2,
min_sample_rate: SampleRate(1),
max_sample_rate: SampleRate(22050),
sample_format: SampleFormat::F32,
},
];
formats.sort_by(|a, b| a.cmp_default_heuristics(b));
// lowest-priority first:
assert_eq!(formats[0].sample_format(), SampleFormat::F32);
assert_eq!(formats[0].min_sample_rate(), SampleRate(1));
assert_eq!(formats[0].max_sample_rate(), SampleRate(96000));
assert_eq!(formats[0].channels(), 1);
assert_eq!(formats[1].sample_format(), SampleFormat::U16);
assert_eq!(formats[1].min_sample_rate(), SampleRate(1));
assert_eq!(formats[1].max_sample_rate(), SampleRate(96000));
assert_eq!(formats[1].channels(), 2);
assert_eq!(formats[2].sample_format(), SampleFormat::I16);
assert_eq!(formats[2].min_sample_rate(), SampleRate(1));
assert_eq!(formats[2].max_sample_rate(), SampleRate(96000));
assert_eq!(formats[2].channels(), 2);
assert_eq!(formats[3].sample_format(), SampleFormat::F32);
assert_eq!(formats[3].min_sample_rate(), SampleRate(1));
assert_eq!(formats[3].max_sample_rate(), SampleRate(22050));
assert_eq!(formats[3].channels(), 2);
assert_eq!(formats[4].sample_format(), SampleFormat::F32);
assert_eq!(formats[4].min_sample_rate(), SampleRate(1));
assert_eq!(formats[4].max_sample_rate(), SampleRate(96000));
assert_eq!(formats[4].channels(), 2);
}
impl From<SupportedStreamConfig> for StreamConfig {
fn from(conf: SupportedStreamConfig) -> Self {
conf.config()
}
}
// If a backend does not provide an API for retrieving supported formats, we query it with a bunch
// of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa.
//
// If a rate you desire is missing from this list, feel free to add it!
#[cfg(target_os = "windows")]
const COMMON_SAMPLE_RATES: &[SampleRate] = &[
SampleRate(5512),
SampleRate(8000),
SampleRate(11025),
SampleRate(16000),
SampleRate(22050),
SampleRate(32000),
SampleRate(44100),
SampleRate(48000),
SampleRate(64000),
SampleRate(88200),
SampleRate(96000),
SampleRate(176400),
SampleRate(192000),
];
#[test]
fn test_stream_instant() {
let a = StreamInstant::new(2, 0);
let b = StreamInstant::new(-2, 0);
let min = StreamInstant::new(i64::MIN, 0);
let max = StreamInstant::new(i64::MAX, 0);
assert_eq!(
a.sub(Duration::from_secs(1)),
Some(StreamInstant::new(1, 0))
);
assert_eq!(
a.sub(Duration::from_secs(2)),
Some(StreamInstant::new(0, 0))
);
assert_eq!(
a.sub(Duration::from_secs(3)),
Some(StreamInstant::new(-1, 0))
);
assert_eq!(min.sub(Duration::from_secs(1)), None);
assert_eq!(
b.add(Duration::from_secs(1)),
Some(StreamInstant::new(-1, 0))
);
assert_eq!(
b.add(Duration::from_secs(2)),
Some(StreamInstant::new(0, 0))
);
assert_eq!(
b.add(Duration::from_secs(3)),
Some(StreamInstant::new(1, 0))
);
assert_eq!(max.add(Duration::from_secs(1)), None);
}
-742
View File
@@ -1,742 +0,0 @@
//! Platform-specific items.
//!
//! This module also contains the implementation of the platform's dynamically dispatched [`Host`]
//! type and its associated [`Device`], [`Stream`] and other associated types. These
//! types are useful in the case that users require switching between audio host APIs at runtime.
#[doc(inline)]
pub use self::platform_impl::*;
/// A macro to assist with implementing a platform's dynamically dispatched [`Host`] type.
///
/// These dynamically dispatched types are necessary to allow for users to switch between hosts at
/// runtime.
///
/// For example the invocation `impl_platform_host(Wasapi wasapi "WASAPI", Asio asio "ASIO")`,
/// this macro should expand to:
///
// This sample code block is marked as text because it's not a valid test,
// it's just illustrative. (see rust issue #96573)
/// ```text
/// pub enum HostId {
/// Wasapi,
/// Asio,
/// }
///
/// pub enum Host {
/// Wasapi(crate::host::wasapi::Host),
/// Asio(crate::host::asio::Host),
/// }
/// ```
///
/// And so on for Device, Devices, Host, Stream, SupportedInputConfigs,
/// SupportedOutputConfigs and all their necessary trait implementations.
///
macro_rules! impl_platform_host {
($($(#[cfg($feat: meta)])? $HostVariant:ident $host_mod:ident $host_name:literal),*) => {
/// All hosts supported by CPAL on this platform.
pub const ALL_HOSTS: &'static [HostId] = &[
$(
$(#[cfg($feat)])?
HostId::$HostVariant,
)*
];
/// The platform's dynamically dispatched `Host` type.
///
/// An instance of this `Host` type may represent one of the `Host`s available
/// on the platform.
///
/// Use this type if you require switching between available hosts at runtime.
///
/// This type may be constructed via the [`host_from_id`] function. [`HostId`]s may
/// be acquired via the [`ALL_HOSTS`] const, and the [`available_hosts`] function.
pub struct Host(HostInner);
/// The `Device` implementation associated with the platform's dynamically dispatched
/// [`Host`] type.
#[derive(Clone)]
pub struct Device(DeviceInner);
/// The `Devices` iterator associated with the platform's dynamically dispatched [`Host`]
/// type.
pub struct Devices(DevicesInner);
/// The `Stream` implementation associated with the platform's dynamically dispatched
/// [`Host`] type.
// Streams cannot be `Send` or `Sync` if we plan to support Android's AAudio API. This is
// because the stream API is not thread-safe, and the API prohibits calling certain
// functions within the callback.
//
// TODO: Confirm this and add more specific detail and references.
#[must_use = "If the stream is not stored it will not play."]
pub struct Stream(StreamInner, crate::platform::NotSendSyncAcrossAllPlatforms);
/// The `SupportedInputConfigs` iterator associated with the platform's dynamically
/// dispatched [`Host`] type.
pub struct SupportedInputConfigs(SupportedInputConfigsInner);
/// The `SupportedOutputConfigs` iterator associated with the platform's dynamically
/// dispatched [`Host`] type.
pub struct SupportedOutputConfigs(SupportedOutputConfigsInner);
/// Unique identifier for available hosts on the platform.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum HostId {
$(
$(#[cfg($feat)])?
$HostVariant,
)*
}
/// Contains a platform specific [`Device`] implementation.
#[derive(Clone)]
pub enum DeviceInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::Device),
)*
}
/// Contains a platform specific [`Devices`] implementation.
pub enum DevicesInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::Devices),
)*
}
/// Contains a platform specific [`Host`] implementation.
pub enum HostInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::Host),
)*
}
/// Contains a platform specific [`Stream`] implementation.
pub enum StreamInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::Stream),
)*
}
enum SupportedInputConfigsInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::SupportedInputConfigs),
)*
}
enum SupportedOutputConfigsInner {
$(
$(#[cfg($feat)])?
$HostVariant(crate::host::$host_mod::SupportedOutputConfigs),
)*
}
impl HostId {
pub fn name(&self) -> &'static str {
match self {
$(
$(#[cfg($feat)])?
HostId::$HostVariant => $host_name,
)*
}
}
}
impl Devices {
/// Returns a reference to the underlying platform specific implementation of this
/// `Devices`.
pub fn as_inner(&self) -> &DevicesInner {
&self.0
}
/// Returns a mutable reference to the underlying platform specific implementation of
/// this `Devices`.
pub fn as_inner_mut(&mut self) -> &mut DevicesInner {
&mut self.0
}
/// Returns the underlying platform specific implementation of this `Devices`.
pub fn into_inner(self) -> DevicesInner {
self.0
}
}
impl Device {
/// Returns a reference to the underlying platform specific implementation of this
/// `Device`.
pub fn as_inner(&self) -> &DeviceInner {
&self.0
}
/// Returns a mutable reference to the underlying platform specific implementation of
/// this `Device`.
pub fn as_inner_mut(&mut self) -> &mut DeviceInner {
&mut self.0
}
/// Returns the underlying platform specific implementation of this `Device`.
pub fn into_inner(self) -> DeviceInner {
self.0
}
}
impl Host {
/// The unique identifier associated with this `Host`.
pub fn id(&self) -> HostId {
match self.0 {
$(
$(#[cfg($feat)])?
HostInner::$HostVariant(_) => HostId::$HostVariant,
)*
}
}
/// Returns a reference to the underlying platform specific implementation of this
/// `Host`.
pub fn as_inner(&self) -> &HostInner {
&self.0
}
/// Returns a mutable reference to the underlying platform specific implementation of
/// this `Host`.
pub fn as_inner_mut(&mut self) -> &mut HostInner {
&mut self.0
}
/// Returns the underlying platform specific implementation of this `Host`.
pub fn into_inner(self) -> HostInner {
self.0
}
}
impl Stream {
/// Returns a reference to the underlying platform specific implementation of this
/// `Stream`.
pub fn as_inner(&self) -> &StreamInner {
&self.0
}
/// Returns a mutable reference to the underlying platform specific implementation of
/// this `Stream`.
pub fn as_inner_mut(&mut self) -> &mut StreamInner {
&mut self.0
}
/// Returns the underlying platform specific implementation of this `Stream`.
pub fn into_inner(self) -> StreamInner {
self.0
}
}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
$(
$(#[cfg($feat)])?
DevicesInner::$HostVariant(ref mut d) => {
d.next().map(DeviceInner::$HostVariant).map(Device::from)
}
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
$(
$(#[cfg($feat)])?
DevicesInner::$HostVariant(ref d) => d.size_hint(),
)*
}
}
}
impl Iterator for SupportedInputConfigs {
type Item = crate::SupportedStreamConfigRange;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
$(
$(#[cfg($feat)])?
SupportedInputConfigsInner::$HostVariant(ref mut s) => s.next(),
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
$(
$(#[cfg($feat)])?
SupportedInputConfigsInner::$HostVariant(ref d) => d.size_hint(),
)*
}
}
}
impl Iterator for SupportedOutputConfigs {
type Item = crate::SupportedStreamConfigRange;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
$(
$(#[cfg($feat)])?
SupportedOutputConfigsInner::$HostVariant(ref mut s) => s.next(),
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
$(
$(#[cfg($feat)])?
SupportedOutputConfigsInner::$HostVariant(ref d) => d.size_hint(),
)*
}
}
}
impl crate::traits::DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn name(&self) -> Result<String, crate::DeviceNameError> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d.name(),
)*
}
}
fn supported_input_configs(&self) -> Result<Self::SupportedInputConfigs, crate::SupportedStreamConfigsError> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => {
d.supported_input_configs()
.map(SupportedInputConfigsInner::$HostVariant)
.map(SupportedInputConfigs)
}
)*
}
}
fn supported_output_configs(&self) -> Result<Self::SupportedOutputConfigs, crate::SupportedStreamConfigsError> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => {
d.supported_output_configs()
.map(SupportedOutputConfigsInner::$HostVariant)
.map(SupportedOutputConfigs)
}
)*
}
}
fn default_input_config(&self) -> Result<crate::SupportedStreamConfig, crate::DefaultStreamConfigError> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d.default_input_config(),
)*
}
}
fn default_output_config(&self) -> Result<crate::SupportedStreamConfig, crate::DefaultStreamConfigError> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d.default_output_config(),
)*
}
}
fn build_input_stream_raw<D, E>(
&self,
config: &crate::StreamConfig,
sample_format: crate::SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<std::time::Duration>,
) -> Result<Self::Stream, crate::BuildStreamError>
where
D: FnMut(&crate::Data, &crate::InputCallbackInfo) + Send + 'static,
E: FnMut(crate::StreamError) + Send + 'static,
{
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d
.build_input_stream_raw(
config,
sample_format,
data_callback,
error_callback,
timeout,
)
.map(StreamInner::$HostVariant)
.map(Stream::from),
)*
}
}
fn build_output_stream_raw<D, E>(
&self,
config: &crate::StreamConfig,
sample_format: crate::SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<std::time::Duration>,
) -> Result<Self::Stream, crate::BuildStreamError>
where
D: FnMut(&mut crate::Data, &crate::OutputCallbackInfo) + Send + 'static,
E: FnMut(crate::StreamError) + Send + 'static,
{
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d
.build_output_stream_raw(
config,
sample_format,
data_callback,
error_callback,
timeout,
)
.map(StreamInner::$HostVariant)
.map(Stream::from),
)*
}
}
}
impl crate::traits::HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
$(
$(#[cfg($feat)])?
if crate::host::$host_mod::Host::is_available() { return true; }
)*
false
}
fn devices(&self) -> Result<Self::Devices, crate::DevicesError> {
match self.0 {
$(
$(#[cfg($feat)])?
HostInner::$HostVariant(ref h) => {
h.devices().map(DevicesInner::$HostVariant).map(Devices::from)
}
)*
}
}
fn default_input_device(&self) -> Option<Self::Device> {
match self.0 {
$(
$(#[cfg($feat)])?
HostInner::$HostVariant(ref h) => {
h.default_input_device().map(DeviceInner::$HostVariant).map(Device::from)
}
)*
}
}
fn default_output_device(&self) -> Option<Self::Device> {
match self.0 {
$(
$(#[cfg($feat)])?
HostInner::$HostVariant(ref h) => {
h.default_output_device().map(DeviceInner::$HostVariant).map(Device::from)
}
)*
}
}
}
impl crate::traits::StreamTrait for Stream {
fn play(&self) -> Result<(), crate::PlayStreamError> {
match self.0 {
$(
$(#[cfg($feat)])?
StreamInner::$HostVariant(ref s) => {
s.play()
}
)*
}
}
fn pause(&self) -> Result<(), crate::PauseStreamError> {
match self.0 {
$(
$(#[cfg($feat)])?
StreamInner::$HostVariant(ref s) => {
s.pause()
}
)*
}
}
}
impl From<DeviceInner> for Device {
fn from(d: DeviceInner) -> Self {
Device(d)
}
}
impl From<DevicesInner> for Devices {
fn from(d: DevicesInner) -> Self {
Devices(d)
}
}
impl From<HostInner> for Host {
fn from(h: HostInner) -> Self {
Host(h)
}
}
impl From<StreamInner> for Stream {
fn from(s: StreamInner) -> Self {
Stream(s, Default::default())
}
}
$(
$(#[cfg($feat)])?
impl From<crate::host::$host_mod::Device> for Device {
fn from(h: crate::host::$host_mod::Device) -> Self {
DeviceInner::$HostVariant(h).into()
}
}
$(#[cfg($feat)])?
impl From<crate::host::$host_mod::Devices> for Devices {
fn from(h: crate::host::$host_mod::Devices) -> Self {
DevicesInner::$HostVariant(h).into()
}
}
$(#[cfg($feat)])?
impl From<crate::host::$host_mod::Host> for Host {
fn from(h: crate::host::$host_mod::Host) -> Self {
HostInner::$HostVariant(h).into()
}
}
$(#[cfg($feat)])?
impl From<crate::host::$host_mod::Stream> for Stream {
fn from(h: crate::host::$host_mod::Stream) -> Self {
StreamInner::$HostVariant(h).into()
}
}
)*
/// Produces a list of hosts that are currently available on the system.
pub fn available_hosts() -> Vec<HostId> {
let mut host_ids = vec![];
$(
$(#[cfg($feat)])?
if <crate::host::$host_mod::Host as crate::traits::HostTrait>::is_available() {
host_ids.push(HostId::$HostVariant);
}
)*
host_ids
}
/// Given a unique host identifier, initialise and produce the host if it is available.
pub fn host_from_id(id: HostId) -> Result<Host, crate::HostUnavailable> {
match id {
$(
$(#[cfg($feat)])?
HostId::$HostVariant => {
crate::host::$host_mod::Host::new()
.map(HostInner::$HostVariant)
.map(Host::from)
}
)*
}
}
};
}
// TODO: Add pulseaudio and jack here eventually.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
))]
mod platform_impl {
pub use crate::host::alsa::{
Device as AlsaDevice, Devices as AlsaDevices, Host as AlsaHost, Stream as AlsaStream,
SupportedInputConfigs as AlsaSupportedInputConfigs,
SupportedOutputConfigs as AlsaSupportedOutputConfigs,
};
#[cfg(feature = "jack")]
pub use crate::host::jack::{
Device as JackDevice, Devices as JackDevices, Host as JackHost, Stream as JackStream,
SupportedInputConfigs as JackSupportedInputConfigs,
SupportedOutputConfigs as JackSupportedOutputConfigs,
};
impl_platform_host!(#[cfg(feature = "jack")] Jack jack "JACK", Alsa alsa "ALSA");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
AlsaHost::new()
.expect("the default host should always be available")
.into()
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod platform_impl {
pub use crate::host::coreaudio::{
Device as CoreAudioDevice, Devices as CoreAudioDevices, Host as CoreAudioHost,
Stream as CoreAudioStream, SupportedInputConfigs as CoreAudioSupportedInputConfigs,
SupportedOutputConfigs as CoreAudioSupportedOutputConfigs,
};
impl_platform_host!(CoreAudio coreaudio "CoreAudio");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
CoreAudioHost::new()
.expect("the default host should always be available")
.into()
}
}
#[cfg(target_os = "emscripten")]
mod platform_impl {
pub use crate::host::emscripten::{
Device as EmscriptenDevice, Devices as EmscriptenDevices, Host as EmscriptenHost,
Stream as EmscriptenStream, SupportedInputConfigs as EmscriptenSupportedInputConfigs,
SupportedOutputConfigs as EmscriptenSupportedOutputConfigs,
};
impl_platform_host!(Emscripten emscripten "Emscripten");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
EmscriptenHost::new()
.expect("the default host should always be available")
.into()
}
}
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
mod platform_impl {
pub use crate::host::webaudio::{
Device as WebAudioDevice, Devices as WebAudioDevices, Host as WebAudioHost,
Stream as WebAudioStream, SupportedInputConfigs as WebAudioSupportedInputConfigs,
SupportedOutputConfigs as WebAudioSupportedOutputConfigs,
};
impl_platform_host!(WebAudio webaudio "WebAudio");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
WebAudioHost::new()
.expect("the default host should always be available")
.into()
}
}
#[cfg(windows)]
mod platform_impl {
#[cfg(feature = "asio")]
pub use crate::host::asio::{
Device as AsioDevice, Devices as AsioDevices, Host as AsioHost, Stream as AsioStream,
SupportedInputConfigs as AsioSupportedInputConfigs,
SupportedOutputConfigs as AsioSupportedOutputConfigs,
};
pub use crate::host::wasapi::{
Device as WasapiDevice, Devices as WasapiDevices, Host as WasapiHost,
Stream as WasapiStream, SupportedInputConfigs as WasapiSupportedInputConfigs,
SupportedOutputConfigs as WasapiSupportedOutputConfigs,
};
impl_platform_host!(#[cfg(feature = "asio")] Asio asio "ASIO", Wasapi wasapi "WASAPI");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
WasapiHost::new()
.expect("the default host should always be available")
.into()
}
}
#[cfg(target_os = "android")]
mod platform_impl {
pub use crate::host::oboe::{
Device as OboeDevice, Devices as OboeDevices, Host as OboeHost, Stream as OboeStream,
SupportedInputConfigs as OboeSupportedInputConfigs,
SupportedOutputConfigs as OboeSupportedOutputConfigs,
};
impl_platform_host!(Oboe oboe "Oboe");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
OboeHost::new()
.expect("the default host should always be available")
.into()
}
}
#[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"),
)))]
mod platform_impl {
pub use crate::host::null::{
Device as NullDevice, Devices as NullDevices, Host as NullHost,
SupportedInputConfigs as NullSupportedInputConfigs,
SupportedOutputConfigs as NullSupportedOutputConfigs,
};
impl_platform_host!(Null null "Null");
/// The default host for the current compilation target platform.
pub fn default_host() -> Host {
NullHost::new()
.expect("the default host should always be available")
.into()
}
}
// The following zero-sized types are for applying Send/Sync restrictions to ensure
// consistent behaviour across different platforms. These verbosely named types are used
// (rather than using the markers directly) in the hope of making the compile errors
// slightly more helpful.
//
// TODO: Remove these in favour of using negative trait bounds if they stabilise.
// A marker used to remove the `Send` and `Sync` traits.
struct NotSendSyncAcrossAllPlatforms(std::marker::PhantomData<*mut ()>);
impl Default for NotSendSyncAcrossAllPlatforms {
fn default() -> Self {
NotSendSyncAcrossAllPlatforms(std::marker::PhantomData)
}
}
-167
View File
@@ -1,167 +0,0 @@
use std::{fmt::Display, mem};
#[cfg(target_os = "emscripten")]
use wasm_bindgen::prelude::*;
pub use dasp_sample::{FromSample, Sample, I24, I48, U24, U48};
/// Format that each sample has.
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SampleFormat {
/// `i8` with a valid range of 'u8::MIN..=u8::MAX' with `0` being the origin
I8,
/// `i16` with a valid range of 'u16::MIN..=u16::MAX' with `0` being the origin
I16,
// /// `I24` with a valid range of '-(1 << 23)..(1 << 23)' with `0` being the origin
// I24,
/// `i32` with a valid range of 'u32::MIN..=u32::MAX' with `0` being the origin
I32,
// /// `I24` with a valid range of '-(1 << 47)..(1 << 47)' with `0` being the origin
// I48,
/// `i64` with a valid range of 'u64::MIN..=u64::MAX' with `0` being the origin
I64,
/// `u8` with a valid range of 'u8::MIN..=u8::MAX' with `1 << 7 == 128` being the origin
U8,
/// `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,
/// `u32` with a valid range of 'u32::MIN..=u32::MAX' with `1 << 31` being the origin
U32,
// /// `U48` with a valid range of '0..(1 << 48)' with `1 << 47` being the origin
// U48,
/// `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,
/// `f64` with a valid range of -1.0..1.0 with 0.0 being the origin
F64,
}
impl SampleFormat {
/// Returns the size in bytes of a sample of this format.
#[inline]
#[must_use]
pub fn sample_size(&self) -> usize {
match *self {
SampleFormat::I8 | SampleFormat::U8 => mem::size_of::<i8>(),
SampleFormat::I16 | SampleFormat::U16 => mem::size_of::<i16>(),
// SampleFormat::I24 | SampleFormat::U24 => 3,
SampleFormat::I32 | SampleFormat::U32 => mem::size_of::<i32>(),
// SampleFormat::I48 | SampleFormat::U48 => 6,
SampleFormat::I64 | SampleFormat::U64 => mem::size_of::<i64>(),
SampleFormat::F32 => mem::size_of::<f32>(),
SampleFormat::F64 => mem::size_of::<f64>(),
}
}
#[inline]
#[must_use]
pub fn is_int(&self) -> bool {
//matches!(*self, SampleFormat::I8 | SampleFormat::I16 | SampleFormat::I24 | SampleFormat::I32 | SampleFormat::I48 | SampleFormat::I64)
matches!(
*self,
SampleFormat::I8 | SampleFormat::I16 | SampleFormat::I32 | SampleFormat::I64
)
}
#[inline]
#[must_use]
pub fn is_uint(&self) -> bool {
//matches!(*self, SampleFormat::U8 | SampleFormat::U16 | SampleFormat::U24 | SampleFormat::U32 | SampleFormat::U48 | SampleFormat::U64)
matches!(
*self,
SampleFormat::U8 | SampleFormat::U16 | SampleFormat::U32 | SampleFormat::U64
)
}
#[inline]
#[must_use]
pub fn is_float(&self) -> bool {
matches!(*self, SampleFormat::F32 | SampleFormat::F64)
}
}
impl Display for SampleFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
SampleFormat::I8 => "i8",
SampleFormat::I16 => "i16",
// SampleFormat::I24 => "i24",
SampleFormat::I32 => "i32",
// SampleFormat::I48 => "i48",
SampleFormat::I64 => "i64",
SampleFormat::U8 => "u8",
SampleFormat::U16 => "u16",
// SampleFormat::U24 => "u24",
SampleFormat::U32 => "u32",
// SampleFormat::U48 => "u48",
SampleFormat::U64 => "u64",
SampleFormat::F32 => "f32",
SampleFormat::F64 => "f64",
}
.fmt(f)
}
}
pub trait SizedSample: Sample {
const FORMAT: SampleFormat;
}
impl SizedSample for i8 {
const FORMAT: SampleFormat = SampleFormat::I8;
}
impl SizedSample for i16 {
const FORMAT: SampleFormat = SampleFormat::I16;
}
// impl SizedSample for I24 { const FORMAT: SampleFormat = SampleFormat::I24; }
impl SizedSample for i32 {
const FORMAT: SampleFormat = SampleFormat::I32;
}
// impl SizedSample for I48 { const FORMAT: SampleFormat = SampleFormat::I48; }
impl SizedSample for i64 {
const FORMAT: SampleFormat = SampleFormat::I64;
}
impl SizedSample for u8 {
const FORMAT: SampleFormat = SampleFormat::U8;
}
impl SizedSample for u16 {
const FORMAT: SampleFormat = SampleFormat::U16;
}
// impl SizedSample for U24 { const FORMAT: SampleFormat = SampleFormat::U24; }
impl SizedSample for u32 {
const FORMAT: SampleFormat = SampleFormat::U32;
}
// impl SizedSample for U48 { const FORMAT: SampleFormat = SampleFormat::U48; }
impl SizedSample for u64 {
const FORMAT: SampleFormat = SampleFormat::U64;
}
impl SizedSample for f32 {
const FORMAT: SampleFormat = SampleFormat::F32;
}
impl SizedSample for f64 {
const FORMAT: SampleFormat = SampleFormat::F64;
}
-221
View File
@@ -1,221 +0,0 @@
//! The suite of traits allowing CPAL to abstract over hosts, devices, event loops and stream IDs.
use std::time::Duration;
use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError,
PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
};
/// A [`Host`] provides access to the available audio devices on the system.
///
/// Each platform may have a number of available hosts depending on the system, each with their own
/// pros and cons.
///
/// For example, WASAPI is the standard audio host API that ships with the Windows operating
/// system. However, due to historical limitations with respect to performance and flexibility,
/// Steinberg created the ASIO API providing better audio device support for pro audio and
/// low-latency applications. As a result, it is common for some devices and device capabilities to
/// only be available via ASIO, while others are only available via WASAPI.
///
/// Another great example is the Linux platform. While the ALSA host API is the lowest-level API
/// available to almost all distributions of Linux, its flexibility is limited as it requires that
/// each process have exclusive access to the devices with which they establish streams. PulseAudio
/// is another popular host API that aims to solve this issue by providing user-space mixing,
/// however it has its own limitations w.r.t. low-latency and high-performance audio applications.
/// JACK is yet another host API that is more suitable to pro-audio applications, however it is
/// less readily available by default in many Linux distributions and is known to be tricky to
/// set up.
///
/// [`Host`]: crate::Host
pub trait HostTrait {
/// The type used for enumerating available devices by the host.
type Devices: Iterator<Item = Self::Device>;
/// The `Device` type yielded by the host.
type Device: DeviceTrait;
/// Whether or not the host is available on the system.
fn is_available() -> bool;
/// An iterator yielding all [`Device`](DeviceTrait)s currently available to the host on the system.
///
/// Can be empty if the system does not support audio in general.
fn devices(&self) -> Result<Self::Devices, DevicesError>;
/// The default input audio device on the system.
///
/// Returns `None` if no input device is available.
fn default_input_device(&self) -> Option<Self::Device>;
/// The default output audio device on the system.
///
/// Returns `None` if no output device is available.
fn default_output_device(&self) -> Option<Self::Device>;
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// input stream formats.
///
/// Can be empty if the system does not support audio input.
fn input_devices(&self) -> Result<InputDevices<Self::Devices>, DevicesError> {
fn supports_input<D: DeviceTrait>(device: &D) -> bool {
device
.supported_input_configs()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_input::<Self::Device>))
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// output stream formats.
///
/// Can be empty if the system does not support audio output.
fn output_devices(&self) -> Result<OutputDevices<Self::Devices>, DevicesError> {
fn supports_output<D: DeviceTrait>(device: &D) -> bool {
device
.supported_output_configs()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_output::<Self::Device>))
}
}
/// A device that is capable of audio input and/or output.
///
/// Please note that `Device`s may become invalid if they get disconnected. Therefore, all the
/// methods that involve a device return a `Result` allowing the user to handle this case.
pub trait DeviceTrait {
/// The iterator type yielding supported input stream formats.
type SupportedInputConfigs: Iterator<Item = SupportedStreamConfigRange>;
/// The iterator type yielding supported output stream formats.
type SupportedOutputConfigs: Iterator<Item = SupportedStreamConfigRange>;
/// The stream type created by [`build_input_stream_raw`] and [`build_output_stream_raw`].
///
/// [`build_input_stream_raw`]: Self::build_input_stream_raw
/// [`build_output_stream_raw`]: Self::build_output_stream_raw
type Stream: StreamTrait;
/// The human-readable name of the device.
fn name(&self) -> Result<String, DeviceNameError>;
/// An iterator yielding formats that are supported by the backend.
///
/// Can return an error if the device is no longer valid (e.g. it has been disconnected).
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError>;
/// An iterator yielding output stream formats that are supported by the device.
///
/// Can return an error if the device is no longer valid (e.g. it has been disconnected).
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError>;
/// The default input stream format for the device.
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError>;
/// The default output stream format for the device.
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError>;
/// Create an input stream.
fn build_input_stream<T, D, E>(
&self,
config: &StreamConfig,
mut data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
T: SizedSample,
D: FnMut(&[T], &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
self.build_input_stream_raw(
config,
T::FORMAT,
move |data, info| {
data_callback(
data.as_slice()
.expect("host supplied incorrect sample type"),
info,
)
},
error_callback,
timeout,
)
}
/// Create an output stream.
fn build_output_stream<T, D, E>(
&self,
config: &StreamConfig,
mut data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
T: SizedSample,
D: FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
self.build_output_stream_raw(
config,
T::FORMAT,
move |data, info| {
data_callback(
data.as_slice_mut()
.expect("host supplied incorrect sample type"),
info,
)
},
error_callback,
timeout,
)
}
/// Create a dynamically typed input stream.
fn build_input_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static;
/// Create a dynamically typed output stream.
fn build_output_stream_raw<D, E>(
&self,
config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static;
}
/// A stream created from [`Device`](DeviceTrait), with methods to control playback.
pub trait StreamTrait {
/// Run the stream.
///
/// Note: Not all platforms automatically run the stream upon creation, so it is important to
/// call `play` after creation if it is expected that the stream should run immediately.
fn play(&self) -> Result<(), PlayStreamError>;
/// Some devices support pausing the audio stream. This can be useful for saving energy in
/// moments of silence.
///
/// Note: Not all devices support suspending the stream at the hardware level. This method may
/// fail in these cases.
fn pause(&self) -> Result<(), PauseStreamError>;
}
-1
View File
@@ -1 +0,0 @@
{"v":1}
-6
View File
@@ -1,6 +0,0 @@
{
"git": {
"sha1": "27f2b424060ff4a977285ff83ecd6c076205c121"
},
"path_in_vcs": ""
}
-74
View File
@@ -1,74 +0,0 @@
name: CI
on:
pull_request:
push:
branches: [main, master]
env:
RUSTFLAGS: "-C debuginfo=0 -D warnings"
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
tests:
name: Tests
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
toolchain: [stable, beta, nightly]
include:
- os: macos-latest
MACOS: true
- os: windows-latest
- os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install linux deps
run: |
sudo apt update
sudo apt install -y --no-install-recommends libasound2-dev pkg-config
if: contains(matrix.os, 'ubuntu')
- name: install ${{ matrix.toolchain }} toolchain
id: install_toolchain
run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo clippy -- -D warnings
if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest'
- run: |
rustup component add rustfmt
cargo fmt --all -- --check
if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest'
- run: cargo test --all-targets
- run: cargo test --features=symphonia-all --all-targets
cargo-publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
CRATESIO_TOKEN: ${{ secrets.CRATESIO_TOKEN }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update apt
run: sudo apt update
- name: Install alsa
run: sudo apt install -y --no-install-recommends libasound2-dev pkg-config
- name: Run cargo publish for rodio
continue-on-error: true
run: |
RODIO_TMP=$(mktemp /tmp/rodioXXX.txt) || echo "::error::mktemp error"
echo "RODIO_TMP=$RODIO_TMP" >> $GITHUB_ENV
cargo publish --token $CRATESIO_TOKEN 2> $RODIO_TMP
- name: Check if rodio is already published
run: |
empty=0
RODIO_TMP="${{ env.RODIO_TMP }}"
grep -q '[^[:space:]]' < $RODIO_TMP || empty=1
[ $empty -eq 0 ] && cat $RODIO_TMP
[ $empty -eq 1 ] || grep -q "is already uploaded" < $RODIO_TMP
-2
View File
@@ -1,2 +0,0 @@
target
Cargo.lock
-2
View File
@@ -1,2 +0,0 @@
reorder_imports = true
use_try_shorthand = true
-137
View File
@@ -1,137 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
# Version 0.19.0 (2024-06-29)
### Added
- Adds a new source `track_position`. It keeps track of duration since the
beginning of the underlying source.
### Fixed
- Mp4a with decodable tracks after undecodable tracks now play. This matches
VLC's behaviour.
# Version 0.18.1 (2024-05-23)
### Fixed
- Seek no longer hangs if the sink is empty.
# Version 0.18.0 (2024-05-05)
### Changed
- `Source` trait is now also implemented for `Box<dyn Source>` and `&mut Source`
- `fn new_vorbis` is now also available when the `symphonia-vorbis` feature is enabled
### Added
- Adds a new method `try_seek` to all sources. It returns either an error or
seeks to the given position. A few sources are "unsupported" they return the
error `Unsupported`.
- Adds `SpatialSink::clear()` bringing it in line with `Sink`
### Fixed
- channel upscaling now follows the 'WAVEFORMATEXTENSIBLE' format and no longer
repeats the last source channel on all extra output channels.
Stereo content playing on a 5.1 speaker set will now only use the front left
and front right speaker instead of repeating the right sample on all speakers
except the front left one.
- `mp3::is_mp3()` no longer changes the position in the stream when the stream
is mp3
# Version 0.17.3 (2023-10-23)
- Build fix for `minimp3` backend.
# Version 0.17.2 (2023-10-17)
- Add `EmptyCallback` source.
- Fix index out of bounds bug.
- Use non-vulnerable `minimp3` fork.
- Add filter functions with additional q parameter.
# Version 0.17.1 (2023-02-25)
- Disable `symphonia`'s default features.
# Version 0.17.0 (2023-02-17)
- Update `cpal` to [0.15](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0150-2022-01-29).
- Default to `symphonia` for mp3 decoding.
# Version 0.16.0 (2022-09-14)
- Update `cpal` to [0.14](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0140-2022-08-22).
- Update `symphonia` to [0.5](https://github.com/pdeljanov/Symphonia/releases/tag/v0.5.1).
# Version 0.15.0 (2022-01-23)
- Remove requirement that the argument `Decoder::new` and `LoopedDecoder::new` implement `Send`.
- Add optional symphonia backend.
- `WavDecoder`'s `total_duration` now returns the total duration of the sound rather than the remaining duration.
- Add 32-bit signed in WAV decoding.
- `SineWave::new()` now takes a `f32` instead of a `u32`.
- Add `len()` method to `SpatialSink`.
# Version 0.14.0 (2021-05-21)
- Re-export `cpal` in full.
- Replace panics when calling `OutputStream::try_default`, `OutputStream::try_from_device` with new
`StreamError` variants.
- `OutputStream::try_default` will now fallback to non-default output devices if an `OutputStream`
cannot be created from the default device.
# Version 0.13.1 (2021-03-28)
- Fix panic when no `pulseaudio-alsa` was installed.
# Version 0.13.0 (2020-11-03)
- Update `cpal` to [0.13](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0130-2020-10-28).
- Add Android support.
# Version 0.12.0 (2020-10-05)
- Breaking: Update `cpal` to [0.12](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0120-2020-07-09).
- Breaking: Rework API removing global "rodio audio processing" thread & adapting to the upstream cpal API changes.
- Add new_X format specific methods to Decoder.
- Fix resampler dependency on internal `Vec::capacity` behaviour.
# Version 0.11.0 (2020-03-16)
- Update `lewton` to [0.10](https://github.com/RustAudio/lewton/blob/master/CHANGELOG.md#release-0100---january-30-2020).
- Breaking: Update `cpal` to [0.11](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0110-2019-12-11)
# Version 0.10.0 (2019-11-16)
- Removal of nalgebra in favour of own code.
- Fix a bug that switched channels when resuming after having paused.
- Attempt all supported output formats if the default format fails in `Sink::new`.
- Breaking: Update `cpal` to [0.10](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md#version-0100-2019-07-05).
# Version 0.9.0 (2019-06-08)
- Remove exclusive `&mut` borrow requirements in `Sink` & `SpatialSink` setters.
- Use `nalgebra` instead of `cgmath` for `Spatial` source.
# Version 0.8.1 (2018-09-18)
- Update `lewton` dependency to [0.9](https://github.com/RustAudio/lewton/blob/master/CHANGELOG.md#release-090---august-16-2018)
- Change license from `Apache-2.0` only to `Apache-2.0 OR MIT`
# Version 0.8.0 (2018-06-22)
- Add mp3 decoding capabilities via `minimp3`
# Version 0.7.0 (2018-04-19)
- Update `cpal` dependency to 0.8, and adopt the new naming convention
- BREAKING CHANGES:
- renamed `Endpoint` to `Device`
- split `default_endpoint()` into `default_output_device()` and `default_input_device()`
- renamed `endpoints()` to `devices()`
- introduced `output_devices()` and `input_devices()`
-109
View File
@@ -1,109 +0,0 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "rodio"
version = "0.19.0"
exclude = [
"assets/**",
"tests/**",
]
description = "Audio playback library"
documentation = "https://docs.rs/rodio"
readme = "README.md"
keywords = [
"audio",
"playback",
"gamedev",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/RustAudio/rodio"
[[example]]
name = "music_m4a"
required-features = [
"symphonia-isomp4",
"symphonia-aac",
]
[dependencies.claxon]
version = "0.4.2"
optional = true
[dependencies.cpal]
version = "0.15.3"
[dependencies.crossbeam-channel]
version = "0.5.8"
optional = true
[dependencies.hound]
version = "3.3.1"
optional = true
[dependencies.lewton]
version = "0.10"
optional = true
[dependencies.minimp3_fixed]
version = "0.5.4"
optional = true
[dependencies.symphonia]
version = "0.5.4"
optional = true
default-features = false
[dependencies.thiserror]
version = "1.0.49"
[dev-dependencies.quickcheck]
version = "0.9.2"
[dev-dependencies.rstest]
version = "0.18.2"
[dev-dependencies.rstest_reuse]
version = "0.6.0"
[features]
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
default = [
"flac",
"vorbis",
"wav",
"mp3",
]
flac = ["claxon"]
minimp3 = ["dep:minimp3_fixed"]
mp3 = ["symphonia-mp3"]
symphonia-aac = ["symphonia/aac"]
symphonia-all = [
"symphonia-aac",
"symphonia-flac",
"symphonia-isomp4",
"symphonia-mp3",
"symphonia-vorbis",
"symphonia-wav",
]
symphonia-flac = ["symphonia/flac"]
symphonia-isomp4 = ["symphonia/isomp4"]
symphonia-mp3 = ["symphonia/mp3"]
symphonia-vorbis = ["symphonia/vorbis"]
symphonia-wav = [
"symphonia/wav",
"symphonia/pcm",
"symphonia/adpcm",
]
vorbis = ["lewton"]
wasm-bindgen = ["cpal/wasm-bindgen"]
wav = ["hound"]
-47
View File
@@ -1,47 +0,0 @@
[package]
name = "rodio"
version = "0.19.0"
license = "MIT OR Apache-2.0"
description = "Audio playback library"
keywords = ["audio", "playback", "gamedev"]
repository = "https://github.com/RustAudio/rodio"
documentation = "https://docs.rs/rodio"
exclude = ["assets/**", "tests/**"]
edition = "2021"
[dependencies]
cpal = "0.15.3"
claxon = { version = "0.4.2", optional = true }
hound = { version = "3.3.1", optional = true }
lewton = { version = "0.10", optional = true }
minimp3_fixed = { version = "0.5.4", optional = true}
symphonia = { version = "0.5.4", optional = true, default-features = false }
crossbeam-channel = { version = "0.5.8", optional = true }
thiserror = "1.0.49"
[features]
default = ["flac", "vorbis", "wav", "mp3"]
flac = ["claxon"]
vorbis = ["lewton"]
wav = ["hound"]
mp3 = ["symphonia-mp3"]
minimp3 = ["dep:minimp3_fixed"]
wasm-bindgen = ["cpal/wasm-bindgen"]
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
symphonia-aac = ["symphonia/aac"]
symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"]
symphonia-flac = ["symphonia/flac"]
symphonia-isomp4 = ["symphonia/isomp4"]
symphonia-mp3 = ["symphonia/mp3"]
symphonia-vorbis = ["symphonia/vorbis"]
symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"]
[dev-dependencies]
quickcheck = "0.9.2"
rstest = "0.18.2"
rstest_reuse = "0.6.0"
[[example]]
name = "music_m4a"
required-features = ["symphonia-isomp4", "symphonia-aac"]
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Pierre Krieger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-23
View File
@@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
-35
View File
@@ -1,35 +0,0 @@
# Audio playback library
[![Crates.io Version](https://img.shields.io/crates/v/rodio.svg)](https://crates.io/crates/rodio)
[![Crates.io Downloads](https://img.shields.io/crates/d/rodio.svg)](https://crates.io/crates/rodio)
[![Build Status](https://github.com/RustAudio/rodio/workflows/CI/badge.svg)](https://github.com/RustAudio/rodio/actions)
Rust playback library.
Playback is handled by [cpal](https://github.com/RustAudio/cpal). Format decoding can be handled either by [Symphonia](https://github.com/pdeljanov/Symphonia), or by format-specific decoders:
- MP3 by [minimp3](https://github.com/lieff/minimp3) (but defaults to [Symphonia](https://github.com/pdeljanov/Symphonia)).
- WAV by [hound](https://github.com/ruud-v-a/hound).
- Vorbis by [lewton](https://github.com/est31/lewton).
- FLAC by [claxon](https://github.com/ruuda/claxon).
- MP4 and AAC (both disabled by default) are handled only by [Symphonia](https://github.com/pdeljanov/Symphonia).
See [the docs](https://docs.rs/rodio/latest/rodio/#alternative-decoder-backends) for more details on backends.
# [Documentation](http://docs.rs/rodio)
[The documentation](http://docs.rs/rodio) contains an introduction to the library.
## License
[License]: #license
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0), or
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### License of your contributions
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
-36
View File
@@ -1,36 +0,0 @@
use std::io::BufReader;
use std::thread;
use std::time::Duration;
fn main() {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let file = std::fs::File::open("assets/beep.wav").unwrap();
let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap();
beep1.set_volume(0.2);
println!("Started beep1");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("assets/beep2.wav").unwrap();
let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap();
beep2.set_volume(0.3);
beep2.detach();
println!("Started beep2");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("assets/beep3.ogg").unwrap();
let beep3 = stream_handle.play_once(file).unwrap();
beep3.set_volume(0.2);
println!("Started beep3");
thread::sleep(Duration::from_millis(1500));
drop(beep1);
println!("Stopped beep1");
thread::sleep(Duration::from_millis(1500));
drop(beep3);
println!("Stopped beep3");
thread::sleep(Duration::from_millis(1500));
}
-38
View File
@@ -1,38 +0,0 @@
use rodio::source::{SineWave, Source};
use rodio::{dynamic_mixer, OutputStream, Sink};
use std::time::Duration;
fn main() {
// Construct a dynamic controller and mixer, stream_handle, and sink.
let (controller, mixer) = dynamic_mixer::mixer::<f32>(2, 44_100);
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
// Create four unique sources. The frequencies used here correspond
// notes in the key of C and in octave 4: C4, or middle C on a piano,
// E4, G4, and A4 respectively.
let source_c = SineWave::new(261.63)
.take_duration(Duration::from_secs_f32(1.))
.amplify(0.20);
let source_e = SineWave::new(329.63)
.take_duration(Duration::from_secs_f32(1.))
.amplify(0.20);
let source_g = SineWave::new(392.0)
.take_duration(Duration::from_secs_f32(1.))
.amplify(0.20);
let source_a = SineWave::new(440.0)
.take_duration(Duration::from_secs_f32(1.))
.amplify(0.20);
// Add sources C, E, G, and A to the mixer controller.
controller.add(source_c);
controller.add(source_e);
controller.add(source_g);
controller.add(source_a);
// Append the dynamic mixer to the sink to play a C major 6th chord.
sink.append(mixer);
// Sleep the thread until sink is empty.
sink.sleep_until_end();
}
-11
View File
@@ -1,11 +0,0 @@
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.flac").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-11
View File
@@ -1,11 +0,0 @@
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.m4a").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-11
View File
@@ -1,11 +0,0 @@
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-11
View File
@@ -1,11 +0,0 @@
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-11
View File
@@ -1,11 +0,0 @@
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.wav").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-15
View File
@@ -1,15 +0,0 @@
use rodio::Source;
use std::io::BufReader;
use std::time::Duration;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7);
sink.append(with_reverb);
sink.sleep_until_end();
}
-22
View File
@@ -1,22 +0,0 @@
use std::io::BufReader;
use std::time::Duration;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(0)).unwrap();
std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(4)).unwrap();
sink.sleep_until_end();
// wont do anything since the sound has ended already
sink.try_seek(Duration::from_secs(5)).unwrap();
println!("seek example ended");
}
-53
View File
@@ -1,53 +0,0 @@
use std::io::BufReader;
use std::thread;
use std::time::Duration;
use rodio::Source;
fn main() {
let iter_duration = Duration::from_secs(5);
let iter_distance = 5.;
let refresh_duration = Duration::from_millis(10);
let num_steps = iter_duration.as_secs_f32() / refresh_duration.as_secs_f32();
let step_distance = iter_distance / num_steps;
let num_steps = num_steps as u32;
let repeats = 5;
let total_duration = iter_duration * 2 * repeats;
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]);
let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap();
let file = std::fs::File::open("assets/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file))
.unwrap()
.repeat_infinite()
.take_duration(total_duration);
sink.append(source);
// A sound emitter playing the music starting at the centre gradually moves to the right
// until it stops and begins traveling to the left, it will eventually pass through the
// listener again and go to the far left.
// This is repeated 5 times.
for _ in 0..repeats {
for _ in 0..num_steps {
thread::sleep(refresh_duration);
positions.0[0] += step_distance;
sink.set_emitter_position(positions.0);
}
for _ in 0..(num_steps * 2) {
thread::sleep(refresh_duration);
positions.0[0] -= step_distance;
sink.set_emitter_position(positions.0);
}
for _ in 0..num_steps {
thread::sleep(refresh_duration);
positions.0[0] += step_distance;
sink.set_emitter_position(positions.0);
}
}
sink.sleep_until_end();
}
-12
View File
@@ -1,12 +0,0 @@
//! Plays a tone alternating between right and left ears, with right being first.
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/RL.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
-200
View File
@@ -1,200 +0,0 @@
//! A simple source of samples coming from a buffer.
//!
//! The `SamplesBuffer` struct can be used to treat a list of values as a `Source`.
//!
//! # Example
//!
//! ```
//! use rodio::buffer::SamplesBuffer;
//! let _ = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]);
//! ```
//!
use std::time::Duration;
use crate::source::SeekError;
use crate::{Sample, Source};
/// A buffer of samples treated as a source.
pub struct SamplesBuffer<S> {
data: Vec<S>,
pos: usize,
channels: u16,
sample_rate: u32,
duration: Duration,
}
impl<S> SamplesBuffer<S>
where
S: Sample,
{
/// Builds a new `SamplesBuffer`.
///
/// # Panic
///
/// - Panics if the number of channels is zero.
/// - Panics if the samples rate is zero.
/// - Panics if the length of the buffer is larger than approximately 16 billion elements.
/// This is because the calculation of the duration would overflow.
///
pub fn new<D>(channels: u16, sample_rate: u32, data: D) -> SamplesBuffer<S>
where
D: Into<Vec<S>>,
{
assert!(channels != 0);
assert!(sample_rate != 0);
let data = data.into();
let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap()
/ sample_rate as u64
/ channels as u64;
let duration = Duration::new(
duration_ns / 1_000_000_000,
(duration_ns % 1_000_000_000) as u32,
);
SamplesBuffer {
data,
pos: 0,
channels,
sample_rate,
duration,
}
}
}
impl<S> Source for SamplesBuffer<S>
where
S: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
self.channels
}
#[inline]
fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
Some(self.duration)
}
// this is fast because all the samples are in memory already
// and due to the constant sample_rate we can jump to the right
// sample directly
//
/// This jumps in memory till the sample for `pos`.
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
let curr_channel = self.pos % self.channels() as usize;
let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32;
// saturate pos at the end of the source
let new_pos = new_pos as usize;
let new_pos = new_pos.min(self.data.len());
// make sure the next sample is for the right channel
let new_pos = new_pos.next_multiple_of(self.channels() as usize);
let new_pos = new_pos - curr_channel;
self.pos = new_pos;
Ok(())
}
}
impl<S> Iterator for SamplesBuffer<S>
where
S: Sample,
{
type Item = S;
#[inline]
fn next(&mut self) -> Option<S> {
let sample = self.data.get(self.pos)?;
self.pos += 1;
Some(*sample)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.data.len(), Some(self.data.len()))
}
}
#[cfg(test)]
mod tests {
use crate::buffer::SamplesBuffer;
use crate::source::Source;
#[test]
fn basic() {
let _ = SamplesBuffer::new(1, 44100, vec![0i16, 0, 0, 0, 0, 0]);
}
#[test]
#[should_panic]
fn panic_if_zero_channels() {
SamplesBuffer::new(0, 44100, vec![0i16, 0, 0, 0, 0, 0]);
}
#[test]
#[should_panic]
fn panic_if_zero_sample_rate() {
SamplesBuffer::new(1, 0, vec![0i16, 0, 0, 0, 0, 0]);
}
#[test]
fn duration_basic() {
let buf = SamplesBuffer::new(2, 2, vec![0i16, 0, 0, 0, 0, 0]);
let dur = buf.total_duration().unwrap();
assert_eq!(dur.as_secs(), 1);
assert_eq!(dur.subsec_nanos(), 500_000_000);
}
#[test]
fn iteration() {
let mut buf = SamplesBuffer::new(1, 44100, vec![1i16, 2, 3, 4, 5, 6]);
assert_eq!(buf.next(), Some(1));
assert_eq!(buf.next(), Some(2));
assert_eq!(buf.next(), Some(3));
assert_eq!(buf.next(), Some(4));
assert_eq!(buf.next(), Some(5));
assert_eq!(buf.next(), Some(6));
assert_eq!(buf.next(), None);
}
#[cfg(test)]
mod try_seek {
use super::*;
use std::time::Duration;
#[test]
fn channel_order_stays_correct() {
const SAMPLE_RATE: u32 = 100;
const CHANNELS: u16 = 2;
let mut buf = SamplesBuffer::new(
CHANNELS,
SAMPLE_RATE,
(0..2000i16).into_iter().collect::<Vec<_>>(),
);
buf.try_seek(Duration::from_secs(5)).unwrap();
assert_eq!(
buf.next(),
Some(5i16 * SAMPLE_RATE as i16 * CHANNELS as i16)
);
assert!(buf.next().is_some_and(|s| s % 2 == 1));
assert!(buf.next().is_some_and(|s| s % 2 == 0));
buf.try_seek(Duration::from_secs(6)).unwrap();
assert!(buf.next().is_some_and(|s| s % 2 == 1),);
}
}
}
-181
View File
@@ -1,181 +0,0 @@
use cpal::Sample;
/// Iterator that converts from a certain channel count to another.
#[derive(Clone, Debug)]
pub struct ChannelCountConverter<I>
where
I: Iterator,
{
input: I,
from: cpal::ChannelCount,
to: cpal::ChannelCount,
sample_repeat: Option<I::Item>,
next_output_sample_pos: cpal::ChannelCount,
}
impl<I> ChannelCountConverter<I>
where
I: Iterator,
{
/// Initializes the iterator.
///
/// # Panic
///
/// Panics if `from` or `to` are equal to 0.
///
#[inline]
pub fn new(
input: I,
from: cpal::ChannelCount,
to: cpal::ChannelCount,
) -> ChannelCountConverter<I> {
assert!(from >= 1);
assert!(to >= 1);
ChannelCountConverter {
input,
from,
to,
sample_repeat: None,
next_output_sample_pos: 0,
}
}
/// Destroys this iterator and returns the underlying iterator.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
/// Get mutable access to the iterator
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
}
impl<I> Iterator for ChannelCountConverter<I>
where
I: Iterator,
I::Item: Sample,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
let result = match self.next_output_sample_pos {
0 => {
// save first sample for mono -> stereo conversion
let value = self.input.next();
self.sample_repeat = value;
value
}
x if x < self.from => self.input.next(),
1 => self.sample_repeat,
_ => Some(I::Item::EQUILIBRIUM),
};
if result.is_some() {
self.next_output_sample_pos += 1;
}
if self.next_output_sample_pos == self.to {
self.next_output_sample_pos = 0;
if self.from > self.to {
for _ in self.to..self.from {
self.input.next(); // discarding extra input
}
}
}
result
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let (min, max) = self.input.size_hint();
let consumed = std::cmp::min(self.from, self.next_output_sample_pos) as usize;
let calculate = |size| {
(size + consumed) / self.from as usize * self.to as usize
- self.next_output_sample_pos as usize
};
let min = calculate(min);
let max = max.map(calculate);
(min, max)
}
}
impl<I> ExactSizeIterator for ChannelCountConverter<I>
where
I: ExactSizeIterator,
I::Item: Sample,
{
}
#[cfg(test)]
mod test {
use super::ChannelCountConverter;
#[test]
fn remove_channels() {
let input = vec![1u16, 2, 3, 4, 5, 6];
let output = ChannelCountConverter::new(input.into_iter(), 3, 2).collect::<Vec<_>>();
assert_eq!(output, [1, 2, 4, 5]);
let input = vec![1u16, 2, 3, 4, 5, 6, 7, 8];
let output = ChannelCountConverter::new(input.into_iter(), 4, 1).collect::<Vec<_>>();
assert_eq!(output, [1, 5]);
}
#[test]
fn add_channels() {
let input = vec![1i16, 2, 3, 4];
let output = ChannelCountConverter::new(input.into_iter(), 1, 2).collect::<Vec<_>>();
assert_eq!(output, [1, 1, 2, 2, 3, 3, 4, 4]);
let input = vec![1i16, 2];
let output = ChannelCountConverter::new(input.into_iter(), 1, 4).collect::<Vec<_>>();
assert_eq!(output, [1, 1, 0, 0, 2, 2, 0, 0]);
let input = vec![1i16, 2, 3, 4];
let output = ChannelCountConverter::new(input.into_iter(), 2, 4).collect::<Vec<_>>();
assert_eq!(output, [1, 2, 0, 0, 3, 4, 0, 0]);
}
#[test]
fn size_hint() {
fn test(input: &[i16], from: cpal::ChannelCount, to: cpal::ChannelCount) {
let mut converter = ChannelCountConverter::new(input.iter().copied(), from, to);
let count = converter.clone().count();
for left_in_iter in (0..=count).rev() {
println!("left_in_iter = {}", left_in_iter);
assert_eq!(converter.size_hint(), (left_in_iter, Some(left_in_iter)));
converter.next();
}
assert_eq!(converter.size_hint(), (0, Some(0)));
}
test(&[1i16, 2, 3], 1, 2);
test(&[1i16, 2, 3, 4], 2, 4);
test(&[1i16, 2, 3, 4], 4, 2);
test(&[1i16, 2, 3, 4, 5, 6], 3, 8);
test(&[1i16, 2, 3, 4, 5, 6, 7, 8], 4, 1);
}
#[test]
fn len_more() {
let input = vec![1i16, 2, 3, 4];
let output = ChannelCountConverter::new(input.into_iter(), 2, 3);
assert_eq!(output.len(), 6);
}
#[test]
fn len_less() {
let input = vec![1i16, 2, 3, 4];
let output = ChannelCountConverter::new(input.into_iter(), 2, 1);
assert_eq!(output.len(), 2);
}
}
-17
View File
@@ -1,17 +0,0 @@
/*!
This module contains function that will convert from one PCM format to another.
This includes conversion between sample formats, channels or sample rates.
*/
pub use self::channels::ChannelCountConverter;
pub use self::sample::DataConverter;
pub use self::sample::Sample;
pub use self::sample_rate::SampleRateConverter;
mod channels;
// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This
// `pub` mandatory
pub mod sample;
mod sample_rate;
-159
View File
@@ -1,159 +0,0 @@
use cpal::{FromSample, Sample as CpalSample};
use std::marker::PhantomData;
/// Converts the samples data type to `O`.
#[derive(Clone, Debug)]
pub struct DataConverter<I, O> {
input: I,
marker: PhantomData<O>,
}
impl<I, O> DataConverter<I, O> {
/// Builds a new converter.
#[inline]
pub fn new(input: I) -> DataConverter<I, O> {
DataConverter {
input,
marker: PhantomData,
}
}
/// Destroys this iterator and returns the underlying iterator.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
/// get mutable access to the iterator
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
}
impl<I, O> Iterator for DataConverter<I, O>
where
I: Iterator,
I::Item: Sample,
O: FromSample<I::Item> + Sample,
{
type Item = O;
#[inline]
fn next(&mut self) -> Option<O> {
self.input.next().map(|s| CpalSample::from_sample(s))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I, O> ExactSizeIterator for DataConverter<I, O>
where
I: ExactSizeIterator,
I::Item: Sample,
O: FromSample<I::Item> + Sample,
{
}
/// Represents a value of a single sample.
///
/// This trait is implemented by default on three types: `i16`, `u16` and `f32`.
///
/// - For `i16`, silence corresponds to the value `0`. The minimum and maximum amplitudes are
/// represented by `i16::min_value()` and `i16::max_value()` respectively.
/// - For `u16`, silence corresponds to the value `u16::max_value() / 2`. The minimum and maximum
/// amplitudes are represented by `0` and `u16::max_value()` respectively.
/// - For `f32`, silence corresponds to the value `0.0`. The minimum and maximum amplitudes are
/// represented by `-1.0` and `1.0` respectively.
///
/// You can implement this trait on your own type as well if you wish so.
///
pub trait Sample: CpalSample {
/// Linear interpolation between two samples.
///
/// The result should be equal to
/// `first * numerator / denominator + second * (1 - numerator / denominator)`.
fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self;
/// Multiplies the value of this sample by the given amount.
fn amplify(self, value: f32) -> Self;
/// Calls `saturating_add` on the sample.
fn saturating_add(self, other: Self) -> Self;
/// Returns the value corresponding to the absence of sound.
fn zero_value() -> Self;
}
impl Sample for u16 {
#[inline]
fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 {
let a = first as i32;
let b = second as i32;
let n = numerator as i32;
let d = denominator as i32;
(a + (b - a) * n / d) as u16
}
#[inline]
fn amplify(self, value: f32) -> u16 {
((self as f32) * value) as u16
}
#[inline]
fn saturating_add(self, other: u16) -> u16 {
self.saturating_add(other)
}
#[inline]
fn zero_value() -> u16 {
32768
}
}
impl Sample for i16 {
#[inline]
fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 {
(first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32)
as i16
}
#[inline]
fn amplify(self, value: f32) -> i16 {
((self as f32) * value) as i16
}
#[inline]
fn saturating_add(self, other: i16) -> i16 {
self.saturating_add(other)
}
#[inline]
fn zero_value() -> i16 {
0
}
}
impl Sample for f32 {
#[inline]
fn lerp(first: f32, second: f32, numerator: u32, denominator: u32) -> f32 {
first + (second - first) * numerator as f32 / denominator as f32
}
#[inline]
fn amplify(self, value: f32) -> f32 {
self * value
}
#[inline]
fn saturating_add(self, other: f32) -> f32 {
self + other
}
#[inline]
fn zero_value() -> f32 {
0.0
}
}
-377
View File
@@ -1,377 +0,0 @@
use crate::conversions::Sample;
use std::mem;
/// Iterator that converts from a certain sample rate to another.
#[derive(Clone, Debug)]
pub struct SampleRateConverter<I>
where
I: Iterator,
{
/// The iterator that gives us samples.
input: I,
/// We convert chunks of `from` samples into chunks of `to` samples.
from: u32,
/// We convert chunks of `from` samples into chunks of `to` samples.
to: u32,
/// Number of channels in the stream
channels: cpal::ChannelCount,
/// One sample per channel, extracted from `input`.
current_frame: Vec<I::Item>,
/// Position of `current_sample` modulo `from`.
current_frame_pos_in_chunk: u32,
/// The samples right after `current_sample` (one per channel), extracted from `input`.
next_frame: Vec<I::Item>,
/// The position of the next sample that the iterator should return, modulo `to`.
/// This counter is incremented (modulo `to`) every time the iterator is called.
next_output_frame_pos_in_chunk: u32,
/// The buffer containing the samples waiting to be output.
output_buffer: Vec<I::Item>,
}
impl<I> SampleRateConverter<I>
where
I: Iterator,
I::Item: Sample,
{
///
///
/// # Panic
///
/// Panics if `from` or `to` are equal to 0.
///
#[inline]
pub fn new(
mut input: I,
from: cpal::SampleRate,
to: cpal::SampleRate,
num_channels: cpal::ChannelCount,
) -> SampleRateConverter<I> {
let from = from.0;
let to = to.0;
assert!(from >= 1);
assert!(to >= 1);
// finding greatest common divisor
let gcd = {
#[inline]
fn gcd(a: u32, b: u32) -> u32 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
gcd(from, to)
};
let (first_samples, next_samples) = if from == to {
// if `from` == `to` == 1, then we just pass through
debug_assert_eq!(from, gcd);
(Vec::new(), Vec::new())
} else {
let first = input
.by_ref()
.take(num_channels as usize)
.collect::<Vec<_>>();
let next = input
.by_ref()
.take(num_channels as usize)
.collect::<Vec<_>>();
(first, next)
};
SampleRateConverter {
input,
from: from / gcd,
to: to / gcd,
channels: num_channels,
current_frame_pos_in_chunk: 0,
next_output_frame_pos_in_chunk: 0,
current_frame: first_samples,
next_frame: next_samples,
output_buffer: Vec::with_capacity(num_channels as usize - 1),
}
}
/// Destroys this iterator and returns the underlying iterator.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
/// get mutable access to the iterator
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
fn next_input_frame(&mut self) {
self.current_frame_pos_in_chunk += 1;
mem::swap(&mut self.current_frame, &mut self.next_frame);
self.next_frame.clear();
for _ in 0..self.channels {
if let Some(i) = self.input.next() {
self.next_frame.push(i);
} else {
break;
}
}
}
}
impl<I> Iterator for SampleRateConverter<I>
where
I: Iterator,
I::Item: Sample + Clone,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
// the algorithm below doesn't work if `self.from == self.to`
if self.from == self.to {
debug_assert_eq!(self.from, 1);
return self.input.next();
}
// Short circuit if there are some samples waiting.
if !self.output_buffer.is_empty() {
return Some(self.output_buffer.remove(0));
}
// The frame we are going to return from this function will be a linear interpolation
// between `self.current_frame` and `self.next_frame`.
if self.next_output_frame_pos_in_chunk == self.to {
// If we jump to the next frame, we reset the whole state.
self.next_output_frame_pos_in_chunk = 0;
self.next_input_frame();
while self.current_frame_pos_in_chunk != self.from {
self.next_input_frame();
}
self.current_frame_pos_in_chunk = 0;
} else {
// Finding the position of the first sample of the linear interpolation.
let req_left_sample =
(self.from * self.next_output_frame_pos_in_chunk / self.to) % self.from;
// Advancing `self.current_frame`, `self.next_frame` and
// `self.current_frame_pos_in_chunk` until the latter variable
// matches `req_left_sample`.
while self.current_frame_pos_in_chunk != req_left_sample {
self.next_input_frame();
debug_assert!(self.current_frame_pos_in_chunk < self.from);
}
}
// Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`.
// Note that `self.output_buffer` can be truncated if there is not enough data in
// `self.next_frame`.
let mut result = None;
let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to;
for (off, (cur, next)) in self
.current_frame
.iter()
.zip(self.next_frame.iter())
.enumerate()
{
let sample = Sample::lerp(*cur, *next, numerator, self.to);
if off == 0 {
result = Some(sample);
} else {
self.output_buffer.push(sample);
}
}
// Incrementing the counter for the next iteration.
self.next_output_frame_pos_in_chunk += 1;
if result.is_some() {
result
} else {
// draining `self.current_frame`
if !self.current_frame.is_empty() {
let r = Some(self.current_frame.remove(0));
mem::swap(&mut self.output_buffer, &mut self.current_frame);
self.current_frame.clear();
r
} else {
None
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let apply = |samples: usize| {
// `samples_after_chunk` will contain the number of samples remaining after the chunk
// currently being processed
let samples_after_chunk = samples;
// adding the samples of the next chunk that may have already been read
let samples_after_chunk = if self.current_frame_pos_in_chunk == self.from - 1 {
samples_after_chunk + self.next_frame.len()
} else {
samples_after_chunk
};
// removing the samples of the current chunk that have not yet been read
let samples_after_chunk = samples_after_chunk.saturating_sub(
self.from
.saturating_sub(self.current_frame_pos_in_chunk + 2) as usize
* usize::from(self.channels),
);
// calculating the number of samples after the transformation
// TODO: this is wrong here \|/
let samples_after_chunk = samples_after_chunk * self.to as usize / self.from as usize;
// `samples_current_chunk` will contain the number of samples remaining to be output
// for the chunk currently being processed
let samples_current_chunk = (self.to - self.next_output_frame_pos_in_chunk) as usize
* usize::from(self.channels);
samples_current_chunk + samples_after_chunk + self.output_buffer.len()
};
if self.from == self.to {
self.input.size_hint()
} else {
let (min, max) = self.input.size_hint();
(apply(min), max.map(apply))
}
}
}
impl<I> ExactSizeIterator for SampleRateConverter<I>
where
I: ExactSizeIterator,
I::Item: Sample + Clone,
{
}
#[cfg(test)]
mod test {
use super::SampleRateConverter;
use core::time::Duration;
use cpal::SampleRate;
use quickcheck::quickcheck;
// TODO: Remove once cpal 0.12.2 is released and the dependency is updated
// (cpal#483 implemented ops::Mul on SampleRate)
const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate {
SampleRate(k * r.0)
}
quickcheck! {
/// Check that resampling an empty input produces no output.
fn empty(from: u32, to: u32, n: u16) -> () {
let from = if from == 0 { return; } else { SampleRate(from) };
let to = if to == 0 { return; } else { SampleRate(to) };
if n == 0 { return; }
let input: Vec<u16> = Vec::new();
let output =
SampleRateConverter::new(input.into_iter(), from, to, n)
.collect::<Vec<_>>();
assert_eq!(output, []);
}
/// Check that resampling to the same rate does not change the signal.
fn identity(from: u32, n: u16, input: Vec<u16>) -> () {
let from = if from == 0 { return; } else { SampleRate(from) };
if n == 0 { return; }
let output =
SampleRateConverter::new(input.clone().into_iter(), from, from, n)
.collect::<Vec<_>>();
assert_eq!(input, output);
}
/// Check that dividing the sample rate by k (integer) is the same as
/// dropping a sample from each channel.
fn divide_sample_rate(to: u32, k: u32, input: Vec<u16>, n: u16) -> () {
let to = if to == 0 { return; } else { SampleRate(to) };
let from = multiply_rate(to, k);
if k == 0 || n == 0 { return; }
// Truncate the input, so it contains an integer number of frames.
let input = {
let ns = n as usize;
let mut i = input;
i.truncate(ns * (i.len() / ns));
i
};
let output =
SampleRateConverter::new(input.clone().into_iter(), from, to, n)
.collect::<Vec<_>>();
assert_eq!(input.chunks_exact(n.into())
.step_by(k as usize).collect::<Vec<_>>().concat(),
output)
}
/// Check that, after multiplying the sample rate by k, every k-th
/// sample in the output matches exactly with the input.
fn multiply_sample_rate(from: u32, k: u32, input: Vec<u16>, n: u16) -> () {
let from = if from == 0 { return; } else { SampleRate(from) };
let to = multiply_rate(from, k);
if k == 0 || n == 0 { return; }
// Truncate the input, so it contains an integer number of frames.
let input = {
let ns = n as usize;
let mut i = input;
i.truncate(ns * (i.len() / ns));
i
};
let output =
SampleRateConverter::new(input.clone().into_iter(), from, to, n)
.collect::<Vec<_>>();
assert_eq!(input,
output.chunks_exact(n.into())
.step_by(k as usize).collect::<Vec<_>>().concat()
)
}
#[ignore]
/// Check that resampling does not change the audio duration,
/// except by a negligible amount (± 1ms). Reproduces #316.
/// Ignored, pending a bug fix.
fn preserve_durations(d: Duration, freq: f32, to: u32) -> () {
use crate::source::{SineWave, Source};
let to = if to == 0 { return; } else { SampleRate(to) };
let source = SineWave::new(freq).take_duration(d);
let from = SampleRate(source.sample_rate());
let resampled =
SampleRateConverter::new(source, from, to, 1);
let duration =
Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32);
let delta = if d < duration { duration - d } else { d - duration };
assert!(delta < Duration::from_millis(1),
"Resampled duration ({:?}) is not close to original ({:?}); Δ = {:?}",
duration, d, delta);
}
}
#[test]
fn upsample() {
let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22];
let output =
SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2);
assert_eq!(output.len(), 12);
let output = output.collect::<Vec<_>>();
assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]);
}
}
-144
View File
@@ -1,144 +0,0 @@
use std::cmp::Ordering;
use std::io::{Read, Seek, SeekFrom};
use std::mem;
use std::time::Duration;
use crate::source::SeekError;
use crate::Source;
use claxon::FlacReader;
/// Decoder for the Flac format.
pub struct FlacDecoder<R>
where
R: Read + Seek,
{
reader: FlacReader<R>,
current_block: Vec<i32>,
current_block_channel_len: usize,
current_block_off: usize,
bits_per_sample: u32,
sample_rate: u32,
channels: u16,
samples: Option<u64>,
}
impl<R> FlacDecoder<R>
where
R: Read + Seek,
{
/// Attempts to decode the data as Flac.
pub fn new(mut data: R) -> Result<FlacDecoder<R>, R> {
if !is_flac(data.by_ref()) {
return Err(data);
}
let reader = FlacReader::new(data).unwrap();
let spec = reader.streaminfo();
Ok(FlacDecoder {
reader,
current_block: Vec::with_capacity(
spec.max_block_size as usize * spec.channels as usize,
),
current_block_channel_len: 1,
current_block_off: 0,
bits_per_sample: spec.bits_per_sample,
sample_rate: spec.sample_rate,
channels: spec.channels as u16,
samples: spec.samples,
})
}
pub fn into_inner(self) -> R {
self.reader.into_inner()
}
}
impl<R> Source for FlacDecoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
self.channels
}
#[inline]
fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
// `samples` in FLAC means "inter-channel samples" aka frames
// so we do not divide by `self.channels` here.
self.samples
.map(|s| Duration::from_micros(s * 1_000_000 / self.sample_rate as u64))
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
impl<R> Iterator for FlacDecoder<R>
where
R: Read + Seek,
{
type Item = i16;
#[inline]
fn next(&mut self) -> Option<i16> {
loop {
if self.current_block_off < self.current_block.len() {
// Read from current block.
let real_offset = (self.current_block_off % self.channels as usize)
* self.current_block_channel_len
+ self.current_block_off / self.channels as usize;
let raw_val = self.current_block[real_offset];
self.current_block_off += 1;
let real_val = match self.bits_per_sample.cmp(&16) {
Ordering::Less => (raw_val << (16 - self.bits_per_sample)) as i16,
Ordering::Equal => raw_val as i16,
Ordering::Greater => (raw_val >> (self.bits_per_sample - 16)) as i16,
};
return Some(real_val);
}
// Load the next block.
self.current_block_off = 0;
let buffer = mem::take(&mut self.current_block);
match self.reader.blocks().read_next_or_eof(buffer) {
Ok(Some(block)) => {
self.current_block_channel_len = (block.len() / block.channels()) as usize;
self.current_block = block.into_buffer();
}
_ => return None,
}
}
}
}
/// Returns true if the stream contains Flac data, then resets it to where it was.
fn is_flac<R>(mut data: R) -> bool
where
R: Read + Seek,
{
let stream_pos = data.stream_position().unwrap();
if FlacReader::new(data.by_ref()).is_err() {
data.seek(SeekFrom::Start(stream_pos)).unwrap();
return false;
}
data.seek(SeekFrom::Start(stream_pos)).unwrap();
true
}
-575
View File
@@ -1,575 +0,0 @@
//! Decodes samples from an audio file.
use std::error::Error;
use std::fmt;
#[allow(unused_imports)]
use std::io::{Read, Seek, SeekFrom};
use std::mem;
use std::str::FromStr;
use std::time::Duration;
use crate::source::SeekError;
use crate::Source;
#[cfg(feature = "symphonia")]
use self::read_seek_source::ReadSeekSource;
#[cfg(feature = "symphonia")]
use ::symphonia::core::io::{MediaSource, MediaSourceStream};
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
mod flac;
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
mod mp3;
#[cfg(feature = "symphonia")]
mod read_seek_source;
#[cfg(feature = "symphonia")]
pub mod symphonia;
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
mod vorbis;
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
mod wav;
/// Source of audio samples from decoding a file.
///
/// Supports MP3, WAV, Vorbis and Flac.
pub struct Decoder<R>(DecoderImpl<R>)
where
R: Read + Seek;
pub struct LoopedDecoder<R>(DecoderImpl<R>)
where
R: Read + Seek;
// can not 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<R>
where
R: Read + Seek,
{
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
Wav(wav::WavDecoder<R>),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
Vorbis(vorbis::VorbisDecoder<R>),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
Flac(flac::FlacDecoder<R>),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
Mp3(mp3::Mp3Decoder<R>),
#[cfg(feature = "symphonia")]
Symphonia(symphonia::SymphoniaDecoder),
None(::std::marker::PhantomData<R>),
}
impl<R: Read + Seek> DecoderImpl<R> {
#[inline]
fn next(&mut self) -> Option<f32> {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.next(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.next(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.next(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.next(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.next(),
DecoderImpl::None(_) => None,
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.size_hint(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.size_hint(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.size_hint(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.size_hint(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.size_hint(),
DecoderImpl::None(_) => (0, None),
}
}
#[inline]
fn current_frame_len(&self) -> Option<usize> {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.current_frame_len(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.current_frame_len(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.current_frame_len(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.current_frame_len(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.current_frame_len(),
DecoderImpl::None(_) => Some(0),
}
}
#[inline]
fn channels(&self) -> u16 {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.channels(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.channels(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.channels(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.channels(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.channels(),
DecoderImpl::None(_) => 0,
}
}
#[inline]
fn sample_rate(&self) -> u32 {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.sample_rate(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.sample_rate(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.sample_rate(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.sample_rate(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.sample_rate(),
DecoderImpl::None(_) => 1,
}
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.total_duration(),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.total_duration(),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.total_duration(),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.total_duration(),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.total_duration(),
DecoderImpl::None(_) => Some(Duration::default()),
}
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
match self {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => source.try_seek(pos),
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => source.try_seek(pos),
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => source.try_seek(pos),
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => source.try_seek(pos),
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => source.try_seek(pos),
DecoderImpl::None(_) => Err(SeekError::NotSupported {
underlying_source: "DecoderImpl::None",
}),
}
}
}
impl<R> Decoder<R>
where
R: Read + Seek + Send + Sync + 'static,
{
/// Builds a new decoder.
///
/// Attempts to automatically detect the format of the source of data.
#[allow(unused_variables)]
pub fn new(data: R) -> Result<Decoder<R>, DecoderError> {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
let data = match wav::WavDecoder::new(data) {
Err(data) => data,
Ok(decoder) => {
return Ok(Decoder(DecoderImpl::Wav(decoder)));
}
};
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
let data = match flac::FlacDecoder::new(data) {
Err(data) => data,
Ok(decoder) => {
return Ok(Decoder(DecoderImpl::Flac(decoder)));
}
};
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
let data = match vorbis::VorbisDecoder::new(data) {
Err(data) => data,
Ok(decoder) => {
return Ok(Decoder(DecoderImpl::Vorbis(decoder)));
}
};
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
let data = match mp3::Mp3Decoder::new(data) {
Err(data) => data,
Ok(decoder) => {
return Ok(Decoder(DecoderImpl::Mp3(decoder)));
}
};
#[cfg(feature = "symphonia")]
{
let mss = MediaSourceStream::new(
Box::new(ReadSeekSource::new(data)) as Box<dyn MediaSource>,
Default::default(),
);
match symphonia::SymphoniaDecoder::new(mss, None) {
Err(e) => Err(e),
Ok(decoder) => Ok(Decoder(DecoderImpl::Symphonia(decoder))),
}
}
#[cfg(not(feature = "symphonia"))]
Err(DecoderError::UnrecognizedFormat)
}
pub fn new_looped(data: R) -> Result<LoopedDecoder<R>, DecoderError> {
Self::new(data).map(LoopedDecoder::new)
}
/// Builds a new decoder from wav data.
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
pub fn new_wav(data: R) -> Result<Decoder<R>, DecoderError> {
match wav::WavDecoder::new(data) {
Err(_) => Err(DecoderError::UnrecognizedFormat),
Ok(decoder) => Ok(Decoder(DecoderImpl::Wav(decoder))),
}
}
/// Builds a new decoder from wav data.
#[cfg(feature = "symphonia-wav")]
pub fn new_wav(data: R) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, "wav")
}
/// Builds a new decoder from flac data.
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
pub fn new_flac(data: R) -> Result<Decoder<R>, DecoderError> {
match flac::FlacDecoder::new(data) {
Err(_) => Err(DecoderError::UnrecognizedFormat),
Ok(decoder) => Ok(Decoder(DecoderImpl::Flac(decoder))),
}
}
/// Builds a new decoder from flac data.
#[cfg(feature = "symphonia-flac")]
pub fn new_flac(data: R) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, "flac")
}
/// Builds a new decoder from vorbis data.
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
pub fn new_vorbis(data: R) -> Result<Decoder<R>, DecoderError> {
match vorbis::VorbisDecoder::new(data) {
Err(_) => Err(DecoderError::UnrecognizedFormat),
Ok(decoder) => Ok(Decoder(DecoderImpl::Vorbis(decoder))),
}
}
/// Builds a new decoder from vorbis data.
#[cfg(feature = "symphonia-vorbis")]
pub fn new_vorbis(data: R) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, "ogg")
}
/// Builds a new decoder from mp3 data.
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
pub fn new_mp3(data: R) -> Result<Decoder<R>, DecoderError> {
match mp3::Mp3Decoder::new(data) {
Err(_) => Err(DecoderError::UnrecognizedFormat),
Ok(decoder) => Ok(Decoder(DecoderImpl::Mp3(decoder))),
}
}
/// Builds a new decoder from mp3 data.
#[cfg(feature = "symphonia-mp3")]
pub fn new_mp3(data: R) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, "mp3")
}
/// Builds a new decoder from aac data.
#[cfg(feature = "symphonia-aac")]
pub fn new_aac(data: R) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, "aac")
}
/// Builds a new decoder from mp4 data.
#[cfg(feature = "symphonia-isomp4")]
pub fn new_mp4(data: R, hint: Mp4Type) -> Result<Decoder<R>, DecoderError> {
Decoder::new_symphonia(data, &hint.to_string())
}
#[cfg(feature = "symphonia")]
fn new_symphonia(data: R, hint: &str) -> Result<Decoder<R>, DecoderError> {
let mss = MediaSourceStream::new(
Box::new(ReadSeekSource::new(data)) as Box<dyn MediaSource>,
Default::default(),
);
match symphonia::SymphoniaDecoder::new(mss, Some(hint)) {
Err(e) => Err(e),
Ok(decoder) => Ok(Decoder(DecoderImpl::Symphonia(decoder))),
}
}
}
#[derive(Debug)]
pub enum Mp4Type {
Mp4,
M4a,
M4p,
M4b,
M4r,
M4v,
Mov,
}
impl FromStr for Mp4Type {
type Err = String;
fn from_str(input: &str) -> Result<Mp4Type, Self::Err> {
match &input.to_lowercase()[..] {
"mp4" => Ok(Mp4Type::Mp4),
"m4a" => Ok(Mp4Type::M4a),
"m4p" => Ok(Mp4Type::M4p),
"m4b" => Ok(Mp4Type::M4b),
"m4r" => Ok(Mp4Type::M4r),
"m4v" => Ok(Mp4Type::M4v),
"mov" => Ok(Mp4Type::Mov),
_ => Err(format!("{} is not a valid mp4 extension", input)),
}
}
}
impl fmt::Display for Mp4Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let text = match self {
Mp4Type::Mp4 => "mp4",
Mp4Type::M4a => "m4a",
Mp4Type::M4p => "m4p",
Mp4Type::M4b => "m4b",
Mp4Type::M4r => "m4r",
Mp4Type::M4v => "m4v",
Mp4Type::Mov => "mov",
};
write!(f, "{}", text)
}
}
impl<R> LoopedDecoder<R>
where
R: Read + Seek,
{
fn new(decoder: Decoder<R>) -> LoopedDecoder<R> {
Self(decoder.0)
}
}
impl<R> Iterator for Decoder<R>
where
R: Read + Seek,
{
type Item = f32;
#[inline]
fn next(&mut self) -> Option<f32> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<R> Source for Decoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.0.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.0.channels()
}
fn sample_rate(&self) -> u32 {
self.0.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.0.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.0.try_seek(pos)
}
}
impl<R> Iterator for LoopedDecoder<R>
where
R: Read + Seek,
{
type Item = f32;
#[inline]
fn next(&mut self) -> Option<f32> {
if let Some(sample) = self.0.next() {
Some(sample)
} else {
let decoder = mem::replace(&mut self.0, DecoderImpl::None(Default::default()));
let (decoder, sample) = match decoder {
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
DecoderImpl::Wav(source) => {
let mut reader = source.into_inner();
reader.seek(SeekFrom::Start(0)).ok()?;
let mut source = wav::WavDecoder::new(reader).ok()?;
let sample = source.next();
(DecoderImpl::Wav(source), sample)
}
#[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))]
DecoderImpl::Vorbis(source) => {
use lewton::inside_ogg::OggStreamReader;
let mut reader = source.into_inner().into_inner();
reader.seek_bytes(SeekFrom::Start(0)).ok()?;
let mut source = vorbis::VorbisDecoder::from_stream_reader(
OggStreamReader::from_ogg_reader(reader).ok()?,
);
let sample = source.next();
(DecoderImpl::Vorbis(source), sample)
}
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
DecoderImpl::Flac(source) => {
let mut reader = source.into_inner();
reader.seek(SeekFrom::Start(0)).ok()?;
let mut source = flac::FlacDecoder::new(reader).ok()?;
let sample = source.next();
(DecoderImpl::Flac(source), sample)
}
#[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))]
DecoderImpl::Mp3(source) => {
let mut reader = source.into_inner();
reader.seek(SeekFrom::Start(0)).ok()?;
let mut source = mp3::Mp3Decoder::new(reader).ok()?;
let sample = source.next();
(DecoderImpl::Mp3(source), sample)
}
#[cfg(feature = "symphonia")]
DecoderImpl::Symphonia(source) => {
let mut reader = source.into_inner();
reader.seek(SeekFrom::Start(0)).ok()?;
let mut source = symphonia::SymphoniaDecoder::new(reader, None).ok()?;
let sample = source.next();
(DecoderImpl::Symphonia(source), sample)
}
none @ DecoderImpl::None(_) => (none, None),
};
self.0 = decoder;
sample
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<R> Source for LoopedDecoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.0.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.0.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.0.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
None
}
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.0.try_seek(pos)
}
}
/// Error that can happen when creating a decoder.
#[derive(Debug, Clone)]
pub enum DecoderError {
/// The format of the data has not been recognized.
UnrecognizedFormat,
/// An IO error occurred while reading, writing, or seeking the stream.
#[cfg(feature = "symphonia")]
IoError(String),
/// The stream contained malformed data and could not be decoded or demuxed.
#[cfg(feature = "symphonia")]
DecodeError(&'static str),
/// A default or user-defined limit was reached while decoding or demuxing the stream. Limits
/// are used to prevent denial-of-service attacks from malicious streams.
#[cfg(feature = "symphonia")]
LimitError(&'static str),
/// The demuxer or decoder needs to be reset before continuing.
#[cfg(feature = "symphonia")]
ResetRequired,
/// No streams were found by the decoder
#[cfg(feature = "symphonia")]
NoStreams,
}
impl fmt::Display for DecoderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
DecoderError::UnrecognizedFormat => "Unrecognized format",
#[cfg(feature = "symphonia")]
DecoderError::IoError(msg) => &msg[..],
#[cfg(feature = "symphonia")]
DecoderError::DecodeError(msg) => msg,
#[cfg(feature = "symphonia")]
DecoderError::LimitError(msg) => msg,
#[cfg(feature = "symphonia")]
DecoderError::ResetRequired => "Reset required",
#[cfg(feature = "symphonia")]
DecoderError::NoStreams => "No streams",
};
write!(f, "{}", text)
}
}
impl Error for DecoderError {}
-125
View File
@@ -1,125 +0,0 @@
use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use crate::source::SeekError;
use crate::Source;
use minimp3::Decoder;
use minimp3::Frame;
use minimp3_fixed as minimp3;
pub struct Mp3Decoder<R>
where
R: Read + Seek,
{
// decoder: SeekDecoder<R>,
decoder: Decoder<R>,
current_frame: Frame,
current_frame_offset: usize,
}
impl<R> Mp3Decoder<R>
where
R: Read + Seek,
{
pub fn new(mut data: R) -> Result<Self, R> {
if !is_mp3(data.by_ref()) {
return Err(data);
}
// let mut decoder = SeekDecoder::new(data)
let mut decoder = Decoder::new(data);
// parameters are correct and minimp3 is used correctly
// thus if we crash here one of these invariants is broken:
// .expect("should be able to allocate memory, perform IO");
// let current_frame = decoder.decode_frame()
let current_frame = decoder.next_frame()
// the reader makes enough data available therefore
// if we crash here the invariant broken is:
.expect("data should not corrupt");
Ok(Mp3Decoder {
decoder,
current_frame,
current_frame_offset: 0,
})
}
pub fn into_inner(self) -> R {
self.decoder.into_inner()
}
}
impl<R> Source for Mp3Decoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
Some(self.current_frame.data.len())
}
#[inline]
fn channels(&self) -> u16 {
self.current_frame.channels as _
}
#[inline]
fn sample_rate(&self) -> u32 {
self.current_frame.sample_rate as _
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
None
}
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
// TODO waiting for PR in minimp3_fixed or minimp3
// let pos = (pos.as_secs_f32() * self.sample_rate() as f32) as u64;
// // do not trigger a sample_rate, channels and frame len update
// // as the seek only takes effect after the current frame is done
// self.decoder.seek_samples(pos)?;
// Ok(())
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
impl<R> Iterator for Mp3Decoder<R>
where
R: Read + Seek,
{
type Item = i16;
fn next(&mut self) -> Option<i16> {
if self.current_frame_offset == self.current_frame_len().unwrap() {
if let Ok(frame) = self.decoder.next_frame() {
// if let Ok(frame) = self.decoder.decode_frame() {
self.current_frame = frame;
self.current_frame_offset = 0;
} else {
return None;
}
}
let v = self.current_frame.data[self.current_frame_offset];
self.current_frame_offset += 1;
Some(v)
}
}
/// Returns true if the stream contains mp3 data, then resets it to where it was.
fn is_mp3<R>(mut data: R) -> bool
where
R: Read + Seek,
{
let stream_pos = data.seek(SeekFrom::Current(0)).unwrap();
let mut decoder = Decoder::new(data.by_ref());
let ok = decoder.next_frame().is_ok();
data.seek(SeekFrom::Start(stream_pos)).unwrap();
ok
}
-37
View File
@@ -1,37 +0,0 @@
use std::io::{Read, Result, Seek, SeekFrom};
use symphonia::core::io::MediaSource;
pub struct ReadSeekSource<T: Read + Seek + Send + Sync> {
inner: T,
}
impl<T: Read + Seek + Send + Sync> ReadSeekSource<T> {
/// Instantiates a new `ReadSeekSource<T>` by taking ownership and wrapping the provided
/// `Read + Seek`er.
pub fn new(inner: T) -> Self {
ReadSeekSource { inner }
}
}
impl<T: Read + Seek + Send + Sync> MediaSource for ReadSeekSource<T> {
fn is_seekable(&self) -> bool {
true
}
fn byte_len(&self) -> Option<u64> {
None
}
}
impl<T: Read + Seek + Send + Sync> Read for ReadSeekSource<T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.inner.read(buf)
}
}
impl<T: Read + Seek + Send + Sync> Seek for ReadSeekSource<T> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.inner.seek(pos)
}
}
-291
View File
@@ -1,291 +0,0 @@
use std::time::Duration;
use symphonia::{
core::{
audio::{AudioBufferRef, SampleBuffer, SignalSpec},
codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL},
errors::Error,
formats::{FormatOptions, FormatReader, SeekedTo},
io::MediaSourceStream,
meta::MetadataOptions,
probe::Hint,
units::{self, Time},
},
default::get_probe,
};
use crate::{source, Source};
use super::DecoderError;
// Decoder errors are not considered fatal.
// The correct action is to just get a new packet and try again.
// But a decode error in more than 3 consecutive packets is fatal.
const MAX_DECODE_RETRIES: usize = 3;
pub(crate) struct SymphoniaDecoder {
decoder: Box<dyn Decoder>,
current_frame_offset: usize,
format: Box<dyn FormatReader>,
total_duration: Option<Time>,
buffer: SampleBuffer<f32>,
spec: SignalSpec,
}
impl SymphoniaDecoder {
pub(crate) fn new(
mss: MediaSourceStream,
extension: Option<&str>,
) -> Result<Self, DecoderError> {
match SymphoniaDecoder::init(mss, extension) {
Err(e) => match e {
Error::IoError(e) => Err(DecoderError::IoError(e.to_string())),
Error::DecodeError(e) => Err(DecoderError::DecodeError(e)),
Error::SeekError(_) => {
unreachable!("Seek errors should not occur during initialization")
}
Error::Unsupported(_) => Err(DecoderError::UnrecognizedFormat),
Error::LimitError(e) => Err(DecoderError::LimitError(e)),
Error::ResetRequired => Err(DecoderError::ResetRequired),
},
Ok(Some(decoder)) => Ok(decoder),
Ok(None) => Err(DecoderError::NoStreams),
}
}
pub(crate) fn into_inner(self) -> MediaSourceStream {
self.format.into_inner()
}
fn init(
mss: MediaSourceStream,
extension: Option<&str>,
) -> symphonia::core::errors::Result<Option<SymphoniaDecoder>> {
let mut hint = Hint::new();
if let Some(ext) = extension {
hint.with_extension(ext);
}
let format_opts: FormatOptions = FormatOptions {
enable_gapless: true,
..Default::default()
};
let metadata_opts: MetadataOptions = Default::default();
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),
};
// Select the first supported track
let track_id = probed
.format
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.ok_or(symphonia::core::errors::Error::Unsupported(
"No track with supported codec",
))?
.id;
let track = probed
.format
.tracks()
.iter()
.find(|track| track.id == track_id)
.unwrap();
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &DecoderOptions::default())?;
let total_duration = stream
.codec_params
.time_base
.zip(stream.codec_params.n_frames)
.map(|(base, frames)| base.calc_time(frames));
let mut decode_errors: usize = 0;
let decoded = loop {
let current_frame = match probed.format.next_packet() {
Ok(packet) => packet,
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_frame.track_id() != track_id {
continue;
}
match decoder.decode(&current_frame) {
Ok(decoded) => break decoded,
Err(e) => match e {
Error::DecodeError(_) => {
decode_errors += 1;
if decode_errors > MAX_DECODE_RETRIES {
return Err(e);
} else {
continue;
}
}
_ => return Err(e),
},
}
};
let spec = decoded.spec().to_owned();
let buffer = SymphoniaDecoder::get_buffer(decoded, &spec);
Ok(Some(SymphoniaDecoder {
decoder,
current_frame_offset: 0,
format: probed.format,
total_duration,
buffer,
spec,
}))
}
#[inline]
fn get_buffer(decoded: AudioBufferRef, spec: &SignalSpec) -> SampleBuffer<f32> {
let duration = units::Duration::from(decoded.capacity() as u64);
let mut buffer = SampleBuffer::<f32>::new(duration, *spec);
buffer.copy_interleaved_ref(decoded);
buffer
}
}
impl Source for SymphoniaDecoder {
#[inline]
fn current_frame_len(&self) -> Option<usize> {
Some(self.buffer.samples().len())
}
#[inline]
fn channels(&self) -> u16 {
self.spec.channels.count() as u16
}
#[inline]
fn sample_rate(&self) -> u32 {
self.spec.rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.total_duration
.map(|Time { seconds, frac }| Duration::new(seconds, (1f64 / frac) as u32))
}
fn try_seek(&mut self, pos: Duration) -> Result<(), source::SeekError> {
use symphonia::core::formats::{SeekMode, SeekTo};
let seek_beyond_end = self
.total_duration()
.is_some_and(|dur| dur.saturating_sub(pos).as_millis() < 1);
let time = if seek_beyond_end {
let time = self.total_duration.expect("if guarantees this is Some");
skip_back_a_tiny_bit(time) // some decoders can only seek to just before the end
} else {
pos.as_secs_f64().into()
};
// make sure the next sample is for the right channel
let to_skip = self.current_frame_offset % self.channels() as usize;
let seek_res = self
.format
.seek(
SeekMode::Accurate,
SeekTo::Time {
time,
track_id: None,
},
)
.map_err(SeekError::BaseSeek)?;
self.refine_position(seek_res)?;
self.current_frame_offset += to_skip;
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum SeekError {
#[error("Could not get next packet while refining seek position: {0:?}")]
Refining(symphonia::core::errors::Error),
#[error("Format reader failed to seek: {0:?}")]
BaseSeek(symphonia::core::errors::Error),
#[error("Decoding failed retrying on the next packet failed: {0:?}")]
Retrying(symphonia::core::errors::Error),
#[error("Decoding failed on multiple consecutive packets: {0:?}")]
Decoding(symphonia::core::errors::Error),
}
impl SymphoniaDecoder {
/// note frame offset must be set after
fn refine_position(&mut self, seek_res: SeekedTo) -> Result<(), source::SeekError> {
let mut samples_to_pass = seek_res.required_ts - seek_res.actual_ts;
let packet = loop {
let candidate = self.format.next_packet().map_err(SeekError::Refining)?;
if candidate.dur() > samples_to_pass {
break candidate;
} else {
samples_to_pass -= candidate.dur();
}
};
let mut decoded = self.decoder.decode(&packet);
for _ in 0..MAX_DECODE_RETRIES {
if decoded.is_err() {
let packet = self.format.next_packet().map_err(SeekError::Retrying)?;
decoded = self.decoder.decode(&packet);
}
}
let decoded = decoded.map_err(SeekError::Decoding)?;
decoded.spec().clone_into(&mut self.spec);
self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec);
self.current_frame_offset = samples_to_pass as usize * self.channels() as usize;
Ok(())
}
}
fn skip_back_a_tiny_bit(
Time {
mut seconds,
mut frac,
}: Time,
) -> Time {
frac -= 0.0001;
if frac < 0.0 {
seconds = seconds.saturating_sub(1);
frac = 1.0 - frac;
}
Time { seconds, frac }
}
impl Iterator for SymphoniaDecoder {
type Item = f32;
#[inline]
fn next(&mut self) -> Option<f32> {
if self.current_frame_offset >= self.buffer.len() {
let packet = self.format.next_packet().ok()?;
let mut decoded = self.decoder.decode(&packet);
for _ in 0..MAX_DECODE_RETRIES {
if decoded.is_err() {
let packet = self.format.next_packet().ok()?;
decoded = self.decoder.decode(&packet);
}
}
let decoded = decoded.ok()?;
decoded.spec().clone_into(&mut self.spec);
self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec);
self.current_frame_offset = 0;
}
let sample = *self.buffer.samples().get(self.current_frame_offset)?;
self.current_frame_offset += 1;
Some(sample)
}
}
-144
View File
@@ -1,144 +0,0 @@
use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use crate::source::SeekError;
use crate::Source;
use lewton::inside_ogg::OggStreamReader;
/// Decoder for an OGG file that contains Vorbis sound format.
pub struct VorbisDecoder<R>
where
R: Read + Seek,
{
stream_reader: OggStreamReader<R>,
current_data: Vec<i16>,
next: usize,
}
impl<R> VorbisDecoder<R>
where
R: Read + Seek,
{
/// Attempts to decode the data as ogg/vorbis.
pub fn new(mut data: R) -> Result<VorbisDecoder<R>, R> {
if !is_vorbis(data.by_ref()) {
return Err(data);
}
let stream_reader = OggStreamReader::new(data).unwrap();
Ok(Self::from_stream_reader(stream_reader))
}
pub fn from_stream_reader(mut stream_reader: OggStreamReader<R>) -> Self {
let mut data = match stream_reader.read_dec_packet_itl() {
Ok(Some(d)) => d,
_ => Vec::new(),
};
// The first packet is always empty, therefore
// we need to read the second frame to get some data
if let Ok(Some(mut d)) = stream_reader.read_dec_packet_itl() {
data.append(&mut d);
}
VorbisDecoder {
stream_reader,
current_data: data,
next: 0,
}
}
pub fn into_inner(self) -> OggStreamReader<R> {
self.stream_reader
}
}
impl<R> Source for VorbisDecoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
Some(self.current_data.len())
}
#[inline]
fn channels(&self) -> u16 {
self.stream_reader.ident_hdr.audio_channels as u16
}
#[inline]
fn sample_rate(&self) -> u32 {
self.stream_reader.ident_hdr.audio_sample_rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
None
}
/// seek is broken, https://github.com/RustAudio/lewton/issues/73.
// We could work around it by:
// - using unsafe to create an instance of Self
// - use mem::swap to turn the &mut self into a mut self
// - take out the underlying Read+Seek
// - make a new self and seek
//
// If this issue is fixed use the implementation in
// commit: 3bafe32388b4eb7a48c6701e6c65044dc8c555e6
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
impl<R> Iterator for VorbisDecoder<R>
where
R: Read + Seek,
{
type Item = i16;
#[inline]
fn next(&mut self) -> Option<i16> {
if let Some(sample) = self.current_data.get(self.next).copied() {
self.next += 1;
if self.current_data.is_empty() {
if let Ok(Some(data)) = self.stream_reader.read_dec_packet_itl() {
self.current_data = data;
self.next = 0;
}
}
Some(sample)
} else {
if let Ok(Some(data)) = self.stream_reader.read_dec_packet_itl() {
self.current_data = data;
self.next = 0;
}
let sample = self.current_data.get(self.next).copied();
self.next += 1;
sample
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.current_data.len(), None)
}
}
/// Returns true if the stream contains Vorbis data, then resets it to where it was.
fn is_vorbis<R>(mut data: R) -> bool
where
R: Read + Seek,
{
let stream_pos = data.stream_position().unwrap();
if OggStreamReader::new(data.by_ref()).is_err() {
data.seek(SeekFrom::Start(stream_pos)).unwrap();
return false;
}
data.seek(SeekFrom::Start(stream_pos)).unwrap();
true
}
-222
View File
@@ -1,222 +0,0 @@
use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;
use crate::source::SeekError;
use crate::Source;
use hound::{SampleFormat, WavReader};
/// Decoder for the WAV format.
pub struct WavDecoder<R>
where
R: Read + Seek,
{
reader: SamplesIterator<R>,
total_duration: Duration,
sample_rate: u32,
channels: u16,
}
impl<R> WavDecoder<R>
where
R: Read + Seek,
{
/// Attempts to decode the data as WAV.
pub fn new(mut data: R) -> Result<WavDecoder<R>, R> {
if !is_wave(data.by_ref()) {
return Err(data);
}
let reader = WavReader::new(data).unwrap();
let spec = reader.spec();
let len = reader.len() as u64;
let reader = SamplesIterator {
reader,
samples_read: 0,
};
let sample_rate = spec.sample_rate;
let channels = spec.channels;
let total_duration =
Duration::from_micros((1_000_000 * len) / (sample_rate as u64 * channels as u64));
Ok(WavDecoder {
reader,
total_duration,
sample_rate,
channels,
})
}
pub fn into_inner(self) -> R {
self.reader.reader.into_inner()
}
}
struct SamplesIterator<R>
where
R: Read + Seek,
{
reader: WavReader<R>,
samples_read: u32, // wav header is u32 so this suffices
}
impl<R> Iterator for SamplesIterator<R>
where
R: Read + Seek,
{
type Item = i16;
#[inline]
fn next(&mut self) -> Option<i16> {
let spec = self.reader.spec();
match (spec.sample_format, spec.bits_per_sample) {
(SampleFormat::Float, 32) => self.reader.samples().next().map(|value| {
self.samples_read += 1;
f32_to_i16(value.unwrap_or(0.0))
}),
(SampleFormat::Int, 8) => self.reader.samples().next().map(|value| {
self.samples_read += 1;
i8_to_i16(value.unwrap_or(0))
}),
(SampleFormat::Int, 16) => self.reader.samples().next().map(|value| {
self.samples_read += 1;
value.unwrap_or(0)
}),
(SampleFormat::Int, 24) => self.reader.samples().next().map(|value| {
self.samples_read += 1;
i24_to_i16(value.unwrap_or(0))
}),
(SampleFormat::Int, 32) => self.reader.samples().next().map(|value| {
self.samples_read += 1;
i32_to_i16(value.unwrap_or(0))
}),
(sample_format, bits_per_sample) => panic!(
"Unimplemented wav spec: {:?}, {}",
sample_format, bits_per_sample
),
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (self.reader.len() - self.samples_read) as usize;
(len, Some(len))
}
}
impl<R> ExactSizeIterator for SamplesIterator<R> where R: Read + Seek {}
impl<R> Source for WavDecoder<R>
where
R: Read + Seek,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
self.channels
}
#[inline]
fn sample_rate(&self) -> u32 {
self.sample_rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
Some(self.total_duration)
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
let file_len = self.reader.reader.duration();
let new_pos = pos.as_secs_f32() * self.sample_rate() as f32;
let new_pos = new_pos as u32;
let new_pos = new_pos.min(file_len); // saturate pos at the end of the source
// make sure the next sample is for the right channel
let to_skip = self.reader.samples_read % self.channels() as u32;
self.reader
.reader
.seek(new_pos)
.map_err(SeekError::HoundDecoder)?;
self.reader.samples_read = new_pos * self.channels() as u32;
for _ in 0..to_skip {
self.next();
}
Ok(())
}
}
impl<R> Iterator for WavDecoder<R>
where
R: Read + Seek,
{
type Item = i16;
#[inline]
fn next(&mut self) -> Option<i16> {
self.reader.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.reader.size_hint()
}
}
impl<R> ExactSizeIterator for WavDecoder<R> where R: Read + Seek {}
/// Returns true if the stream contains WAV data, then resets it to where it was.
fn is_wave<R>(mut data: R) -> bool
where
R: Read + Seek,
{
let stream_pos = data.stream_position().unwrap();
if WavReader::new(data.by_ref()).is_err() {
data.seek(SeekFrom::Start(stream_pos)).unwrap();
return false;
}
data.seek(SeekFrom::Start(stream_pos)).unwrap();
true
}
/// Returns a 32 bit WAV float as an i16. WAV floats are typically in the range of
/// [-1.0, 1.0] while i16s are in the range [-32768, 32767]. Note that this
/// function definitely causes precision loss but hopefully this isn't too
/// audiable when actually playing?
fn f32_to_i16(f: f32) -> i16 {
// prefer to clip the input rather than be excessively loud.
(f.clamp(-1.0, 1.0) * i16::MAX as f32) as i16
}
/// Returns an 8-bit WAV int as an i16. This scales the sample value by a factor
/// of 256.
fn i8_to_i16(i: i8) -> i16 {
i as i16 * 256
}
/// Returns a 24 bit WAV int as an i16. Note that this is a 24 bit integer, not a
/// 32 bit one. 24 bit ints are in the range [−8,388,608, 8,388,607] while i16s
/// are in the range [-32768, 32767]. Note that this function definitely causes
/// precision loss but hopefully this isn't too audiable when actually playing?
fn i24_to_i16(i: i32) -> i16 {
(i >> 8) as i16
}
/// Returns a 32 bit WAV int as an i16. 32 bit ints are in the range
/// [-2,147,483,648, 2,147,483,647] while i16s are in the range [-32768, 32767].
/// Note that this function definitely causes precision loss but hopefully this
/// isn't too audiable when actually playing?
fn i32_to_i16(i: i32) -> i16 {
(i >> 16) as i16
}
-303
View File
@@ -1,303 +0,0 @@
//! Mixer that plays multiple sounds at the same time.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::source::{SeekError, Source, UniformSourceIterator};
use crate::Sample;
/// Builds a new mixer.
///
/// You can choose the characteristics of the output thanks to this constructor. All the sounds
/// added to the mixer will be converted to these values.
///
/// After creating a mixer, you can add new sounds with the controller.
pub fn mixer<S>(
channels: u16,
sample_rate: u32,
) -> (Arc<DynamicMixerController<S>>, DynamicMixer<S>)
where
S: Sample + Send + 'static,
{
let input = Arc::new(DynamicMixerController {
has_pending: AtomicBool::new(false),
pending_sources: Mutex::new(Vec::new()),
channels,
sample_rate,
});
let output = DynamicMixer {
current_sources: Vec::with_capacity(16),
input: input.clone(),
sample_count: 0,
still_pending: vec![],
still_current: vec![],
};
(input, output)
}
/// The input of the mixer.
pub struct DynamicMixerController<S> {
has_pending: AtomicBool,
pending_sources: Mutex<Vec<Box<dyn Source<Item = S> + Send>>>,
channels: u16,
sample_rate: u32,
}
impl<S> DynamicMixerController<S>
where
S: Sample + Send + 'static,
{
/// Adds a new source to mix to the existing ones.
#[inline]
pub fn add<T>(&self, source: T)
where
T: Source<Item = S> + Send + 'static,
{
let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate);
self.pending_sources
.lock()
.unwrap()
.push(Box::new(uniform_source) as Box<_>);
self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering?
}
}
/// The output of the mixer. Implements `Source`.
pub struct DynamicMixer<S> {
// The current iterator that produces samples.
current_sources: Vec<Box<dyn Source<Item = S> + Send>>,
// The pending sounds.
input: Arc<DynamicMixerController<S>>,
// The number of samples produced so far.
sample_count: usize,
// A temporary vec used in start_pending_sources.
still_pending: Vec<Box<dyn Source<Item = S> + Send>>,
// A temporary vec used in sum_current_sources.
still_current: Vec<Box<dyn Source<Item = S> + Send>>,
}
impl<S> Source for DynamicMixer<S>
where
S: Sample + Send + 'static,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
None
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
// uncomment when #510 is implemented (query position of playback)
// let mut org_positions = Vec::with_capacity(self.current_sources.len());
// let mut encounterd_err = None;
//
// for source in &mut self.current_sources {
// let pos = /* source.playback_pos() */ todo!();
// if let Err(e) = source.try_seek(pos) {
// encounterd_err = Some(e);
// break;
// } else {
// // store pos in case we need to roll back
// org_positions.push(pos);
// }
// }
//
// if let Some(e) = encounterd_err {
// // rollback seeks that happend before err
// for (pos, source) in org_positions
// .into_iter()
// .zip(self.current_sources.iter_mut())
// {
// source.try_seek(pos)?;
// }
// Err(e)
// } else {
// Ok(())
// }
}
}
impl<S> Iterator for DynamicMixer<S>
where
S: Sample + Send + 'static,
{
type Item = S;
#[inline]
fn next(&mut self) -> Option<S> {
if self.input.has_pending.load(Ordering::SeqCst) {
self.start_pending_sources();
}
self.sample_count += 1;
let sum = self.sum_current_sources();
if self.current_sources.is_empty() {
None
} else {
Some(sum)
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}
impl<S> DynamicMixer<S>
where
S: Sample + Send + 'static,
{
// Samples from the #next() function are interlaced for each of the channels.
// We need to ensure we start playing sources so that their samples are
// in-step with the modulo of the samples produced so far. Otherwise, the
// sound will play on the wrong channels, e.g. left / right will be reversed.
fn start_pending_sources(&mut self) {
let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering?
for source in pending.drain(..) {
let in_step = self.sample_count % source.channels() as usize == 0;
if in_step {
self.current_sources.push(source);
} else {
self.still_pending.push(source);
}
}
std::mem::swap(&mut self.still_pending, &mut pending);
let has_pending = !pending.is_empty();
self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering?
}
fn sum_current_sources(&mut self) -> S {
let mut sum = S::zero_value();
for mut source in self.current_sources.drain(..) {
if let Some(value) = source.next() {
sum = sum.saturating_add(value);
self.still_current.push(source);
}
}
std::mem::swap(&mut self.still_current, &mut self.current_sources);
sum
}
}
#[cfg(test)]
mod tests {
use crate::buffer::SamplesBuffer;
use crate::dynamic_mixer;
use crate::source::Source;
#[test]
fn basic() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
assert_eq!(rx.channels(), 1);
assert_eq!(rx.sample_rate(), 48000);
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), None);
}
#[test]
fn channels_conv() {
let (tx, mut rx) = dynamic_mixer::mixer(2, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
assert_eq!(rx.channels(), 2);
assert_eq!(rx.sample_rate(), 48000);
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), None);
}
#[test]
fn rate_conv() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 96000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
assert_eq!(rx.channels(), 1);
assert_eq!(rx.sample_rate(), 96000);
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), None);
}
#[test]
fn start_afterwards() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 6, 6, 7, 7, 7]));
assert_eq!(rx.next(), Some(15));
assert_eq!(rx.next(), Some(-5));
assert_eq!(rx.next(), Some(6));
assert_eq!(rx.next(), Some(6));
tx.add(SamplesBuffer::new(1, 48000, vec![2i16]));
assert_eq!(rx.next(), Some(9));
assert_eq!(rx.next(), Some(7));
assert_eq!(rx.next(), Some(7));
assert_eq!(rx.next(), None);
}
}
-137
View File
@@ -1,137 +0,0 @@
//! Audio playback library.
//!
//! The main concept of this library is the [`Source`] trait, which
//! represents a sound (streaming or not). In order to play a sound, there are three steps:
//!
//! - Create an object that represents the streaming sound. It can be a sine wave, a buffer, a
//! [`decoder`], etc. or even your own type that implements the [`Source`] trait.
//! - Get an output stream handle to a physical device. For example, get a stream to the system's
//! default sound device with [`OutputStream::try_default()`]
//! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle.
//!
//! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s,
//! which may not be the case. If you get a compilation error, try calling
//! [`.convert_samples()`](Source::convert_samples) on the source to fix it.
//!
//! For example, here is how you would play an audio file:
//!
//! ```no_run
//! use std::fs::File;
//! use std::io::BufReader;
//! use rodio::{Decoder, OutputStream, source::Source};
//!
//! // Get an output stream handle to the default physical sound device
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
//! // Load a sound from a file, using a path relative to Cargo.toml
//! let file = BufReader::new(File::open("examples/music.ogg").unwrap());
//! // Decode that sound file into a source
//! let source = Decoder::new(file).unwrap();
//! // Play the sound directly on the device
//! stream_handle.play_raw(source.convert_samples());
//!
//! // The sound plays in a separate audio thread,
//! // so we need to keep the main thread alive while it's playing.
//! std::thread::sleep(std::time::Duration::from_secs(5));
//! ```
//!
//! ## Sink
//!
//! In order to make it easier to control the playback, the rodio library also provides a type
//! named [`Sink`] which represents an audio track.
//!
//! Instead of playing the sound with [`play_raw`](OutputStreamHandle::play_raw), you can add it to
//! a [`Sink`] instead.
//!
//! - Get a [`Sink`] to the output stream, and [`.append()`](Sink::append) your sound to it.
//!
//! ```no_run
//! use std::fs::File;
//! use std::io::BufReader;
//! use std::time::Duration;
//! use rodio::{Decoder, OutputStream, Sink};
//! use rodio::source::{SineWave, Source};
//!
//! // _stream must live as long as the sink
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
//! let sink = Sink::try_new(&stream_handle).unwrap();
//!
//! // Add a dummy source of the sake of the example.
//! let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20);
//! sink.append(source);
//!
//! // The sound plays in a separate thread. This call will block the current thread until the sink
//! // has finished playing all its queued sounds.
//! sink.sleep_until_end();
//! ```
//!
//! The [`append`](Sink::append) method will add the sound at the end of the
//! sink. It will be played when all the previous sounds have been played. If you want multiple
//! sounds to play simultaneously, you should create multiple [`Sink`]s.
//!
//! The [`Sink`] type also provides utilities such as playing/pausing or controlling the volume.
//!
//! **Please note that the [`Sink`] requires the [`OutputStream`], make sure that the outputstream is not dropped before the sink.**
//!
//! ## Filters
//!
//! The [`Source`] trait provides various filters, similar to the standard [`Iterator`] trait.
//!
//! Example:
//!
//! ```
//! use rodio::Source;
//! use std::time::Duration;
//!
//! // Repeats the first five seconds of the sound forever.
//! # let source = rodio::source::SineWave::new(440.0);
//! let source = source.take_duration(Duration::from_secs(5)).repeat_infinite();
//! ```
//!
//! ## Alternative Decoder Backends
//!
//! [Symphonia](https://github.com/pdeljanov/Symphonia) is an alternative decoder library that can be used in place
//! of many of the default backends.
//! Currently, the main benefit is that Symphonia is the only backend that supports M4A and AAC,
//! but it may be used to implement additional optional functionality in the future.
//!
//! To use, enable either the `symphonia-all` feature to enable all Symphonia codecs
//! or enable specific codecs using one of the `symphonia-{codec name}` features.
//! If you enable one or more of the Symphonia codecs, you may want to set `default-features = false` in order
//! to avoid adding extra crates to your binary.
//! See the [available feature flags](https://docs.rs/crate/rodio/latest/features) for all options.
//!
//! ## How it works under the hood
//!
//! Rodio spawns a background thread that is dedicated to reading from the sources and sending
//! the output to the device. Whenever you give up ownership of a [`Source`] in order to play it,
//! it is sent to this background thread where it will be read by rodio.
//!
//! All the sounds are mixed together by rodio before being sent to the operating system or the
//! hardware. Therefore there is no restriction on the number of sounds that play simultaneously or
//! the number of sinks that can be created (except for the fact that creating too many will slow
//! down your program).
//!
#![cfg_attr(test, deny(missing_docs))]
pub use cpal::{
self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices,
SupportedStreamConfig,
};
mod conversions;
mod sink;
mod spatial_sink;
mod stream;
pub mod buffer;
pub mod decoder;
pub mod dynamic_mixer;
pub mod queue;
pub mod source;
pub mod static_buffer;
pub use crate::conversions::Sample;
pub use crate::decoder::Decoder;
pub use crate::sink::Sink;
pub use crate::source::Source;
pub use crate::spatial_sink::SpatialSink;
pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError};
-319
View File
@@ -1,319 +0,0 @@
//! Queue that plays sounds one after the other.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::source::{Empty, SeekError, Source, Zero};
use crate::Sample;
#[cfg(feature = "crossbeam-channel")]
use crossbeam_channel::{unbounded as channel, Receiver, Sender};
#[cfg(not(feature = "crossbeam-channel"))]
use std::sync::mpsc::{channel, Receiver, Sender};
/// Builds a new queue. It consists of an input and an output.
///
/// The input can be used to add sounds to the end of the queue, while the output implements
/// `Source` and plays the sounds.
///
/// The parameter indicates how the queue should behave if the queue becomes empty:
///
/// - If you pass `true`, then the queue is infinite and will play a silence instead until you add
/// a new sound.
/// - If you pass `false`, then the queue will report that it has finished playing.
///
pub fn queue<S>(keep_alive_if_empty: bool) -> (Arc<SourcesQueueInput<S>>, SourcesQueueOutput<S>)
where
S: Sample + Send + 'static,
{
let input = Arc::new(SourcesQueueInput {
next_sounds: Mutex::new(Vec::new()),
keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty),
});
let output = SourcesQueueOutput {
current: Box::new(Empty::<S>::new()) as Box<_>,
signal_after_end: None,
input: input.clone(),
};
(input, output)
}
// TODO: consider reimplementing this with `from_factory`
type Sound<S> = Box<dyn Source<Item = S> + Send>;
type SignalDone = Option<Sender<()>>;
/// The input of the queue.
pub struct SourcesQueueInput<S> {
next_sounds: Mutex<Vec<(Sound<S>, SignalDone)>>,
// See constructor.
keep_alive_if_empty: AtomicBool,
}
impl<S> SourcesQueueInput<S>
where
S: Sample + Send + 'static,
{
/// Adds a new source to the end of the queue.
#[inline]
pub fn append<T>(&self, source: T)
where
T: Source<Item = S> + Send + 'static,
{
self.next_sounds
.lock()
.unwrap()
.push((Box::new(source) as Box<_>, None));
}
/// Adds a new source to the end of the queue.
///
/// The `Receiver` will be signalled when the sound has finished playing.
///
/// Enable the feature flag `crossbeam-channel` in rodio to use a `crossbeam_channel::Receiver` instead.
#[inline]
pub fn append_with_signal<T>(&self, source: T) -> Receiver<()>
where
T: Source<Item = S> + Send + 'static,
{
let (tx, rx) = channel();
self.next_sounds
.lock()
.unwrap()
.push((Box::new(source) as Box<_>, Some(tx)));
rx
}
/// Sets whether the queue stays alive if there's no more sound to play.
///
/// See also the constructor.
pub fn set_keep_alive_if_empty(&self, keep_alive_if_empty: bool) {
self.keep_alive_if_empty
.store(keep_alive_if_empty, Ordering::Release);
}
/// Removes all the sounds from the queue. Returns the number of sounds cleared.
pub fn clear(&self) -> usize {
let mut sounds = self.next_sounds.lock().unwrap();
let len = sounds.len();
sounds.clear();
len
}
}
/// The output of the queue. Implements `Source`.
pub struct SourcesQueueOutput<S> {
// The current iterator that produces samples.
current: Box<dyn Source<Item = S> + Send>,
// Signal this sender before picking from `next`.
signal_after_end: Option<Sender<()>>,
// The next sounds.
input: Arc<SourcesQueueInput<S>>,
}
const THRESHOLD: usize = 512;
impl<S> Source for SourcesQueueOutput<S>
where
S: Sample + Send + 'static,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
// This function is non-trivial because the boundary between two sounds in the queue should
// be a frame boundary as well.
//
// The current sound is free to return `None` for `current_frame_len()`, in which case
// we *should* return the number of samples remaining the current sound.
// This can be estimated with `size_hint()`.
//
// If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
// situation we force a frame to have a maximum number of samples indicate by this
// constant.
// Try the current `current_frame_len`.
if let Some(val) = self.current.current_frame_len() {
if val != 0 {
return Some(val);
} else if self.input.keep_alive_if_empty.load(Ordering::Acquire)
&& self.input.next_sounds.lock().unwrap().is_empty()
{
// The next source will be a filler silence which will have the length of `THRESHOLD`
return Some(THRESHOLD);
}
}
// Try the size hint.
let (lower_bound, _) = self.current.size_hint();
// The iterator default implementation just returns 0.
// That's a problematic value, so skip it.
if lower_bound > 0 {
return Some(lower_bound);
}
// Otherwise we use the constant value.
Some(THRESHOLD)
}
#[inline]
fn channels(&self) -> u16 {
self.current.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.current.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
None
}
/// Only seeks within the current source.
// We can not go back to previous sources. We could implement seek such
// that it advances the queue if the position is beyond the current song.
//
// We would then however need to enable seeking backwards across sources too.
// That no longer seems in line with the queue behaviour.
//
// A final pain point is that we would need the total duration for the
// next few songs.
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.current.try_seek(pos)
}
}
impl<S> Iterator for SourcesQueueOutput<S>
where
S: Sample + Send + 'static,
{
type Item = S;
#[inline]
fn next(&mut self) -> Option<S> {
loop {
// Basic situation that will happen most of the time.
if let Some(sample) = self.current.next() {
return Some(sample);
}
// Since `self.current` has finished, we need to pick the next sound.
// In order to avoid inlining this expensive operation, the code is in another function.
if self.go_next().is_err() {
return None;
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.current.size_hint().0, None)
}
}
impl<S> SourcesQueueOutput<S>
where
S: Sample + Send + 'static,
{
// Called when `current` is empty and we must jump to the next element.
// Returns `Ok` if the sound should continue playing, or an error if it should stop.
//
// This method is separate so that it is not inlined.
fn go_next(&mut self) -> Result<(), ()> {
if let Some(signal_after_end) = self.signal_after_end.take() {
let _ = signal_after_end.send(());
}
let (next, signal_after_end) = {
let mut next = self.input.next_sounds.lock().unwrap();
if next.len() == 0 {
let silence = Box::new(Zero::<S>::new_samples(1, 44100, THRESHOLD)) as Box<_>;
if self.input.keep_alive_if_empty.load(Ordering::Acquire) {
// Play a short silence in order to avoid spinlocking.
(silence, None)
} else {
return Err(());
}
} else {
next.remove(0)
}
};
self.current = next;
self.signal_after_end = signal_after_end;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::buffer::SamplesBuffer;
use crate::queue;
use crate::source::Source;
#[test]
#[ignore] // FIXME: samples rate and channel not updated immediately after transition
fn basic() {
let (tx, mut rx) = queue::queue(false);
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.append(SamplesBuffer::new(2, 96000, vec![5i16, 5, 5, 5]));
assert_eq!(rx.channels(), 1);
assert_eq!(rx.sample_rate(), 48000);
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
assert_eq!(rx.channels(), 2);
assert_eq!(rx.sample_rate(), 96000);
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), Some(5));
assert_eq!(rx.next(), None);
}
#[test]
fn immediate_end() {
let (_, mut rx) = queue::queue::<i16>(false);
assert_eq!(rx.next(), None);
}
#[test]
fn keep_alive() {
let (tx, mut rx) = queue::queue(true);
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
for _ in 0..100000 {
assert_eq!(rx.next(), Some(0));
}
}
#[test]
#[ignore] // TODO: not yet implemented
fn no_delay_when_added() {
let (tx, mut rx) = queue::queue(true);
for _ in 0..500 {
assert_eq!(rx.next(), Some(0));
}
tx.append(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
assert_eq!(rx.next(), Some(10));
assert_eq!(rx.next(), Some(-10));
}
}
-428
View File
@@ -1,428 +0,0 @@
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[cfg(feature = "crossbeam-channel")]
use crossbeam_channel::{Receiver, Sender};
#[cfg(not(feature = "crossbeam-channel"))]
use std::sync::mpsc::{Receiver, Sender};
use crate::source::SeekError;
use crate::stream::{OutputStreamHandle, PlayError};
use crate::{queue, source::Done, Sample, Source};
use cpal::FromSample;
/// Handle to a device that outputs sounds.
///
/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue
/// playing.
pub struct Sink {
queue_tx: Arc<queue::SourcesQueueInput<f32>>,
sleep_until_end: Mutex<Option<Receiver<()>>>,
controls: Arc<Controls>,
sound_count: Arc<AtomicUsize>,
detached: bool,
}
struct SeekOrder {
pos: Duration,
feedback: Sender<Result<(), SeekError>>,
}
impl SeekOrder {
fn new(pos: Duration) -> (Self, Receiver<Result<(), SeekError>>) {
#[cfg(not(feature = "crossbeam-channel"))]
let (tx, rx) = {
use std::sync::mpsc;
mpsc::channel()
};
#[cfg(feature = "crossbeam-channel")]
let (tx, rx) = {
use crossbeam_channel::bounded;
bounded(1)
};
(Self { pos, feedback: tx }, rx)
}
fn attempt<S>(self, maybe_seekable: &mut S)
where
S: Source,
S::Item: Sample + Send,
{
let res = maybe_seekable.try_seek(self.pos);
let _ignore_receiver_dropped = self.feedback.send(res);
}
}
struct Controls {
pause: AtomicBool,
volume: Mutex<f32>,
stopped: AtomicBool,
speed: Mutex<f32>,
to_clear: Mutex<u32>,
seek: Mutex<Option<SeekOrder>>,
position: Mutex<Duration>,
}
impl Sink {
/// Builds a new `Sink`, beginning playback on a stream.
#[inline]
pub fn try_new(stream: &OutputStreamHandle) -> Result<Sink, PlayError> {
let (sink, queue_rx) = Sink::new_idle();
stream.play_raw(queue_rx)?;
Ok(sink)
}
/// Builds a new `Sink`.
#[inline]
pub fn new_idle() -> (Sink, queue::SourcesQueueOutput<f32>) {
let (queue_tx, queue_rx) = queue::queue(true);
let sink = Sink {
queue_tx,
sleep_until_end: Mutex::new(None),
controls: Arc::new(Controls {
pause: AtomicBool::new(false),
volume: Mutex::new(1.0),
stopped: AtomicBool::new(false),
speed: Mutex::new(1.0),
to_clear: Mutex::new(0),
seek: Mutex::new(None),
position: Mutex::new(Duration::ZERO),
}),
sound_count: Arc::new(AtomicUsize::new(0)),
detached: false,
};
(sink, queue_rx)
}
/// Appends a sound to the queue of sounds to play.
#[inline]
pub fn append<S>(&self, source: S)
where
S: Source + Send + 'static,
f32: FromSample<S::Item>,
S::Item: Sample + Send,
{
// Wait for queue to flush then resume stopped playback
if self.controls.stopped.load(Ordering::SeqCst) {
if self.sound_count.load(Ordering::SeqCst) > 0 {
self.sleep_until_end();
}
self.controls.stopped.store(false, Ordering::SeqCst);
}
let controls = self.controls.clone();
let start_played = AtomicBool::new(false);
let source = source
.speed(1.0)
// must be placed before pausable but after speed & delay
.track_position()
.pausable(false)
.amplify(1.0)
.skippable()
.stoppable()
// if you change the duration update the docs for try_seek!
.periodic_access(Duration::from_millis(100), move |src| {
if controls.stopped.load(Ordering::SeqCst) {
src.stop();
*controls.position.lock().unwrap() = Duration::ZERO;
}
{
let mut to_clear = controls.to_clear.lock().unwrap();
if *to_clear > 0 {
src.inner_mut().skip();
*to_clear -= 1;
*controls.position.lock().unwrap() = Duration::ZERO;
} else {
*controls.position.lock().unwrap() = src.inner().inner().inner().inner().get_pos();
}
}
let amp = src.inner_mut().inner_mut();
amp.set_factor(*controls.volume.lock().unwrap());
amp.inner_mut()
.set_paused(controls.pause.load(Ordering::SeqCst));
amp.inner_mut()
.inner_mut()
.inner_mut()
.set_factor(*controls.speed.lock().unwrap());
if let Some(seek) = controls.seek.lock().unwrap().take() {
seek.attempt(amp)
}
start_played.store(true, Ordering::SeqCst);
})
.convert_samples();
self.sound_count.fetch_add(1, Ordering::Relaxed);
let source = Done::new(source, self.sound_count.clone());
*self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source));
}
/// Gets the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
/// multiply each sample by this value.
#[inline]
pub fn volume(&self) -> f32 {
*self.controls.volume.lock().unwrap()
}
/// Changes the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` will
/// multiply each sample by this value.
#[inline]
pub fn set_volume(&self, value: f32) {
*self.controls.volume.lock().unwrap() = value;
}
/// Gets the speed of the sound.
///
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
/// change the play speed of the sound.
#[inline]
pub fn speed(&self) -> f32 {
*self.controls.speed.lock().unwrap()
}
/// Changes the speed of the sound.
///
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will
/// change the play speed of the sound.
#[inline]
pub fn set_speed(&self, value: f32) {
*self.controls.speed.lock().unwrap() = value;
}
/// Resumes playback of a paused sink.
///
/// No effect if not paused.
#[inline]
pub fn play(&self) {
self.controls.pause.store(false, Ordering::SeqCst);
}
// There is no `can_seek()` method as it is impossible to use correctly. Between
// checking if a source supports seeking and actually seeking the sink can
// switch to a new source.
/// Attempts to seek to a given position in the current source.
///
/// This blocks between 0 and ~5 milliseconds.
///
/// As long as the duration of the source is known, seek is guaranteed to saturate
/// at the end of the source. For example given a source that reports a total duration
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
/// 42 seconds.
///
/// # Errors
/// This function will return [`SeekError::NotSupported`] if one of the underlying
/// sources does not support seeking.
///
/// It will return an error if an implementation ran
/// into one during the seek.
///
/// When seeking beyond the end of a source this
/// function might return an error if the duration of the source is not known.
pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
let (order, feedback) = SeekOrder::new(pos);
*self.controls.seek.lock().unwrap() = Some(order);
if self.sound_count.load(Ordering::Acquire) == 0 {
// No sound is playing, seek will not be performed
return Ok(());
}
match feedback.recv() {
Ok(seek_res) => seek_res,
// The feedback channel closed. Probably another seekorder was set
// invalidating this one and closing the feedback channel
// ... or the audio thread panicked.
Err(_) => Ok(()),
}
}
/// Pauses playback of this sink.
///
/// No effect if already paused.
///
/// A paused sink can be resumed with `play()`.
pub fn pause(&self) {
self.controls.pause.store(true, Ordering::SeqCst);
}
/// Gets if a sink is paused
///
/// Sinks can be paused and resumed using `pause()` and `play()`. This returns `true` if the
/// sink is paused.
pub fn is_paused(&self) -> bool {
self.controls.pause.load(Ordering::SeqCst)
}
/// Removes all currently loaded `Source`s from the `Sink`, and pauses it.
///
/// See `pause()` for information about pausing a `Sink`.
pub fn clear(&self) {
let len = self.sound_count.load(Ordering::SeqCst) as u32;
*self.controls.to_clear.lock().unwrap() = len;
self.sleep_until_end();
self.pause();
}
/// Skips to the next `Source` in the `Sink`
///
/// If there are more `Source`s appended to the `Sink` at the time,
/// it will play the next one. Otherwise, the `Sink` will finish as if
/// it had finished playing a `Source` all the way through.
pub fn skip_one(&self) {
let len = self.sound_count.load(Ordering::SeqCst) as u32;
let mut to_clear = self.controls.to_clear.lock().unwrap();
if len > *to_clear {
*to_clear += 1;
}
}
/// Stops the sink by emptying the queue.
#[inline]
pub fn stop(&self) {
self.controls.stopped.store(true, Ordering::SeqCst);
}
/// Destroys the sink without stopping the sounds that are still playing.
#[inline]
pub fn detach(mut self) {
self.detached = true;
}
/// Sleeps the current thread until the sound ends.
#[inline]
pub fn sleep_until_end(&self) {
if let Some(sleep_until_end) = self.sleep_until_end.lock().unwrap().take() {
let _ = sleep_until_end.recv();
}
}
/// Returns true if this sink has no more sounds to play.
#[inline]
pub fn empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of sounds currently in the queue.
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
self.sound_count.load(Ordering::Relaxed)
}
/// Returns the position of the sound that's being played.
///
/// This takes into account any speedup or delay applied.
///
/// Example: if you apply a speedup of *2* to an mp3 decoder source and
/// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3
/// recording is *10s* from its start.
#[inline]
pub fn get_pos(&self) -> Duration {
*self.controls.position.lock().unwrap()
}
}
impl Drop for Sink {
#[inline]
fn drop(&mut self) {
self.queue_tx.set_keep_alive_if_empty(false);
if !self.detached {
self.controls.stopped.store(true, Ordering::Relaxed);
}
}
}
#[cfg(test)]
mod tests {
use crate::buffer::SamplesBuffer;
use crate::{Sink, Source};
use std::sync::atomic::Ordering;
#[test]
fn test_pause_and_stop() {
let (sink, mut queue_rx) = Sink::new_idle();
// assert_eq!(queue_rx.next(), Some(0.0));
let v = vec![10i16, -10, 20, -20, 30, -30];
// Low rate to ensure immediate control.
sink.append(SamplesBuffer::new(1, 1, v.clone()));
let mut src = SamplesBuffer::new(1, 1, v).convert_samples();
assert_eq!(queue_rx.next(), src.next());
assert_eq!(queue_rx.next(), src.next());
sink.pause();
assert_eq!(queue_rx.next(), Some(0.0));
sink.play();
assert_eq!(queue_rx.next(), src.next());
assert_eq!(queue_rx.next(), src.next());
sink.stop();
assert_eq!(queue_rx.next(), Some(0.0));
assert_eq!(sink.empty(), true);
}
#[test]
fn test_stop_and_start() {
let (sink, mut queue_rx) = Sink::new_idle();
let v = vec![10i16, -10, 20, -20, 30, -30];
sink.append(SamplesBuffer::new(1, 1, v.clone()));
let mut src = SamplesBuffer::new(1, 1, v.clone()).convert_samples();
assert_eq!(queue_rx.next(), src.next());
assert_eq!(queue_rx.next(), src.next());
sink.stop();
assert!(sink.controls.stopped.load(Ordering::SeqCst));
assert_eq!(queue_rx.next(), Some(0.0));
src = SamplesBuffer::new(1, 1, v.clone()).convert_samples();
sink.append(SamplesBuffer::new(1, 1, v));
assert!(!sink.controls.stopped.load(Ordering::SeqCst));
// Flush silence
let mut queue_rx = queue_rx.skip_while(|v| *v == 0.0);
assert_eq!(queue_rx.next(), src.next());
assert_eq!(queue_rx.next(), src.next());
}
#[test]
fn test_volume() {
let (sink, mut queue_rx) = Sink::new_idle();
let v = vec![10i16, -10, 20, -20, 30, -30];
// High rate to avoid immediate control.
sink.append(SamplesBuffer::new(2, 44100, v.clone()));
let src = SamplesBuffer::new(2, 44100, v.clone()).convert_samples();
let mut src = src.amplify(0.5);
sink.set_volume(0.5);
for _ in 0..v.len() {
assert_eq!(queue_rx.next(), src.next());
}
}
}
-103
View File
@@ -1,103 +0,0 @@
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// Internal function that builds a `Amplify` object.
pub fn amplify<I>(input: I, factor: f32) -> Amplify<I>
where
I: Source,
I::Item: Sample,
{
Amplify { input, factor }
}
/// Filter that modifies each sample by a given value.
#[derive(Clone, Debug)]
pub struct Amplify<I> {
input: I,
factor: f32,
}
impl<I> Amplify<I> {
/// Modifies the amplification factor.
#[inline]
pub fn set_factor(&mut self, factor: f32) {
self.factor = factor;
}
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Iterator for Amplify<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
self.input.next().map(|value| value.amplify(self.factor))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> ExactSizeIterator for Amplify<I>
where
I: Source + ExactSizeIterator,
I::Item: Sample,
{
}
impl<I> Source for Amplify<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)
}
}
-251
View File
@@ -1,251 +0,0 @@
use std::f32::consts::PI;
use std::time::Duration;
use crate::Source;
use super::SeekError;
// Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
/// Internal function that builds a `BltFilter` object.
pub fn low_pass<I>(input: I, freq: u32) -> BltFilter<I>
where
I: Source<Item = f32>,
{
low_pass_with_q(input, freq, 0.5)
}
pub fn high_pass<I>(input: I, freq: u32) -> BltFilter<I>
where
I: Source<Item = f32>,
{
high_pass_with_q(input, freq, 0.5)
}
/// Same as low_pass but allows the q value (bandwidth) to be changed
pub fn low_pass_with_q<I>(input: I, freq: u32, q: f32) -> BltFilter<I>
where
I: Source<Item = f32>,
{
BltFilter {
input,
formula: BltFormula::LowPass { freq, q },
applier: None,
x_n1: 0.0,
x_n2: 0.0,
y_n1: 0.0,
y_n2: 0.0,
}
}
/// Same as high_pass but allows the q value (bandwidth) to be changed
pub fn high_pass_with_q<I>(input: I, freq: u32, q: f32) -> BltFilter<I>
where
I: Source<Item = f32>,
{
BltFilter {
input,
formula: BltFormula::HighPass { freq, q },
applier: None,
x_n1: 0.0,
x_n2: 0.0,
y_n1: 0.0,
y_n2: 0.0,
}
}
#[derive(Clone, Debug)]
pub struct BltFilter<I> {
input: I,
formula: BltFormula,
applier: Option<BltApplier>,
x_n1: f32,
x_n2: f32,
y_n1: f32,
y_n2: f32,
}
impl<I> BltFilter<I> {
/// Modifies this filter so that it becomes a low-pass filter.
pub fn to_low_pass(&mut self, freq: u32) {
self.to_low_pass_with_q(freq, 0.5);
}
/// Modifies this filter so that it becomes a high-pass filter
pub fn to_high_pass(&mut self, freq: u32) {
self.to_high_pass_with_q(freq, 0.5);
}
/// Same as to_low_pass but allows the q value (bandwidth) to be changed
pub fn to_low_pass_with_q(&mut self, freq: u32, q: f32) {
self.formula = BltFormula::LowPass { freq, q };
self.applier = None;
}
/// Same as to_high_pass but allows the q value (bandwidth) to be changed
pub fn to_high_pass_with_q(&mut self, freq: u32, q: f32) {
self.formula = BltFormula::HighPass { freq, q };
self.applier = None;
}
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Iterator for BltFilter<I>
where
I: Source<Item = f32>,
{
type Item = f32;
#[inline]
fn next(&mut self) -> Option<f32> {
let last_in_frame = self.input.current_frame_len() == Some(1);
if self.applier.is_none() {
self.applier = Some(self.formula.to_applier(self.input.sample_rate()));
}
let sample = match self.input.next() {
None => return None,
Some(s) => s,
};
let result = self
.applier
.as_ref()
.unwrap()
.apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2);
self.y_n2 = self.y_n1;
self.x_n2 = self.x_n1;
self.y_n1 = result;
self.x_n1 = sample;
if last_in_frame {
self.applier = None;
}
Some(result)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> ExactSizeIterator for BltFilter<I> where I: Source<Item = f32> + ExactSizeIterator {}
impl<I> Source for BltFilter<I>
where
I: Source<Item = f32>,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)
}
}
#[derive(Clone, Debug)]
enum BltFormula {
LowPass { freq: u32, q: f32 },
HighPass { freq: u32, q: f32 },
}
impl BltFormula {
fn to_applier(&self, sampling_frequency: u32) -> BltApplier {
match *self {
BltFormula::LowPass { freq, q } => {
let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
let alpha = w0.sin() / (2.0 * q);
let b1 = 1.0 - w0.cos();
let b0 = b1 / 2.0;
let b2 = b0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * w0.cos();
let a2 = 1.0 - alpha;
BltApplier {
b0: b0 / a0,
b1: b1 / a0,
b2: b2 / a0,
a1: a1 / a0,
a2: a2 / a0,
}
}
BltFormula::HighPass { freq, q } => {
let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
let cos_w0 = w0.cos();
let alpha = w0.sin() / (2.0 * q);
let b0 = (1.0 + cos_w0) / 2.0;
let b1 = -1.0 - cos_w0;
let b2 = b0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * cos_w0;
let a2 = 1.0 - alpha;
BltApplier {
b0: b0 / a0,
b1: b1 / a0,
b2: b2 / a0,
a1: a1 / a0,
a2: a2 / a0,
}
}
}
}
}
#[derive(Clone, Debug)]
struct BltApplier {
b0: f32,
b1: f32,
b2: f32,
a1: f32,
a2: f32,
}
impl BltApplier {
#[inline]
fn apply(&self, x_n: f32, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32) -> f32 {
self.b0 * x_n + self.b1 * x_n1 + self.b2 * x_n2 - self.a1 * y_n1 - self.a2 * y_n2
}
}
-268
View File
@@ -1,268 +0,0 @@
use std::cmp;
use std::mem;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// Internal function that builds a `Buffered` object.
#[inline]
pub fn buffered<I>(input: I) -> Buffered<I>
where
I: Source,
I::Item: Sample,
{
let total_duration = input.total_duration();
let first_frame = extract(input);
Buffered {
current_frame: first_frame,
position_in_frame: 0,
total_duration,
}
}
/// Iterator that at the same time extracts data from the iterator and stores it in a buffer.
pub struct Buffered<I>
where
I: Source,
I::Item: Sample,
{
/// Immutable reference to the next frame of data. Cannot be `Frame::Input`.
current_frame: Arc<Frame<I>>,
/// The position in number of samples of this iterator inside `current_frame`.
position_in_frame: usize,
/// Obtained once at creation and never modified again.
total_duration: Option<Duration>,
}
enum Frame<I>
where
I: Source,
I::Item: Sample,
{
/// Data that has already been extracted from the iterator. Also contains a pointer to the
/// next frame.
Data(FrameData<I>),
/// No more data.
End,
/// Unextracted data. The `Option` should never be `None` and is only here for easier data
/// processing.
Input(Mutex<Option<I>>),
}
struct FrameData<I>
where
I: Source,
I::Item: Sample,
{
data: Vec<I::Item>,
channels: u16,
rate: u32,
next: Mutex<Arc<Frame<I>>>,
}
impl<I> Drop for FrameData<I>
where
I: Source,
I::Item: Sample,
{
fn drop(&mut self) {
// This is necessary to prevent stack overflows deallocating long chains of the mutually
// recursive `Frame` and `FrameData` types. This iteratively traverses as much of the
// chain as needs to be deallocated, and repeatedly "pops" the head off the list. This
// solves the problem, as when the time comes to actually deallocate the `FrameData`,
// the `next` field will contain a `Frame::End`, or an `Arc` with additional references,
// so the depth of recursive drops will be bounded.
while let Ok(arc_next) = self.next.get_mut() {
if let Some(next_ref) = Arc::get_mut(arc_next) {
// This allows us to own the next Frame.
let next = mem::replace(next_ref, Frame::End);
if let Frame::Data(next_data) = next {
// Swap the current FrameData with the next one, allowing the current one
// to go out of scope.
*self = next_data;
} else {
break;
}
} else {
break;
}
}
}
}
/// Builds a frame from the input iterator.
fn extract<I>(mut input: I) -> Arc<Frame<I>>
where
I: Source,
I::Item: Sample,
{
let frame_len = input.current_frame_len();
if frame_len == Some(0) {
return Arc::new(Frame::End);
}
let channels = input.channels();
let rate = input.sample_rate();
let data: Vec<I::Item> = input
.by_ref()
.take(cmp::min(frame_len.unwrap_or(32768), 32768))
.collect();
if data.is_empty() {
return Arc::new(Frame::End);
}
Arc::new(Frame::Data(FrameData {
data,
channels,
rate,
next: Mutex::new(Arc::new(Frame::Input(Mutex::new(Some(input))))),
}))
}
impl<I> Buffered<I>
where
I: Source,
I::Item: Sample,
{
/// Advances to the next frame.
fn next_frame(&mut self) {
let next_frame = {
let mut next_frame_ptr = match &*self.current_frame {
Frame::Data(FrameData { next, .. }) => next.lock().unwrap(),
_ => unreachable!(),
};
let next_frame = match &**next_frame_ptr {
Frame::Data(_) => next_frame_ptr.clone(),
Frame::End => next_frame_ptr.clone(),
Frame::Input(input) => {
let input = input.lock().unwrap().take().unwrap();
extract(input)
}
};
*next_frame_ptr = next_frame.clone();
next_frame
};
self.current_frame = next_frame;
self.position_in_frame = 0;
}
}
impl<I> Iterator for Buffered<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
let current_sample;
let advance_frame;
match &*self.current_frame {
Frame::Data(FrameData { data, .. }) => {
current_sample = Some(data[self.position_in_frame]);
self.position_in_frame += 1;
advance_frame = self.position_in_frame >= data.len();
}
Frame::End => {
current_sample = None;
advance_frame = false;
}
Frame::Input(_) => unreachable!(),
};
if advance_frame {
self.next_frame();
}
current_sample
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
// TODO:
(0, None)
}
}
// TODO: uncomment when `size_hint` is fixed
/*impl<I> ExactSizeIterator for Amplify<I> where I: Source + ExactSizeIterator, I::Item: Sample {
}*/
impl<I> Source for Buffered<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
match &*self.current_frame {
Frame::Data(FrameData { data, .. }) => Some(data.len() - self.position_in_frame),
Frame::End => Some(0),
Frame::Input(_) => unreachable!(),
}
}
#[inline]
fn channels(&self) -> u16 {
match *self.current_frame {
Frame::Data(FrameData { channels, .. }) => channels,
Frame::End => 1,
Frame::Input(_) => unreachable!(),
}
}
#[inline]
fn sample_rate(&self) -> u32 {
match *self.current_frame {
Frame::Data(FrameData { rate, .. }) => rate,
Frame::End => 44100,
Frame::Input(_) => unreachable!(),
}
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.total_duration
}
/// Can not support seek, in the end state we lose the underlying source
/// which makes seeking back impossible.
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
impl<I> Clone for Buffered<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn clone(&self) -> Buffered<I> {
Buffered {
current_frame: self.current_frame.clone(),
position_in_frame: self.position_in_frame,
total_duration: self.total_duration,
}
}
}
-148
View File
@@ -1,148 +0,0 @@
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// Combines channels in input into a single mono source, then plays that mono sound
/// to each channel at the volume given for that channel.
#[derive(Clone, Debug)]
pub struct ChannelVolume<I>
where
I: Source,
I::Item: Sample,
{
input: I,
// Channel number is used as index for amplification value.
channel_volumes: Vec<f32>,
// Current listener being processed.
current_channel: usize,
current_sample: Option<I::Item>,
}
impl<I> ChannelVolume<I>
where
I: Source,
I::Item: Sample,
{
pub fn new(mut input: I, channel_volumes: Vec<f32>) -> ChannelVolume<I>
where
I: Source,
I::Item: Sample,
{
let mut sample = None;
for _ in 0..input.channels() {
if let Some(s) = input.next() {
sample = Some(
sample
.get_or_insert_with(I::Item::zero_value)
.saturating_add(s),
);
}
}
ChannelVolume {
input,
channel_volumes,
current_channel: 0,
current_sample: sample,
}
}
/// Sets the volume for a given channel number. Will panic if channel number
/// was invalid.
pub fn set_volume(&mut self, channel: usize, volume: f32) {
self.channel_volumes[channel] = volume;
}
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Iterator for ChannelVolume<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
// return value
let ret = self
.current_sample
.map(|sample| sample.amplify(self.channel_volumes[self.current_channel]));
self.current_channel += 1;
if self.current_channel >= self.channel_volumes.len() {
self.current_channel = 0;
self.current_sample = None;
for _ in 0..self.input.channels() {
if let Some(s) = self.input.next() {
self.current_sample = Some(
self.current_sample
.get_or_insert_with(I::Item::zero_value)
.saturating_add(s),
);
}
}
}
ret
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> ExactSizeIterator for ChannelVolume<I>
where
I: Source + ExactSizeIterator,
I::Item: Sample,
{
}
impl<I> Source for ChannelVolume<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.channel_volumes.len() as u16
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)
}
}
-69
View File
@@ -1,69 +0,0 @@
use std::time::Duration;
use cpal::FromSample;
use crate::source::{FadeIn, Mix, TakeDuration};
use crate::{Sample, Source};
/// Mixes one sound fading out with another sound fading in for the given duration.
///
/// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is returned.
pub fn crossfade<I1, I2>(
input_fadeout: I1,
input_fadein: I2,
duration: Duration,
) -> Crossfade<I1, I2>
where
I1: Source,
I2: Source,
I1::Item: FromSample<I2::Item> + Sample,
I2::Item: Sample,
{
let mut input_fadeout = input_fadeout.take_duration(duration);
input_fadeout.set_filter_fadeout();
let input_fadein = input_fadein.take_duration(duration).fade_in(duration);
input_fadeout.mix(input_fadein)
}
pub type Crossfade<I1, I2> = Mix<TakeDuration<I1>, FadeIn<TakeDuration<I2>>>;
#[cfg(test)]
mod tests {
use super::*;
use crate::buffer::SamplesBuffer;
fn dummysource(length: u8) -> SamplesBuffer<f32> {
let data: Vec<f32> = (1..=length).map(f32::from).collect();
SamplesBuffer::new(1, 1, data)
}
#[test]
fn test_crossfade() {
let source1 = dummysource(10);
let source2 = dummysource(10);
let mut mixed = crossfade(
source1,
source2,
Duration::from_secs(5) + Duration::from_nanos(1),
);
assert_eq!(mixed.next(), Some(1.0));
assert_eq!(mixed.next(), Some(2.0));
assert_eq!(mixed.next(), Some(3.0));
assert_eq!(mixed.next(), Some(4.0));
assert_eq!(mixed.next(), Some(5.0));
assert_eq!(mixed.next(), None);
let source1 = dummysource(10);
let source2 = dummysource(10).amplify(0.0);
let mut mixed = crossfade(
source1,
source2,
Duration::from_secs(5) + Duration::from_nanos(1),
);
assert_eq!(mixed.next(), Some(1.0 * 1.0));
assert_eq!(mixed.next(), Some(2.0 * 0.8));
assert_eq!(mixed.next(), Some(3.0 * 0.6));
assert_eq!(mixed.next(), Some(4.0 * 0.4));
assert_eq!(mixed.next(), Some(5.0 * 0.2));
assert_eq!(mixed.next(), None);
}
}
-138
View File
@@ -1,138 +0,0 @@
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
fn remaining_samples(until_playback: Duration, sample_rate: u32, channels: u16) -> usize {
let ns = until_playback.as_secs() * 1_000_000_000 + until_playback.subsec_nanos() as u64;
let samples = ns * channels as u64 * sample_rate as u64 / 1_000_000_000;
samples as usize
}
/// Internal function that builds a `Delay` object.
pub fn delay<I>(input: I, duration: Duration) -> Delay<I>
where
I: Source,
I::Item: Sample,
{
Delay {
remaining_samples: remaining_samples(duration, input.sample_rate(), input.channels()),
requested_duration: duration,
input,
}
}
/// A source that delays the given source by a certain amount.
#[derive(Clone, Debug)]
pub struct Delay<I> {
input: I,
remaining_samples: usize,
requested_duration: Duration,
}
impl<I> Delay<I>
where
I: Source,
I::Item: Sample,
{
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Iterator for Delay<I>
where
I: Source,
I::Item: Sample,
{
type Item = <I as Iterator>::Item;
#[inline]
fn next(&mut self) -> Option<<I as Iterator>::Item> {
if self.remaining_samples >= 1 {
self.remaining_samples -= 1;
Some(Sample::zero_value())
} else {
self.input.next()
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let (min, max) = self.input.size_hint();
(
min + self.remaining_samples,
max.map(|v| v + self.remaining_samples),
)
}
}
impl<I> Source for Delay<I>
where
I: Iterator + Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input
.current_frame_len()
.map(|val| val + self.remaining_samples)
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input
.total_duration()
.map(|val| val + self.requested_duration)
}
/// Pos is seen from the perspective of the api user.
///
/// # Example
///
/// ```ignore
/// use std::time::Duration;
///
/// let mut source = inner_source.delay(Duration::from_secs(10));
/// source.try_seek(Duration::from_secs(15));
///
/// // inner_source is now at pos: Duration::from_secs(5);
/// ```
///
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
if pos < self.requested_duration {
self.input.try_seek(Duration::ZERO)?;
let until_playback = self.requested_duration - pos;
self.remaining_samples =
remaining_samples(until_playback, self.sample_rate(), self.channels());
}
let compensated_for_delay = pos.saturating_sub(self.requested_duration);
self.input.try_seek(compensated_for_delay)
}
}
-97
View File
@@ -1,97 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// When the inner source is empty this decrements an `AtomicUsize`.
#[derive(Debug, Clone)]
pub struct Done<I> {
input: I,
signal: Arc<AtomicUsize>,
signal_sent: bool,
}
impl<I> Done<I> {
#[inline]
pub fn new(input: I, signal: Arc<AtomicUsize>) -> Done<I> {
Done {
input,
signal,
signal_sent: false,
}
}
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I: Source> Iterator for Done<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
let next = self.input.next();
if !self.signal_sent && next.is_none() {
self.signal.fetch_sub(1, Ordering::Relaxed);
self.signal_sent = true;
}
next
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> Source for Done<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)
}
}
-65
View File
@@ -1,65 +0,0 @@
use std::marker::PhantomData;
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// An empty source.
#[derive(Debug, Copy, Clone)]
pub struct Empty<S>(PhantomData<S>);
impl<S> Default for Empty<S> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<S> Empty<S> {
#[inline]
pub fn new() -> Empty<S> {
Empty(PhantomData)
}
}
impl<S> Iterator for Empty<S> {
type Item = S;
#[inline]
fn next(&mut self) -> Option<S> {
None
}
}
impl<S> Source for Empty<S>
where
S: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
1
}
#[inline]
fn sample_rate(&self) -> u32 {
48000
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
Some(Duration::new(0, 0))
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
-64
View File
@@ -1,64 +0,0 @@
use std::marker::PhantomData;
use std::time::Duration;
use crate::{Sample, Source};
use super::SeekError;
/// An empty source which executes a callback function
pub struct EmptyCallback<S> {
pub phantom_data: PhantomData<S>,
pub callback: Box<dyn Send + Fn()>,
}
impl<S> EmptyCallback<S> {
#[inline]
pub fn new(callback: Box<dyn Send + Fn()>) -> EmptyCallback<S> {
EmptyCallback {
phantom_data: PhantomData,
callback,
}
}
}
impl<S> Iterator for EmptyCallback<S> {
type Item = S;
#[inline]
fn next(&mut self) -> Option<S> {
(self.callback)();
None
}
}
impl<S> Source for EmptyCallback<S>
where
S: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
}
#[inline]
fn channels(&self) -> u16 {
1
}
#[inline]
fn sample_rate(&self) -> u32 {
48000
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
Some(Duration::new(0, 0))
}
#[inline]
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
Err(SeekError::NotSupported {
underlying_source: std::any::type_name::<Self>(),
})
}
}
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more