diff --git a/.github/workflows/cpal.yml b/.github/workflows/cpal.yml index a8130178f..765e6f09d 100644 --- a/.github/workflows/cpal.yml +++ b/.github/workflows/cpal.yml @@ -14,6 +14,8 @@ jobs: run: sudo apt-get install libasound2-dev - name: Install libjack run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0 + - name: Install dbus + run: sudo apt-get install libdbus-1-dev - name: Install stable uses: dtolnay/rust-toolchain@stable with: @@ -67,6 +69,8 @@ jobs: run: sudo apt-get install libasound2-dev - name: Install libjack run: sudo apt-get install libjack-jackd2-dev libjack-jackd2-0 + - name: Install dbus + run: sudo apt-get install libdbus-1-dev - name: Install stable uses: dtolnay/rust-toolchain@stable - name: Run without features diff --git a/Cargo.toml b/Cargo.toml index 130e36edb..de0026fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,12 +35,14 @@ windows = { version = "0.54.0", features = [ "Win32_Media_Multimedia", "Win32_UI_Shell_PropertiesSystem" ]} +audio_thread_priority = "0.33.0" 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" +audio_thread_priority = "0.33.0" jack = { version = "0.13.0", optional = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] diff --git a/README.md b/README.md index b82cbd2aa..d9f3a46cf 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ Currently, supported hosts include: - Android (via AAudio) - 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. +Note that on Linux, the ALSA development files and D-Bus development files are +required. These are provided as part of the `libasound2-dev` and `libdbus-1-dev` +packages on Debian and Ubuntu distributions and `alsa-lib-devel` and `dbus-devel` +on Fedora. ## Compiling for Web Assembly diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 027b12ca7..fd17f765e 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -10,6 +10,7 @@ use crate::{ SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; +use audio_thread_priority::promote_current_thread_to_real_time; use std::cell::Cell; use std::cmp; use std::convert::TryInto; @@ -589,6 +590,17 @@ fn input_stream_worker( error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), timeout: Option, ) { + let buffer_size = if let BufferSize::Fixed(buffer_size) = stream.conf.buffer_size { + buffer_size + } else { + // if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value + 0 + }; + + if let Err(err) = promote_current_thread_to_real_time(buffer_size, stream.conf.sample_rate.0) { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } + let mut ctxt = StreamWorkerContext::new(&timeout); loop { let flow = @@ -640,6 +652,17 @@ fn output_stream_worker( error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), timeout: Option, ) { + let buffer_size = if let BufferSize::Fixed(buffer_size) = stream.conf.buffer_size { + buffer_size + } else { + // if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value + 0 + }; + + if let Err(err) = promote_current_thread_to_real_time(buffer_size, stream.conf.sample_rate.0) { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } + let mut ctxt = StreamWorkerContext::new(&timeout); loop { let flow = diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 72c7836b2..4b41bde36 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,15 +1,15 @@ use super::windows_err_to_cpal_err; use crate::traits::StreamTrait; use crate::{ - BackendSpecificError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, StreamError, + BackendSpecificError, BufferSize, Data, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, StreamError, }; +use audio_thread_priority::promote_current_thread_to_real_time; 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; @@ -269,7 +269,18 @@ fn run_input( data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo), error_callback: &mut dyn FnMut(StreamError), ) { - boost_current_thread_priority(); + let buffer_size = if let BufferSize::Fixed(buffer_size) = run_ctxt.stream.config.buffer_size { + buffer_size + } else { + // if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value + 0 + }; + + if let Err(err) = + promote_current_thread_to_real_time(buffer_size, run_ctxt.stream.config.sample_rate.0) + { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { @@ -298,7 +309,18 @@ fn run_output( data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo), error_callback: &mut dyn FnMut(StreamError), ) { - boost_current_thread_priority(); + let buffer_size = if let BufferSize::Fixed(buffer_size) = run_ctxt.stream.config.buffer_size { + buffer_size + } else { + // if the buffer size isn't fixed, let audio_thread_priority choose a sensible default value + 0 + }; + + if let Err(err) = + promote_current_thread_to_real_time(buffer_size, run_ctxt.stream.config.sample_rate.0) + { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { @@ -322,17 +344,6 @@ fn run_output( } } -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,