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..0951baa01 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 = { version = "0.33.0", optional = true } 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 = { version = "0.33.0", optional = true } jack = { version = "0.13.0", optional = true } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 5cdf06fe3..e5218b72d 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -589,6 +589,8 @@ fn input_stream_worker( error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), timeout: Option, ) { + boost_current_thread_priority(stream.conf.buffer_size, stream.conf.sample_rate); + let mut ctxt = StreamWorkerContext::new(&timeout); loop { let flow = @@ -640,6 +642,8 @@ fn output_stream_worker( error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), timeout: Option, ) { + boost_current_thread_priority(stream.conf.buffer_size, stream.conf.sample_rate); + let mut ctxt = StreamWorkerContext::new(&timeout); loop { let flow = @@ -684,6 +688,25 @@ fn output_stream_worker( } } +#[cfg(feature = "audio_thread_priority")] +fn boost_current_thread_priority(buffer_size: BufferSize, sample_rate: SampleRate) { + use audio_thread_priority::promote_current_thread_to_real_time; + + let buffer_size = if let BufferSize::Fixed(buffer_size) = 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, sample_rate.0) { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } +} + +#[cfg(not(feature = "audio_thread_priority"))] +fn boost_current_thread_priority(_: BufferSize, _: SampleRate) {} + enum PollDescriptorsFlow { Continue, Return, diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index 72c7836b2..ed572cd59 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -1,15 +1,14 @@ 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 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 +268,10 @@ fn run_input( data_callback: &mut dyn FnMut(&Data, &InputCallbackInfo), error_callback: &mut dyn FnMut(StreamError), ) { - boost_current_thread_priority(); + boost_current_thread_priority( + run_ctxt.stream.config.buffer_size, + run_ctxt.stream.config.sample_rate, + ); loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { @@ -298,7 +300,10 @@ fn run_output( data_callback: &mut dyn FnMut(&mut Data, &OutputCallbackInfo), error_callback: &mut dyn FnMut(StreamError), ) { - boost_current_thread_priority(); + boost_current_thread_priority( + run_ctxt.stream.config.buffer_size, + run_ctxt.stream.config.sample_rate, + ); loop { match process_commands_and_await_signal(&mut run_ctxt, error_callback) { @@ -322,7 +327,26 @@ fn run_output( } } -fn boost_current_thread_priority() { +#[cfg(feature = "audio_thread_priority")] +fn boost_current_thread_priority(buffer_size: BufferSize, sample_rate: crate::SampleRate) { + use audio_thread_priority::promote_current_thread_to_real_time; + + let buffer_size = if let BufferSize::Fixed(buffer_size) = 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, sample_rate.0) { + eprintln!("Failed to promote audio thread to real-time priority: {err}"); + } +} + +#[cfg(not(feature = "audio_thread_priority"))] +fn boost_current_thread_priority(_: BufferSize, _: crate::SampleRate) { + use windows::Win32::Foundation::HANDLE; + unsafe { let thread_id = Threading::GetCurrentThreadId();