Skip to content

Commit f2f92ad

Browse files
authored
feat: PulseAudio support (#957)
1 parent 893b683 commit f2f92ad

11 files changed

Lines changed: 834 additions & 121 deletions

File tree

.github/actions/determine-msrv/action.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ inputs:
88
jack-msrv:
99
description: The MSRV for JACK backend
1010
required: true
11+
pulseaudio-msrv:
12+
description: The MSRV for PulseAudio backend (optional, Linux only)
13+
required: false
14+
default: ''
1115

1216
outputs:
1317
all-features:
@@ -23,7 +27,12 @@ runs:
2327
run: |
2428
PLATFORM_MSRV="${{ inputs.platform-msrv }}"
2529
JACK_MSRV="${{ inputs.jack-msrv }}"
30+
PULSEAUDIO_MSRV="${{ inputs.pulseaudio-msrv }}"
2631
# Use sort -V to find the maximum version
27-
MAX_MSRV=$(printf '%s\n' "$PLATFORM_MSRV" "$JACK_MSRV" | sort -V | tail -n1)
32+
VERSIONS="$PLATFORM_MSRV $JACK_MSRV"
33+
if [ -n "$PULSEAUDIO_MSRV" ]; then
34+
VERSIONS="$VERSIONS $PULSEAUDIO_MSRV"
35+
fi
36+
MAX_MSRV=$(printf '%s\n' $VERSIONS | sort -V | tail -n1)
2837
echo "all-features=$MAX_MSRV" >> $GITHUB_OUTPUT
29-
echo "Platform MSRV: $PLATFORM_MSRV, JACK MSRV: $JACK_MSRV, Using for --all-features: $MAX_MSRV"
38+
echo "Platform MSRV: $PLATFORM_MSRV, JACK MSRV: $JACK_MSRV, PulseAudio MSRV: $PULSEAUDIO_MSRV, Using for --all-features: $MAX_MSRV"

.github/workflows/platforms.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ env:
2828
MSRV_ALSA: "1.82"
2929
MSRV_COREAUDIO: "1.80"
3030
MSRV_JACK: "1.82"
31+
MSRV_PULSEAUDIO: "1.88"
3132
MSRV_WASIP1: "1.78"
3233
MSRV_WASM: "1.82"
3334
MSRV_WINDOWS: "1.82"
@@ -64,6 +65,7 @@ jobs:
6465
with:
6566
platform-msrv: ${{ env.MSRV_ALSA }}
6667
jack-msrv: ${{ env.MSRV_JACK }}
68+
pulseaudio-msrv: ${{ env.MSRV_PULSEAUDIO }}
6769

6870
- name: Install Rust MSRV (${{ env.MSRV_ALSA }})
6971
uses: dtolnay/rust-toolchain@master
@@ -113,6 +115,7 @@ jobs:
113115
with:
114116
platform-msrv: ${{ env.MSRV_ALSA }}
115117
jack-msrv: ${{ env.MSRV_JACK }}
118+
pulseaudio-msrv: ${{ env.MSRV_PULSEAUDIO }}
116119

117120
- name: Install Rust MSRV (${{ env.MSRV_ALSA }})
118121
uses: dtolnay/rust-toolchain@master
@@ -279,12 +282,27 @@ jobs:
279282
steps:
280283
- uses: actions/checkout@v5
281284

285+
- name: Determine MSRV for all-features
286+
id: msrv
287+
uses: ./.github/actions/determine-msrv
288+
with:
289+
platform-msrv: ${{ env.MSRV_AAUDIO }}
290+
jack-msrv: ${{ env.MSRV_JACK }}
291+
pulseaudio-msrv: ${{ env.MSRV_PULSEAUDIO }}
292+
282293
- name: Install Rust MSRV (${{ env.MSRV_AAUDIO }})
283294
uses: dtolnay/rust-toolchain@master
284295
with:
285296
toolchain: ${{ env.MSRV_AAUDIO }}
286297
targets: ${{ env.TARGET }},aarch64-linux-android,i686-linux-android,x86_64-linux-android
287298

299+
- name: Install Rust MSRV (${{ steps.msrv.outputs.all-features }})
300+
if: steps.msrv.outputs.all-features != env.MSRV_AAUDIO
301+
uses: dtolnay/rust-toolchain@master
302+
with:
303+
toolchain: ${{ steps.msrv.outputs.all-features }}
304+
targets: ${{ env.TARGET }}
305+
288306
- name: Rust Cache
289307
uses: Swatinem/rust-cache@v2
290308
with:
@@ -297,7 +315,7 @@ jobs:
297315
run: cargo +${{ env.MSRV_AAUDIO }} check --examples --no-default-features --workspace --verbose --target ${{ env.TARGET }}
298316

299317
- name: Check examples (all features)
300-
run: cargo +${{ env.MSRV_AAUDIO }} check --examples --all-features --workspace --verbose --target ${{ env.TARGET }}
318+
run: cargo +${{ steps.msrv.outputs.all-features }} check --examples --all-features --workspace --verbose --target ${{ env.TARGET }}
301319

302320
- name: Check Android project
303321
working-directory: examples/android

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- `DeviceBusy` error variant to `SupportedStreamConfigsError`, `DefaultStreamConfigError`, and
1313
`BuildStreamError` for retryable device access errors (EBUSY, EAGAIN).
14+
- **PulseAudio**: New host for Linux and some BSDs using the PulseAudio API.
1415

1516
### Changed
1617

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ asio = [
1919
"dep:num-traits",
2020
]
2121

22+
# PulseAudio backend
23+
# Provides audio I/O support on Linux and some BSDs using the PulseAudio sound server
24+
# Requires: PulseAudio server and client libraries installed on the system
25+
# Platform: Linux, DragonFly BSD, FreeBSD, NetBSD
26+
pulseaudio = ["dep:pulseaudio", "dep:futures"]
27+
2228
# JACK Audio Connection Kit backend
2329
# Provides low-latency connections between applications and audio hardware
2430
# Requires: JACK server and client libraries installed on the system
@@ -89,6 +95,8 @@ alsa = "0.11"
8995
libc = "0.2"
9096
audio_thread_priority = { version = "0.34", optional = true }
9197
jack = { version = "0.13", optional = true }
98+
pulseaudio = { version = "0.3", optional = true }
99+
futures = { version = "0.3", optional = true }
92100

93101
[target.'cfg(target_vendor = "apple")'.dependencies]
94102
mach2 = "0.5"

README.md

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ Low-level library for audio input and output in pure Rust.
99

1010
The minimum Rust version required depends on which audio backend and features you're using, as each platform has different dependencies:
1111

12-
- **AAudio (Android):** Rust **1.82** (due to `ndk` crate requirements)
13-
- **ALSA (Linux/BSD):** Rust **1.82** (due to `alsa-sys` crate requirements)
14-
- **CoreAudio (macOS/iOS):** Rust **1.80** (due to `coreaudio-rs` crate requirements)
15-
- **JACK (Linux/BSD/macOS/Windows):** Rust **1.82** (due to `jack` crate requirements)
16-
- **WASAPI/ASIO (Windows):** Rust **1.82** (due to `windows` crate requirements)
17-
- **WASM (`wasm32-unknown`):** Rust **1.82** (due to `gloo` crate requirements)
18-
- **WASM (`wasm32-wasip1`):** Rust **1.78** (target stabilized in 1.78)
12+
- **AAudio (Android):** Rust **1.82**
13+
- **ALSA (Linux/BSD):** Rust **1.82**
14+
- **CoreAudio (macOS/iOS):** Rust **1.80**
15+
- **JACK (Linux/BSD/macOS/Windows):** Rust **1.82**
16+
- **PulseAudio (Linux/BSD):** Rust **1.88**
17+
- **WASAPI/ASIO (Windows):** Rust **1.82**
18+
- **WASM (`wasm32-unknown`):** Rust **1.82**
19+
- **WASM (`wasm32-wasip1`):** Rust **1.78**
1920
- **WASM (`audioworklet`):** Rust **nightly** (requires `-Zbuild-std` for atomics support)
2021

2122
## Supported Platforms
@@ -29,17 +30,20 @@ This library currently supports the following:
2930
- Get the current default input and output stream formats for a device.
3031
- Build and run input and output PCM streams on a chosen device with a given stream format.
3132

32-
Currently, supported hosts include:
33+
Currently, supported platforms include:
3334

34-
- Linux (via ALSA or JACK)
35-
- Windows (via WASAPI by default, ASIO or JACK optionally)
36-
- macOS (via CoreAudio or JACK)
37-
- iOS (via CoreAudio)
3835
- Android (via AAudio)
36+
- BSD (via ALSA by default, JACK or PulseAudio optionally)
3937
- Emscripten
38+
- iOS (via CoreAudio)
39+
- Linux (via ALSA by default, JACK or PulseAudio optionally)
40+
- macOS (via CoreAudio by default, JACK optionally)
4041
- WebAssembly (via Web Audio API or Audio Worklet)
42+
- Windows (via WASAPI by default, ASIO or JACK optionally)
4143

42-
Note that on Linux, the ALSA development files are required for building (even when using JACK). These are provided as part of the `libasound2-dev` package on Debian and Ubuntu distributions and `alsa-lib-devel` on Fedora.
44+
Note that on Linux, the ALSA development files are required for building (even when using JACK or
45+
PulseAudio). These are provided as part of the `libasound2-dev` package on Debian and Ubuntu
46+
distributions and `alsa-lib-devel` on Fedora.
4347

4448
## Compiling for WebAssembly
4549

@@ -61,6 +65,29 @@ Enables the ASIO (Audio Stream Input/Output) backend. ASIO provides low-latency
6165

6266
**Setup:** See the [ASIO setup guide](#asio-on-windows) below for detailed installation instructions.
6367

68+
### `audioworklet`
69+
70+
**Platform:** WebAssembly (wasm32-unknown-unknown)
71+
72+
Enables the Audio Worklet backend for lower-latency web audio processing compared to the default Web Audio API backend.
73+
74+
**Requirements:**
75+
- The `wasm-bindgen` feature (automatically enabled)
76+
- Build with atomics support: `RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals"`
77+
- Web server must send Cross-Origin headers for SharedArrayBuffer support
78+
79+
**Setup:** See the `audioworklet-beep` example README for complete setup instructions.
80+
81+
**Note:** Audio Worklet provides better performance than the default Web Audio API by running audio processing on a separate thread.
82+
83+
### `custom`
84+
85+
**Platform:** All platforms
86+
87+
Enables support for user-defined custom host implementations, allowing integration with audio systems not natively supported by CPAL.
88+
89+
**Usage:** See `examples/custom.rs` for implementation details.
90+
6491
### `jack`
6592

6693
**Platform:** Linux, DragonFly BSD, FreeBSD, NetBSD, macOS, Windows
@@ -74,40 +101,28 @@ Enables the JACK (JACK Audio Connection Kit) backend. JACK is an audio server pr
74101

75102
**Note:** JACK is available as an alternative backend on all supported platforms. It provides an option for pro-audio users who need JACK's routing and inter-application audio connectivity. The native backends (ALSA for Linux/BSD, WASAPI/ASIO for Windows, CoreAudio for macOS) remain the default and recommended choice for most applications.
76103

77-
### `wasm-bindgen`
104+
### `pulseaudio`
78105

79-
**Platform:** WebAssembly (wasm32-unknown-unknown)
106+
**Platform:** Linux, DragonFly BSD, FreeBSD, NetBSD
80107

81-
Enables the Web Audio API backend for browser-based audio. This is the base feature required for any WebAssembly audio support.
108+
Enables the PulseAudio backend. PulseAudio is a sound server commonly used on Linux desktops.
82109

83110
**Requirements:**
84-
- Target `wasm32-unknown-unknown`
85-
- Web browser with Web Audio API support
111+
- PulseAudio server and client libraries must be installed on the system
112+
-
113+
**Usage:** See the [beep example](examples/beep.rs) for selecting the PulseAudio host at runtime.
86114

87-
**Usage:** See the `wasm-beep` example for basic WebAssembly audio setup.
88-
89-
### `audioworklet`
115+
### `wasm-bindgen`
90116

91117
**Platform:** WebAssembly (wasm32-unknown-unknown)
92118

93-
Enables the Audio Worklet backend for lower-latency web audio processing compared to the default Web Audio API backend.
119+
Enables the Web Audio API backend for browser-based audio. This is the base feature required for any WebAssembly audio support.
94120

95121
**Requirements:**
96-
- The `wasm-bindgen` feature (automatically enabled)
97-
- Build with atomics support: `RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals"`
98-
- Web server must send Cross-Origin headers for SharedArrayBuffer support
99-
100-
**Setup:** See the `audioworklet-beep` example README for complete setup instructions.
101-
102-
**Note:** Audio Worklet provides better performance than the default Web Audio API by running audio processing on a separate thread.
103-
104-
### `custom`
105-
106-
**Platform:** All platforms
107-
108-
Enables support for user-defined custom host implementations, allowing integration with audio systems not natively supported by CPAL.
122+
- Target `wasm32-unknown-unknown`
123+
- Web browser with Web Audio API support
109124

110-
**Usage:** See `examples/custom.rs` for implementation details.
125+
**Usage:** See the `wasm-beep` example for basic WebAssembly audio setup.
111126

112127
## ASIO on Windows
113128

examples/beep.rs

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use clap::Parser;
1515
use cpal::{
1616
traits::{DeviceTrait, HostTrait, StreamTrait},
17-
FromSample, Sample, SizedSample, I24,
17+
FromSample, HostUnavailable, Sample, SizedSample, I24,
1818
};
1919

2020
#[derive(Parser, Debug)]
@@ -24,58 +24,57 @@ struct Opt {
2424
#[arg(short, long)]
2525
device: Option<String>,
2626

27-
/// Use the JACK host
28-
#[cfg(all(
29-
any(
30-
target_os = "linux",
31-
target_os = "dragonfly",
32-
target_os = "freebsd",
33-
target_os = "netbsd"
34-
),
35-
feature = "jack"
36-
))]
37-
#[arg(short, long)]
38-
#[allow(dead_code)]
27+
/// Use the JACK host. Requires `--features jack`.
28+
#[arg(long, default_value_t = false)]
3929
jack: bool,
30+
31+
/// Use the PulseAudio host. Requires `--features pulseaudio`.
32+
#[arg(long, default_value_t = false)]
33+
pulseaudio: bool,
4034
}
4135

4236
fn main() -> anyhow::Result<()> {
4337
let opt = Opt::parse();
4438

45-
// Conditionally compile with jack if the feature is specified.
46-
#[cfg(all(
47-
any(
48-
target_os = "linux",
49-
target_os = "dragonfly",
50-
target_os = "freebsd",
51-
target_os = "netbsd"
52-
),
53-
feature = "jack"
39+
// Jack/PulseAudio support must be enabled at compile time, and is
40+
// only available on some platforms.
41+
#[allow(unused_mut, unused_assignments)]
42+
let mut jack_host_id = Err(HostUnavailable);
43+
#[allow(unused_mut, unused_assignments)]
44+
let mut pulseaudio_host_id = Err(HostUnavailable);
45+
46+
#[cfg(any(
47+
target_os = "linux",
48+
target_os = "dragonfly",
49+
target_os = "freebsd",
50+
target_os = "netbsd"
5451
))]
52+
{
53+
#[cfg(feature = "jack")]
54+
{
55+
jack_host_id = Ok(cpal::HostId::Jack);
56+
}
57+
58+
#[cfg(feature = "pulseaudio")]
59+
{
60+
pulseaudio_host_id = Ok(cpal::HostId::PulseAudio);
61+
}
62+
}
63+
5564
// Manually check for flags. Can be passed through cargo with -- e.g.
5665
// cargo run --release --example beep --features jack -- --jack
5766
let host = if opt.jack {
58-
cpal::host_from_id(cpal::available_hosts()
59-
.into_iter()
60-
.find(|id| *id == cpal::HostId::Jack)
61-
.expect(
62-
"make sure --features jack is specified. only works on OSes where jack is available",
63-
)).expect("jack host unavailable")
67+
jack_host_id
68+
.and_then(cpal::host_from_id)
69+
.expect("make sure `--features jack` is specified, and the platform is supported")
70+
} else if opt.pulseaudio {
71+
pulseaudio_host_id
72+
.and_then(cpal::host_from_id)
73+
.expect("make sure `--features pulseaudio` is specified, and the platform is supported")
6474
} else {
6575
cpal::default_host()
6676
};
6777

68-
#[cfg(any(
69-
not(any(
70-
target_os = "linux",
71-
target_os = "dragonfly",
72-
target_os = "freebsd",
73-
target_os = "netbsd"
74-
)),
75-
not(feature = "jack")
76-
))]
77-
let host = cpal::default_host();
78-
7978
let device = if let Some(device) = opt.device {
8079
let id = &device.parse().expect("failed to parse device id");
8180
host.device_by_id(id)

0 commit comments

Comments
 (0)