Skip to content

Commit

Permalink
Merge pull request #6 from SolarLiner/wasapi
Browse files Browse the repository at this point in the history
WASAPI backend
  • Loading branch information
SolarLiner authored Nov 11, 2024
2 parents a26b727 + ef7aadd commit e2726b7
Show file tree
Hide file tree
Showing 19 changed files with 1,107 additions and 31 deletions.
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

0 comments on commit e2726b7

Please sign in to comment.