Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions .github/workflows/platforms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ env:
MSRV_WASM: "1.85"
MSRV_WINDOWS: "1.85"

PACKAGES_LINUX: libasound2-dev libjack-jackd2-dev libdbus-1-dev libpipewire-0.3-dev
PACKAGES_LINUX: libasound2-dev libdbus-1-dev libjack-jackd2-dev libpipewire-0.3-dev

# System packages required by the docs.rs feature set and available on docs.rs build environment.
PACKAGES_DOCS_RS: libasound2-dev libjack-jackd2-dev libdbus-1-dev
PACKAGES_DOCS_RS: libasound2-dev libdbus-1-dev libjack-jackd2-dev

ANDROID_COMPILE_SDK: "30"
ANDROID_BUILD_TOOLS: "30.0.3"
Expand Down Expand Up @@ -180,6 +180,41 @@ jobs:
- name: Check PipeWire on Debian Bookworm
run: cargo check --features pipewire --workspace --verbose

# MIPS (Tier 3 target, requires nightly + build-std; no native 64-bit atomics)
mipsel:
runs-on: ubuntu-latest
container:
image: buildpack-deps:bookworm
env:
TARGET: mipsel-unknown-linux-gnu
PKG_CONFIG_ALLOW_CROSS: "1"
PKG_CONFIG_PATH: /usr/lib/mipsel-linux-gnu/pkgconfig/
CARGO_TARGET_MIPSEL_UNKNOWN_LINUX_GNU_LINKER: mipsel-linux-gnu-gcc
steps:
- uses: actions/checkout@v5

- name: Install mipsel cross-toolchain and audio libs
run: |
dpkg --add-architecture mipsel
apt-get update -qq
apt-get install -y --no-install-recommends libclang-dev crossbuild-essential-mipsel $(for p in ${{ env.PACKAGES_LINUX }}; do echo "$p:mipsel"; done)

- name: Install Rust nightly (for build-std)
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
key: mipsel

- name: Check examples (default features)
run: cargo +nightly check --examples --workspace --verbose --target ${{ env.TARGET }} -Z build-std

- name: Check examples (all features)
run: cargo +nightly check --examples --all-features --workspace --verbose --target ${{ env.TARGET }} -Z build-std

# Windows (x86_64 and i686)
windows:
strategy:
Expand Down Expand Up @@ -577,18 +612,19 @@ jobs:
publish-cpal:
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v')
needs:
- android
- docs-cpal
- ios
- linux
- linux-armv7
- pipewire-bookworm
- windows
- macos
- android
- ios
- mipsel
- pipewire-bookworm
- tvos
- wasm-bindgen
- wasm-audioworklet
- wasm-bindgen
- wasm-wasip1
- docs-cpal
- windows
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **JACK**: Channel enumeration is no longer capped at the physical system port count.
- **PipeWire**: Fix streams starting audio before `play()` is called.
- **PipeWire**: Streams for a specific device no longer auto-reroute if it disappears.
- **PipeWire**: Build on 32-bit targets without native 64-bit atomics.
- **PulseAudio**: `NoData` errors are no longer misreported as buffer xruns.
- **PulseAudio**: Build on 32-bit targets without native 64-bit atomics.
- **visionOS**: The CoreAudio backend now builds.
- **WASAPI**: Default device changes no longer report `DeviceChanged`, which wrongly implied the stream had rerouted automatically.
- **WASAPI**: Reported buffer sizes are no longer off by one frame.
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pipewire = ["dep:pipewire"]
# Provides audio I/O support on Linux and some BSDs using the PulseAudio sound server
# Requires: PulseAudio server and client libraries installed on the system
# Platform: Linux, DragonFly BSD, FreeBSD, NetBSD
pulseaudio = ["dep:pulseaudio", "dep:futures"]
pulseaudio = ["dep:pulseaudio", "dep:futures", "dep:portable-atomic"]

# WebAssembly backend using wasm-bindgen
# Enables the Web Audio API backend for browser-based audio
Expand All @@ -91,6 +91,11 @@ wasm-bindgen = [
[dependencies]
dasp_sample = "0.11"

# Polyfills 64-bit atomics on targets without native support (e.g. mips, powerpc,
# riscv32); used by the pulseaudio backend's lock-free latency tracking.
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic = { version = "1", optional = true }

[dev-dependencies]
anyhow = "1.0"
hound = "3.5"
Expand Down
14 changes: 7 additions & 7 deletions src/host/pipewire/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
hash::{Hash, Hasher},
rc::Rc,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
atomic::{AtomicBool, AtomicU32, Ordering},
mpsc, Arc,
},
thread,
Expand Down Expand Up @@ -369,10 +369,10 @@ impl DeviceTrait for Device {
let device = self.clone();
let wait_timeout = timeout.unwrap_or(Duration::from_secs(2));
let initial_quantum = match config.buffer_size {
BufferSize::Fixed(n) => n as u64,
BufferSize::Default => self.quantum as u64,
BufferSize::Fixed(n) => n,
BufferSize::Default => self.quantum,
};
let last_quantum = Arc::new(AtomicU64::new(initial_quantum));
let last_quantum = Arc::new(AtomicU32::new(initial_quantum));
let last_quantum_clone = last_quantum.clone();
// Keep `capture` monotonic: pw_time delay() grows when another client joins
// needing a larger buffer, which can pull `capture` backward.
Expand Down Expand Up @@ -547,10 +547,10 @@ impl DeviceTrait for Device {
let device = self.clone();
let wait_timeout = timeout.unwrap_or(Duration::from_secs(2));
let initial_quantum = match config.buffer_size {
BufferSize::Fixed(n) => n as u64,
BufferSize::Default => self.quantum as u64,
BufferSize::Fixed(n) => n,
BufferSize::Default => self.quantum,
};
let last_quantum = Arc::new(AtomicU64::new(initial_quantum));
let last_quantum = Arc::new(AtomicU32::new(initial_quantum));
let last_quantum_clone = last_quantum.clone();
// Keep `playback` monotonic: pw_time delay() shrinks when other clients that needed
// a larger buffer leave the graph, which can pull `playback` backward.
Expand Down
22 changes: 11 additions & 11 deletions src/host/pipewire/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{
cell::RefCell,
rc::Rc,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
atomic::{AtomicBool, AtomicU32, Ordering},
Arc, Mutex,
},
thread::JoinHandle,
Expand Down Expand Up @@ -81,7 +81,7 @@ pub(super) enum StreamCommand {
pub struct Stream {
handle: Option<JoinHandle<()>>,
controller: pw::channel::Sender<StreamCommand>,
last_quantum: Arc<AtomicU64>,
last_quantum: Arc<AtomicU32>,
start: Instant,
latch: Latch,
}
Expand All @@ -90,7 +90,7 @@ impl Stream {
pub(super) fn new(
handle: JoinHandle<()>,
controller: pw::channel::Sender<StreamCommand>,
last_quantum: Arc<AtomicU64>,
last_quantum: Arc<AtomicU32>,
start: Instant,
latch: Latch,
) -> Self {
Expand Down Expand Up @@ -224,7 +224,7 @@ pub struct UserData<D> {
error_callback: ErrorCallbackArc,
sample_format: SampleFormat,
format: AudioInfoRaw,
last_quantum: Arc<AtomicU64>,
last_quantum: Arc<AtomicU32>,
start: Instant,
is_default_device: bool,
has_connected: bool,
Expand Down Expand Up @@ -311,14 +311,14 @@ where
fn publish_data_in(&mut self, stream: &pw::stream::Stream, frames: usize, data: &Data) {
#[cfg(feature = "realtime")]
{
let prev = self.last_quantum.swap(frames as u64, Ordering::Relaxed);
if !self.rt_promoted || frames as u64 != prev {
let prev = self.last_quantum.swap(frames as u32, Ordering::Relaxed);
if !self.rt_promoted || frames as u32 != prev {
self.promote_realtime(frames as FrameCount);
}
}

#[cfg(not(feature = "realtime"))]
self.last_quantum.store(frames as u64, Ordering::Relaxed);
self.last_quantum.store(frames as u32, Ordering::Relaxed);

let (callback, capture) = match pw_stream_time(stream) {
Some(t) => {
Expand Down Expand Up @@ -352,14 +352,14 @@ where
fn publish_data_out(&mut self, stream: &pw::stream::Stream, frames: usize, data: &mut Data) {
#[cfg(feature = "realtime")]
{
let prev = self.last_quantum.swap(frames as u64, Ordering::Relaxed);
if !self.rt_promoted || frames as u64 != prev {
let prev = self.last_quantum.swap(frames as u32, Ordering::Relaxed);
if !self.rt_promoted || frames as u32 != prev {
self.promote_realtime(frames as FrameCount);
}
}

#[cfg(not(feature = "realtime"))]
self.last_quantum.store(frames as u64, Ordering::Relaxed);
self.last_quantum.store(frames as u32, Ordering::Relaxed);

let (callback, playback) = match pw_stream_time(stream) {
Some(t) => {
Expand Down Expand Up @@ -539,7 +539,7 @@ pub struct ConnectParams {
pub config: StreamConfig,
pub properties: PropertiesBox,
pub sample_format: SampleFormat,
pub last_quantum: Arc<AtomicU64>,
pub last_quantum: Arc<AtomicU32>,
pub start: Instant,
pub connect_automatically: bool,
pub is_default_device: bool,
Expand Down
7 changes: 6 additions & 1 deletion src/host/pulseaudio/stream.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::{
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex,
},
time::{Duration, Instant},
};

#[cfg(not(target_has_atomic = "64"))]
use portable_atomic::AtomicU64;
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::AtomicU64;

use futures::executor::block_on;
use futures::FutureExt as _;
use pulseaudio::{protocol, AsPlaybackSource};
Expand Down
Loading