Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WASAPI backend #6

Merged
merged 10 commits into from
Nov 11, 2024
65 changes: 65 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ alsa = "0.9.0"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
coreaudio-rs = "0.12.0"

[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.58.0", features = [
"Win32_Media_Audio",
"Win32_Foundation",
"Win32_Devices_Properties",
"Win32_Media_KernelStreaming",
"Win32_System_Com_StructuredStorage",
"Win32_System_Threading",
"Win32_Security",
"Win32_System_SystemServices",
"Win32_System_Variant",
"Win32_Media_Multimedia",
"Win32_UI_Shell_PropertiesSystem"
]}

[[example]]
name = "enumerate_alsa"
path = "examples/enumerate_alsa.rs"

[[example]]
name = "enumerate_coreaudio"
path = "examples/enumerate_coreaudio.rs"

[[example]]
name = "enumerate_wasapi"
path = "examples/enumerate_wasapi.rs"

4 changes: 3 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ fn main() {
wasm: { any(target_os = "wasm32") },
os_alsa: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
target_os = "netbsd") },
os_coreaudio: { any (target_os = "macos", target_os = "ios") }
os_coreaudio: { any (target_os = "macos", target_os = "ios") },
os_wasapi: { target_os = "windows" },
unsupported: { not(any(os_alsa, os_coreaudio, os_wasapi))}
}
}
4 changes: 1 addition & 3 deletions examples/enumerate_alsa.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::error::Error;

mod util;

#[cfg(os_alsa)]
fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), Box<dyn std::error::Error>> {
use crate::util::enumerate::enumerate_devices;
use interflow::backends::alsa::AlsaDriver;

Expand Down
13 changes: 13 additions & 0 deletions examples/enumerate_wasapi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod util;

#[cfg(os_wasapi)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use crate::util::enumerate::enumerate_devices;
use interflow::backends::wasapi::WasapiDriver;
enumerate_devices(WasapiDriver)
}

#[cfg(not(os_wasapi))]
fn main() {
println!("WASAPI driver is not available on this platform");
}
5 changes: 2 additions & 3 deletions src/audio_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl<S: Data> AudioBufferBase<S> {
for (inp, out) in self.as_interleaved().iter().zip(output.iter_mut()) {
*out = *inp;
}
return true;
true
}
}

Expand Down Expand Up @@ -212,8 +212,7 @@ impl<S: DataMut> AudioBufferBase<S> {
pub fn channels_mut(&mut self) -> impl '_ + Iterator<Item = ArrayViewMut1<S::Elem>> {
self.storage.rows_mut().into_iter()
}

/// Return a mutable interleaved 2-D array view, where samples are in rows and channels are in
/// Return a mutable interleaved 2-D array view, where samples are in rows and channels are in
/// columns.
pub fn as_interleaved_mut(&mut self) -> ArrayViewMut2<S::Elem> {
self.storage.view_mut().reversed_axes()
Expand Down
27 changes: 10 additions & 17 deletions src/backends/alsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
use std::{borrow::Cow, ffi::CStr};
use std::borrow::Cow;

use alsa::{device_name::HintIter, pcm, PCM};
use thiserror::Error;
Expand Down Expand Up @@ -53,10 +53,6 @@ impl AudioDriver for AlsaDriver {
}

fn list_devices(&self) -> Result<impl IntoIterator<Item = Self::Device>, Self::Error> {
const C_PCM: &CStr = match CStr::from_bytes_with_nul(b"pcm\0") {
Ok(cstr) => cstr,
Err(_) => unreachable!(),
};
Ok(HintIter::new(None, c"pcm")?
.filter_map(|hint| AlsaDevice::new(hint.name.as_ref()?, hint.direction?).ok()))
}
Expand Down Expand Up @@ -210,11 +206,12 @@ impl AlsaDevice {
fn default_config(&self) -> Result<StreamConfig, AlsaError> {
let samplerate = 48000.; // Default ALSA sample rate
let channel_count = 2; // Stereo stream
let channels = 1 << channel_count - 1;
let channels = 1 << (channel_count - 1);
Ok(StreamConfig {
samplerate: samplerate as _,
channels,
buffer_size_range: (None, None),
exclusive: false,
})
}
}
Expand Down Expand Up @@ -259,6 +256,7 @@ impl<Callback: 'static + Send + AudioInputCallback> AlsaStream<Callback> {
channels: ChannelMap32::default()
.with_indices(std::iter::repeat(1).take(num_channels)),
buffer_size_range: (Some(period_size), Some(period_size)),
exclusive: false,
};
let mut timestamp = Timestamp::new(samplerate);
let mut buffer = vec![0f32; period_size * num_channels];
Expand All @@ -274,13 +272,10 @@ impl<Callback: 'static + Send + AudioInputCallback> AlsaStream<Callback> {
}
let frames = device.pcm.avail_update()? as usize;
let len = frames * num_channels;
match io.readi(&mut buffer[..len]) {
Err(err) => {
log::warn!("ALSA PCM error, trying to recover ...");
log::debug!("Error: {err}");
device.pcm.try_recover(err, true)?;
}
_ => {}
if let Err(err) = io.readi(&mut buffer[..len]) {
log::warn!("ALSA PCM error, trying to recover ...");
log::debug!("Error: {err}");
device.pcm.try_recover(err, true)?;
}
let buffer = AudioRef::from_interleaved(&buffer[..len], num_channels).unwrap();
let context = AudioCallbackContext {
Expand Down Expand Up @@ -333,6 +328,7 @@ impl<Callback: 'static + Send + AudioOutputCallback> AlsaStream<Callback> {
channels: ChannelMap32::default()
.with_indices(std::iter::repeat(1).take(num_channels)),
buffer_size_range: (Some(period_size), Some(period_size)),
exclusive: false,
};
let frames = device.pcm.avail_update()? as usize;
let mut timestamp = Timestamp::new(samplerate);
Expand All @@ -358,10 +354,7 @@ impl<Callback: 'static + Send + AudioOutputCallback> AlsaStream<Callback> {
};
callback.on_output_data(context, input);
timestamp += frames as u64;
match io.writei(&buffer[..len]) {
Err(err) => device.pcm.try_recover(err, true)?,
_ => {}
}
if let Err(err) = io.writei(&buffer[..len]) { device.pcm.try_recover(err, true)? }
match device.pcm.state() {
pcm::State::Suspended => {
if hwp.can_resume() {
Expand Down
12 changes: 10 additions & 2 deletions src/backends/coreaudio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,18 @@ impl AudioDevice for CoreAudioDevice {
.iter()
.copied()
.filter(move |sr| samplerate_range.contains(sr))
.map(move |sr| {
.flat_map(move |sr| {
[false, true]
.into_iter()
.map(move |exclusive| (sr, exclusive))
})
.map(move |(samplerate, exclusive)| {
let channels = 1 << asbd.mFormat.mChannelsPerFrame as u32 - 1;
StreamConfig {
samplerate: sr,
samplerate,
channels,
buffer_size_range: (None, None),
exclusive,
}
})
}))
Expand Down Expand Up @@ -195,6 +201,7 @@ impl AudioInputDevice for CoreAudioDevice {
channels: 0b1, // Hardcoded to mono on non-interleaved inputs
samplerate,
buffer_size_range: (None, None),
exclusive: false,
})
}

Expand Down Expand Up @@ -226,6 +233,7 @@ impl AudioOutputDevice for CoreAudioDevice {
samplerate,
buffer_size_range: (None, None),
channels: 0b11,
exclusive: false,
})
}

Expand Down
25 changes: 24 additions & 1 deletion src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
//!
//! Each backend is provided in its own submodule. Types should be public so that the user isn't
//! limited to going through the main API if they want to choose a specific backend.
use crate::{AudioDriver, AudioInputDevice, AudioOutputDevice, DeviceType};

use crate::{
AudioDriver, AudioInputDevice, AudioOutputDevice, DeviceType,
};

#[cfg(unsupported)]
compile_error!("Unsupported platform (supports ALSA, CoreAudio, and WASAPI)");

#[cfg(os_alsa)]
pub mod alsa;

#[cfg(os_coreaudio)]
pub mod coreaudio;

#[cfg(os_wasapi)]
pub mod wasapi;

/// Returns the default driver.
///
/// "Default" here means that it is a supported driver that is available on the platform.
Expand All @@ -25,11 +34,17 @@ pub mod coreaudio;
/// | **Platform** | **Driver** |
/// |:------------:|:----------:|
/// | Linux | ALSA |
/// | macOS | CoreAudio |
/// | Windows | WASAPI |
#[cfg(any(os_alsa, os_coreaudio, os_wasapi))]
#[allow(clippy::needless_return)]
pub fn default_driver() -> impl AudioDriver {
#[cfg(os_alsa)]
return alsa::AlsaDriver;
#[cfg(os_coreaudio)]
return coreaudio::CoreAudioDriver;
#[cfg(os_wasapi)]
return wasapi::WasapiDriver;
}

/// Returns the default input device for the given audio driver.
Expand All @@ -51,11 +66,15 @@ where
/// "Default" here means both in terms of platform support but also can include runtime selection.
/// Therefore, it is better to use this method directly rather than first getting the default
/// driver from [`default_driver`].
#[cfg(any(os_alsa, os_coreaudio, os_wasapi))]
#[allow(clippy::needless_return)]
pub fn default_input_device() -> impl AudioInputDevice {
#[cfg(os_alsa)]
return default_input_device_from(&alsa::AlsaDriver);
#[cfg(os_coreaudio)]
return default_input_device_from(&coreaudio::CoreAudioDriver);
#[cfg(os_wasapi)]
return default_input_device_from(&wasapi::WasapiDriver);
}

/// Returns the default input device for the given audio driver.
Expand All @@ -77,9 +96,13 @@ where
/// "Default" here means both in terms of platform support but also can include runtime selection.
/// Therefore, it is better to use this method directly rather than first getting the default
/// driver from [`default_driver`].
#[cfg(any(os_alsa, os_coreaudio, os_wasapi))]
#[allow(clippy::needless_return)]
pub fn default_output_device() -> impl AudioOutputDevice {
#[cfg(os_alsa)]
return default_output_device_from(&alsa::AlsaDriver);
#[cfg(os_coreaudio)]
return default_output_device_from(&coreaudio::CoreAudioDriver);
#[cfg(os_wasapi)]
return default_output_device_from(&wasapi::WasapiDriver);
}
Loading
Loading