Skip to content

Commit 166484e

Browse files
committed
set realtime priority for stream threads in alsa and wasapi
implements #939. As someone who doesn't know a whole lot about cpal's inner workings it looks to me like these are the only threads that the library spawns itself, leading me to think these are the only threads that require manual priority changes. However, `audio_thread_priority` claims to also support MacOS, so maybe someone could look into whether that's applicable here?
1 parent 2ea518d commit 166484e

File tree

5 files changed

+60
-19
lines changed

5 files changed

+60
-19
lines changed

Diff for: .github/workflows/cpal.yml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jobs:
1414
run: sudo apt-get install libasound2-dev
1515
- name: Install libjack
1616
run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
17+
- name: Install dbus
18+
run: sudo apt-get install libdbus-1-dev
1719
- name: Install stable
1820
uses: dtolnay/rust-toolchain@stable
1921
with:
@@ -67,6 +69,8 @@ jobs:
6769
run: sudo apt-get install libasound2-dev
6870
- name: Install libjack
6971
run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0
72+
- name: Install dbus
73+
run: sudo apt-get install libdbus-1-dev
7074
- name: Install stable
7175
uses: dtolnay/rust-toolchain@stable
7276
- name: Run without features

Diff for: Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ windows = { version = "0.54.0", features = [
3535
"Win32_Media_Multimedia",
3636
"Win32_UI_Shell_PropertiesSystem"
3737
]}
38+
audio_thread_priority = "0.33.0"
3839
asio-sys = { version = "0.2", path = "asio-sys", optional = true }
3940
num-traits = { version = "0.2.6", optional = true }
4041

4142
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
4243
alsa = "0.9"
4344
libc = "0.2"
45+
audio_thread_priority = "0.33.0"
4446
jack = { version = "0.13.0", optional = true }
4547

4648
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]

Diff for: README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ Currently, supported hosts include:
2323
- Android (via AAudio)
2424
- Emscripten
2525

26-
Note that on Linux, the ALSA development files are required. These are provided
27-
as part of the `libasound2-dev` package on Debian and Ubuntu distributions and
28-
`alsa-lib-devel` on Fedora.
26+
Note that on Linux, the ALSA development files and D-Bus development files are
27+
required. These are provided as part of the `libasound2-dev` and `libdbus-1-dev`
28+
packages on Debian and Ubuntu distributions and `alsa-lib-devel` and `dbus-devel`
29+
on Fedora.
2930

3031
## Compiling for Web Assembly
3132

Diff for: src/host/alsa/mod.rs

+23
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
1111
SupportedStreamConfigsError,
1212
};
13+
use audio_thread_priority::promote_current_thread_to_real_time;
1314
use std::cell::Cell;
1415
use std::cmp;
1516
use std::convert::TryInto;
@@ -589,6 +590,17 @@ fn input_stream_worker(
589590
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
590591
timeout: Option<Duration>,
591592
) {
593+
let buffer_size = if let BufferSize::Fixed(buffer_size) = stream.conf.buffer_size {
594+
buffer_size
595+
} else {
596+
// if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value
597+
0
598+
};
599+
600+
if let Err(err) = promote_current_thread_to_real_time(buffer_size, stream.conf.sample_rate.0) {
601+
eprintln!("Failed to promote audio thread to real-time priority: {err}");
602+
}
603+
592604
let mut ctxt = StreamWorkerContext::new(&timeout);
593605
loop {
594606
let flow =
@@ -640,6 +652,17 @@ fn output_stream_worker(
640652
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
641653
timeout: Option<Duration>,
642654
) {
655+
let buffer_size = if let BufferSize::Fixed(buffer_size) = stream.conf.buffer_size {
656+
buffer_size
657+
} else {
658+
// if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value
659+
0
660+
};
661+
662+
if let Err(err) = promote_current_thread_to_real_time(buffer_size, stream.conf.sample_rate.0) {
663+
eprintln!("Failed to promote audio thread to real-time priority: {err}");
664+
}
665+
643666
let mut ctxt = StreamWorkerContext::new(&timeout);
644667
loop {
645668
let flow =

Diff for: src/host/wasapi/stream.rs

+27-16
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use super::windows_err_to_cpal_err;
22
use crate::traits::StreamTrait;
33
use crate::{
4-
BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
5-
PlayStreamError, SampleFormat, StreamError,
4+
BackendSpecificError, BufferSize, Data, InputCallbackInfo, OutputCallbackInfo,
5+
PauseStreamError, PlayStreamError, SampleFormat, StreamError,
66
};
7+
use audio_thread_priority::promote_current_thread_to_real_time;
78
use std::mem;
89
use std::ptr;
910
use std::sync::mpsc::{channel, Receiver, SendError, Sender};
1011
use std::thread::{self, JoinHandle};
1112
use windows::Win32::Foundation;
12-
use windows::Win32::Foundation::HANDLE;
1313
use windows::Win32::Foundation::WAIT_OBJECT_0;
1414
use windows::Win32::Media::Audio;
1515
use windows::Win32::System::SystemServices;
@@ -269,7 +269,18 @@ fn run_input(
269269
data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo),
270270
error_callback: &mut dyn FnMut(StreamError),
271271
) {
272-
boost_current_thread_priority();
272+
let buffer_size = if let BufferSize::Fixed(buffer_size) = run_ctxt.stream.config.buffer_size {
273+
buffer_size
274+
} else {
275+
// if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value
276+
0
277+
};
278+
279+
if let Err(err) =
280+
promote_current_thread_to_real_time(buffer_size, run_ctxt.stream.config.sample_rate.0)
281+
{
282+
eprintln!("Failed to promote audio thread to real-time priority: {err}");
283+
}
273284

274285
loop {
275286
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
@@ -298,7 +309,18 @@ fn run_output(
298309
data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo),
299310
error_callback: &mut dyn FnMut(StreamError),
300311
) {
301-
boost_current_thread_priority();
312+
let buffer_size = if let BufferSize::Fixed(buffer_size) = run_ctxt.stream.config.buffer_size {
313+
buffer_size
314+
} else {
315+
// if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value
316+
0
317+
};
318+
319+
if let Err(err) =
320+
promote_current_thread_to_real_time(buffer_size, run_ctxt.stream.config.sample_rate.0)
321+
{
322+
eprintln!("Failed to promote audio thread to real-time priority: {err}");
323+
}
302324

303325
loop {
304326
match process_commands_and_await_signal(&mut run_ctxt, error_callback) {
@@ -322,17 +344,6 @@ fn run_output(
322344
}
323345
}
324346

325-
fn boost_current_thread_priority() {
326-
unsafe {
327-
let thread_id = Threading::GetCurrentThreadId();
328-
329-
let _ = Threading::SetThreadPriority(
330-
HANDLE(thread_id as isize),
331-
Threading::THREAD_PRIORITY_TIME_CRITICAL,
332-
);
333-
}
334-
}
335-
336347
enum ControlFlow {
337348
Break,
338349
Continue,

0 commit comments

Comments
 (0)