From 7fe335a80ae79ee8293995ffebc4c9fc97041eb2 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sun, 16 Mar 2025 14:07:00 +0100 Subject: [PATCH 1/4] refactor: move symbols around into nested modules --- src/backends/coreaudio.rs | 8 ++++---- src/backends/wasapi/stream.rs | 6 +++--- src/duplex.rs | 5 ++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 86eca8d..bf5c6c0 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -18,15 +18,15 @@ use coreaudio::sys::{ use thiserror::Error; use crate::audio_buffer::{AudioBuffer, Sample}; -use crate::channel_map::{Bitset, ChannelMap32}; +use crate::channel_map::Bitset; use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice, Channel, DeviceType}; use crate::driver::AudioDriver; +use crate::prelude::ChannelMap32; use crate::stream::{ - AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, - AudioStreamHandle, StreamConfig, + AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; use crate::timestamp::Timestamp; -use crate::SendEverywhereButOnWeb; +use crate::{AudioInput, AudioOutput, SendEverywhereButOnWeb}; /// Type of errors from the CoreAudio backend #[derive(Debug, Error)] diff --git a/src/backends/wasapi/stream.rs b/src/backends/wasapi/stream.rs index be10dd3..f4f925b 100644 --- a/src/backends/wasapi/stream.rs +++ b/src/backends/wasapi/stream.rs @@ -2,11 +2,11 @@ use super::error; use crate::audio_buffer::{AudioMut, AudioRef}; use crate::backends::wasapi::util::WasapiMMDevice; use crate::channel_map::Bitset; +use crate::prelude::{AudioRef, Timestamp}; use crate::stream::{ - AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, - AudioStreamHandle, StreamConfig, + AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; -use crate::timestamp::Timestamp; +use crate::{AudioInput, AudioOutput}; use duplicate::duplicate_item; use std::marker::PhantomData; use std::ptr::NonNull; diff --git a/src/duplex.rs b/src/duplex.rs index ca889e6..c10784a 100644 --- a/src/duplex.rs +++ b/src/duplex.rs @@ -2,17 +2,16 @@ //! //! This module includes a proxy for gathering an input audio stream, and optionally process it to resample it to the //! output sample rate. -use crate::channel_map::Bitset; +use crate::{audio_buffer::AudioRef, channel_map::Bitset, device::AudioDevice}; use crate::device::{AudioInputDevice, AudioOutputDevice}; use crate::stream::{ AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; use crate::stream::{AudioInput, AudioOutput}; use crate::SendEverywhereButOnWeb; -use crate::{audio_buffer::AudioRef, device::AudioDevice}; -use fixed_resample::{PushStatus, ReadStatus, ResamplingChannelConfig}; use std::error::Error; use std::num::NonZeroUsize; +use fixed_resample::{PushStatus, ReadStatus, ResamplingChannelConfig}; use thiserror::Error; const MAX_CHANNELS: usize = 64; From 940127aa1da42850626c28e152b94ece4206bc0e Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 15 Mar 2025 22:46:40 +0100 Subject: [PATCH 2/4] wip: duplex mode API + implementation for ALSA --- examples/duplex.rs | 21 +++++- examples/enumerate_alsa.rs | 6 +- examples/loopback.rs | 17 +++++ examples/util/enumerate.rs | 21 ++++++ src/backends/alsa/device.rs | 117 ++++++++++++++++++++++++++++-- src/backends/alsa/duplex.rs | 139 ++++++++++++++++++++++++++++++++++++ src/backends/alsa/mod.rs | 34 ++++++++- src/backends/mod.rs | 33 +++++++++ 8 files changed, 379 insertions(+), 9 deletions(-) create mode 100644 src/backends/alsa/duplex.rs diff --git a/examples/duplex.rs b/examples/duplex.rs index 0a7ef09..76419a3 100644 --- a/examples/duplex.rs +++ b/examples/duplex.rs @@ -4,6 +4,21 @@ use interflow::{duplex::DuplexStreamConfig, prelude::*}; mod util; +#[cfg(os_alsa)] +fn main() -> Result<()> { + env_logger::init(); + + let device = default_duplex_device(); + let mut config = device.default_duplex_config().unwrap(); + config.buffer_size_range = (Some(128), Some(512)); + let stream = device.create_duplex_stream(config, RingMod::new()).unwrap(); + println!("Press Enter to stop"); + std::io::stdin().read_line(&mut String::new())?; + stream.eject().unwrap(); + Ok(()) +} + +#[cfg(not(os_alsa))] fn main() -> Result<()> { let input = default_input_device(); let output = default_output_device(); @@ -39,8 +54,12 @@ impl AudioDuplexCallback for RingMod { input: AudioInput, mut output: AudioOutput, ) { + if input.buffer.num_samples() < output.buffer.num_samples() { + log::error!("Input underrun"); + } let sr = context.stream_config.samplerate as f32; - for i in 0..output.buffer.num_samples() { + let num_samples = output.buffer.num_samples().min(input.buffer.num_samples()); + for i in 0..num_samples { let inp = input.buffer.get_frame(i)[0]; let c = self.carrier.next_sample(sr); output.buffer.set_mono(i, inp * c); diff --git a/examples/enumerate_alsa.rs b/examples/enumerate_alsa.rs index 2c38560..49e802e 100644 --- a/examples/enumerate_alsa.rs +++ b/examples/enumerate_alsa.rs @@ -1,3 +1,5 @@ +use crate::util::enumerate::enumerate_duplex_devices; + mod util; #[cfg(os_alsa)] @@ -7,7 +9,9 @@ fn main() -> Result<(), Box> { env_logger::init(); - enumerate_devices(AlsaDriver) + enumerate_devices(AlsaDriver)?; + enumerate_duplex_devices(AlsaDriver)?; + Ok(()) } #[cfg(not(os_alsa))] diff --git a/examples/loopback.rs b/examples/loopback.rs index e409726..bc8e904 100644 --- a/examples/loopback.rs +++ b/examples/loopback.rs @@ -6,6 +6,23 @@ use std::sync::Arc; mod util; +#[cfg(os_alsa)] +fn main() -> Result<()> { + env_logger::init(); + + let device = default_duplex_device(); + let mut config = device.default_duplex_config().unwrap(); + config.buffer_size_range = (Some(128), Some(512)); + let value = Arc::new(AtomicF32::new(0.0)); + let stream = device + .create_duplex_stream(config, Loopback::new(44100., value.clone())) + .unwrap(); + util::display_peakmeter(value)?; + stream.eject().unwrap(); + Ok(()) +} + +#[cfg(not(os_alsa))] fn main() -> Result<()> { env_logger::init(); diff --git a/examples/util/enumerate.rs b/examples/util/enumerate.rs index 5521497..17c0dc2 100644 --- a/examples/util/enumerate.rs +++ b/examples/util/enumerate.rs @@ -23,3 +23,24 @@ where } Ok(()) } + +pub fn enumerate_duplex_devices( + driver: Driver, +) -> Result<(), Box> +where + ::Error: 'static, +{ + eprintln!("Driver name : {}", Driver::DISPLAY_NAME); + eprintln!("Driver version: {}", driver.version()?); + if let Some(device) = driver.default_duplex_device()? { + eprintln!("Default duplex device: {}", device.name()); + } else { + eprintln!("No default duplex device"); + } + + eprintln!("All duplex devices"); + for device in driver.list_duplex_devices()? { + eprintln!("\t{}", device.name()); + } + Ok(()) +} diff --git a/src/backends/alsa/device.rs b/src/backends/alsa/device.rs index 66fc779..5b54534 100644 --- a/src/backends/alsa/device.rs +++ b/src/backends/alsa/device.rs @@ -1,4 +1,4 @@ -use crate::backends::alsa::stream::AlsaStream; +use crate::{backends::alsa::stream::AlsaStream, device::AudioDuplexDevice, duplex::AudioDuplexCallback, SendEverywhereButOnWeb}; use crate::backends::alsa::AlsaError; use crate::device::Channel; use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice, DeviceType}; @@ -16,6 +16,23 @@ pub struct AlsaDevice { pub(super) direction: alsa::Direction, } +impl AlsaDevice { + fn channel_map(&self, requested_direction: alsa::Direction) -> impl Iterator { + let max_channels = if self.direction == requested_direction { + self.pcm + .hw_params_current() + .and_then(|hwp| hwp.get_channels_max()) + .unwrap_or(0) + } else { + 0 + }; + (0..max_channels as usize).map(|i| Channel { + index: i, + name: Cow::Owned(format!("Channel {}", i)), + }) + } +} + impl fmt::Debug for AlsaDevice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AlsaDevice") @@ -110,8 +127,8 @@ impl AlsaDevice { } pub(super) fn new(name: &str, direction: alsa::Direction) -> Result { - let pcm = PCM::new(name, direction, true)?; - let pcm = Rc::new(pcm); + log::info!("Opening device: {name}, direction {direction:?}"); + let pcm = Rc::new(PCM::new(name, direction, true)?); Ok(Self { name: name.to_string(), direction, @@ -124,11 +141,12 @@ impl AlsaDevice { hwp.set_channels(config.channels as _)?; hwp.set_rate(config.samplerate as _, alsa::ValueOr::Nearest)?; if let Some(min) = config.buffer_size_range.0 { - hwp.set_buffer_size_min(min as _)?; + hwp.set_buffer_size_min(min as pcm::Frames * 2)?; } if let Some(max) = config.buffer_size_range.1 { - hwp.set_buffer_size_max(max as _)?; + hwp.set_buffer_size_max(max as pcm::Frames * 2)?; } + hwp.set_periods(2, alsa::ValueOr::Nearest)?; hwp.set_format(pcm::Format::float())?; hwp.set_access(pcm::Access::RWInterleaved)?; Ok(hwp) @@ -146,6 +164,7 @@ impl AlsaDevice { log::debug!("Apply config: hwp {hwp:#?}"); + swp.set_avail_min(hwp.get_period_size()?)?; swp.set_start_threshold(hwp.get_buffer_size()?)?; self.pcm.sw_params(&swp)?; log::debug!("Apply config: swp {swp:#?}"); @@ -153,6 +172,16 @@ impl AlsaDevice { Ok((hwp, swp, io)) } + pub(super) fn ensure_state(&self, hwp: &pcm::HwParams) -> Result { + match self.pcm.state() { + pcm::State::Suspended if hwp.can_resume() => self.pcm.resume()?, + pcm::State::Suspended => self.pcm.prepare()?, + pcm::State::Paused => return Ok(true), + _ => {} + } + Ok(false) + } + fn default_config(&self) -> Result { let samplerate = 48e3; // Default ALSA sample rate let channel_count = 2; // Stereo stream @@ -165,3 +194,81 @@ impl AlsaDevice { }) } } + +pub struct AlsaDuplexDevice { + pub(super) input: AlsaDevice, + pub(super) output: AlsaDevice, +} + +impl AudioDevice for AlsaDuplexDevice { + type Error = AlsaError; + + fn name(&self) -> Cow { + Cow::Owned(format!("{} / {}", self.input.name(), self.output.name())) + } + + fn device_type(&self) -> DeviceType { + DeviceType::Duplex + } + + fn is_config_supported(&self, config: &StreamConfig) -> bool { + let Ok((hwp, _, _)) = self.output.apply_config(config) else { + return false; + }; + let Ok(period) = hwp.get_period_size() else { + return false; + }; + let period = period as usize; + self.input + .apply_config(&StreamConfig { + buffer_size_range: (Some(period), Some(period)), + ..*config + }) + .is_ok() + } + + fn enumerate_configurations(&self) -> Option> { + Some( + self.output + .enumerate_configurations()? + .into_iter() + .filter(|config| self.is_config_supported(config)), + ) + } +} + +impl AudioDuplexDevice for AlsaDuplexDevice { + type StreamHandle = AlsaStream; + + fn default_duplex_config(&self) -> Result { + self.output.default_output_config() + } + + fn create_duplex_stream( + &self, + config: StreamConfig, + callback: Callback, + ) -> Result<::StreamHandle, Self::Error> { + AlsaStream::new_duplex( + config, + self.input.name.clone(), + self.output.name.clone(), + callback, + ) + } +} + +impl AlsaDuplexDevice { + /// Create a new duplex device from an input and output device. + pub fn new(input: AlsaDevice, output: AlsaDevice) -> Self { + Self { input, output } + } + + /// Create a full-duplex device from the given name. + pub fn full_duplex(name: &str) -> Result { + Ok(Self::new( + AlsaDevice::new(name, alsa::Direction::Capture)?, + AlsaDevice::new(name, alsa::Direction::Playback)?, + )) + } +} diff --git a/src/backends/alsa/duplex.rs b/src/backends/alsa/duplex.rs new file mode 100644 index 0000000..e428333 --- /dev/null +++ b/src/backends/alsa/duplex.rs @@ -0,0 +1,139 @@ +use crate::{audio_buffer::{AudioMut, AudioRef}, backends::alsa::AlsaError, stream::{AudioCallbackContext, AudioInput, AudioOutput, StreamConfig}}; +use crate::channel_map::{Bitset, ChannelMap32}; +use crate::duplex::AudioDuplexCallback; +use crate::prelude::alsa::device::AlsaDevice; +use crate::prelude::alsa::stream::AlsaStream; +use crate::timestamp::Timestamp; +use alsa::{pcm, PollDescriptors}; +use std::sync::Arc; +use std::time::Duration; + +impl AlsaStream { + pub fn new_duplex( + stream_config: StreamConfig, + input_name: String, + output_name: String, + mut callback: Callback, + ) -> Result { + { + let (tx, rx) = super::triggerfd::trigger()?; + let join_handle = std::thread::spawn({ + move || { + let output_device = AlsaDevice::new(&output_name, alsa::Direction::Playback)?; + let (output_hwp, _, output_io) = output_device.apply_config(&stream_config)?; + let (_, period_size) = output_device.pcm.get_params()?; + let out_periods = period_size as usize; + log::info!("[Output] Period size : {out_periods}"); + let out_channels = output_hwp.get_channels()? as usize; + log::info!("[Output] Num channels: {out_channels}"); + let out_samplerate = output_hwp.get_rate()? as f64; + log::info!("[Output] Sample rate : {out_samplerate}"); + let output_config = StreamConfig { + samplerate: out_samplerate, + channels: ChannelMap32::default() + .with_indices(std::iter::repeat(1).take(out_channels)), + buffer_size_range: (Some(out_periods), Some(out_periods)), + exclusive: false, + }; + let mut out_timestamp = Timestamp::new(out_samplerate); + let mut out_buffer = vec![0f32; out_periods * out_channels]; + let out_latency = out_periods as f64 / out_samplerate; + output_device.pcm.prepare()?; + if output_device.pcm.state() != pcm::State::Running { + output_device.pcm.start()?; + } + + let input_device = AlsaDevice::new(&input_name, alsa::Direction::Capture)?; + let (input_hwp, _, input_io) = input_device.apply_config(&output_config)?; + let (_, period_size) = input_device.pcm.get_params()?; + let in_periods = period_size as usize; + log::info!("[Input] Period size : {in_periods}"); + let in_channels = input_hwp.get_channels()? as usize; + log::info!("[Input] Num channels: {in_channels}"); + let in_samplerate = input_hwp.get_rate()? as f64; + log::info!("[Input] Sample rate : {in_samplerate}"); + let mut in_timestamp = Timestamp::new(in_samplerate); + let mut in_buffer = vec![0f32; in_periods * in_channels]; + let in_latency = in_periods as f64 / in_samplerate; + input_device.pcm.prepare()?; + if input_device.pcm.state() != pcm::State::Running { + input_device.pcm.start()?; + } + let mut poll_descriptors = { + let mut buf = vec![rx.as_pollfd()]; + let num_descriptors = input_device.pcm.count() + output_device.pcm.count(); + buf.extend( + std::iter::repeat(libc::pollfd { + fd: 0, + events: 0, + revents: 0, + }) + .take(num_descriptors), + ); + buf + }; + + let _try = || loop { + let out_frames = output_device.pcm.avail_update()? as usize; + let in_frames = input_device.pcm.avail_update()? as usize; + if out_frames == 0 && in_frames == 0 { + let latency = in_latency.min(out_latency).round() as i32; + if alsa::poll::poll(&mut poll_descriptors, latency)? > 0 { + log::debug!("Eject requested, returning ownership of callback"); + break Ok(callback); + } + continue; + } + + log::debug!("[Output] Frames available: {out_frames}"); + let out_frames = std::cmp::min(out_frames, out_periods); + let out_len = out_frames * out_channels; + let in_frames = std::cmp::min(in_frames, in_periods); + let in_len = in_frames * in_channels; + + if let Err(err) = input_io.readi(&mut in_buffer[..in_len]) { + input_device.pcm.try_recover(err, true)?; + } + + let context = AudioCallbackContext { + timestamp: out_timestamp, + stream_config: output_config, + }; + let input = AudioInput { + timestamp: in_timestamp, + buffer: AudioRef::from_interleaved(&in_buffer[..in_len], in_channels) + .unwrap(), + }; + let output = AudioOutput { + timestamp: out_timestamp, + buffer: AudioMut::from_interleaved_mut( + &mut out_buffer[..out_len], + out_channels, + ) + .unwrap(), + }; + callback.on_audio_data(context, input, output); + + if let Err(err) = output_io.writei(&out_buffer[..out_len]) { + output_device.pcm.try_recover(err, true)?; + } + + in_timestamp += in_frames as u64; + out_timestamp += out_frames as u64; + + if input_device.ensure_state(&input_hwp)? + || output_device.ensure_state(&output_hwp)? + { + std::thread::sleep(Duration::from_secs(1)); + } + }; + _try().inspect_err(|err| log::error!("Error in duplex thread: {:?}", err)) + } + }); + Ok(Self { + eject_trigger: Arc::new(tx), + join_handle, + }) + } + } +} diff --git a/src/backends/alsa/mod.rs b/src/backends/alsa/mod.rs index c92ee6d..0c21dc8 100644 --- a/src/backends/alsa/mod.rs +++ b/src/backends/alsa/mod.rs @@ -5,14 +5,15 @@ //! (PulseAudio, PipeWire) offer ALSA-compatible APIs so that older software can still access the //! audio devices through them. -use crate::device::DeviceType; +use crate::{device::DeviceType, driver::AudioDuplexDriver}; use crate::driver::AudioDriver; use alsa::device_name::HintIter; -use device::AlsaDevice; +use device::{AlsaDevice, AlsaDuplexDevice}; use std::borrow::Cow; use thiserror::Error; mod device; +mod duplex; mod input; mod output; mod stream; @@ -54,3 +55,32 @@ impl AudioDriver for AlsaDriver { .filter_map(|hint| AlsaDevice::new(hint.name.as_ref()?, hint.direction?).ok())) } } + +impl AudioDuplexDriver for AlsaDriver { + type DuplexDevice = AlsaDuplexDevice; + + fn default_duplex_device(&self) -> Result, Self::Error> { + let Some(input) = self.default_device(DeviceType::Input)? else { + return Ok(None); + }; + let Some(output) = self.default_device(DeviceType::Output)? else { + return Ok(None); + }; + Ok(Some(AlsaDuplexDevice::new(input, output))) + } + + fn list_duplex_devices( + &self, + ) -> Result, Self::Error> { + Ok(HintIter::new(None, c"pcm")? + .filter_map(|hint| AlsaDuplexDevice::full_duplex(hint.name.as_ref()?).ok())) + } + + fn device_from_input_output( + &self, + input: Self::Device, + output: Self::Device, + ) -> Result { + Ok(AlsaDuplexDevice::new(input, output)) + } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 54f21c4..89da2ce 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -113,3 +113,36 @@ pub fn default_output_device() -> impl AudioOutputDevice { #[cfg(os_wasapi)] return default_output_device_from(&wasapi::WasapiDriver); } + +/// Default duplex device from the default driver of this platform. +/// +/// "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`]. +#[allow(clippy::non_minimal_cfg)] +#[allow(clippy::needless_return)] +#[cfg(any(os_alsa))] +pub fn default_duplex_device() -> impl crate::device::AudioDuplexDevice { + #[cfg(os_alsa)] + return default_duplex_device_from(&alsa::AlsaDriver); +} + +/// Returns the default duplex device for the given audio driver. +/// +/// The default device is usually the one the user has selected in its system settings. +pub fn default_duplex_device_from(driver: &D) -> D::DuplexDevice +where + D::Device: AudioInputDevice + AudioOutputDevice, +{ + driver + .default_duplex_device() + .expect("Audio driver error") + .unwrap_or_else(|| { + driver + .device_from_input_output( + default_input_device_from(driver), + default_output_device_from(driver), + ) + .expect("Audio driver error") + }) +} From 4868c891875580f7b299fc1a80b982486a26eca1 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Fri, 4 Apr 2025 16:19:18 +0200 Subject: [PATCH 3/4] chore: improve nix shell --- flake.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index f946b41..7e70265 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,10 @@ }; devShells.default = pkgs.clangStdenv.mkDerivation { name = "interflow-devshell"; - buildInputs = buildInputs ++ nativeBuildInputs; + buildInputs = buildInputs ++ nativeBuildInputs ++ (with pkgs; [pre-commit]); + shellHook = '' + pre-commit install + ''; inherit LIBCLANG_PATH; }; } From 29dfb633b531fe3580a939a807cd2cd894e7710a Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Fri, 4 Apr 2025 16:21:33 +0200 Subject: [PATCH 4/4] refactor(api): remove device_type from the AudioDevice trait --- examples/util/enumerate.rs | 2 +- src/backends/alsa/device.rs | 16 ++++------------ src/backends/alsa/duplex.rs | 6 +++++- src/backends/alsa/mod.rs | 2 +- src/backends/coreaudio.rs | 11 ++++------- src/backends/mod.rs | 4 +++- src/backends/pipewire/device.rs | 4 ---- src/backends/wasapi/device.rs | 4 ---- src/device.rs | 3 --- src/duplex.rs | 4 ++-- 10 files changed, 20 insertions(+), 36 deletions(-) diff --git a/examples/util/enumerate.rs b/examples/util/enumerate.rs index 17c0dc2..3f37d34 100644 --- a/examples/util/enumerate.rs +++ b/examples/util/enumerate.rs @@ -19,7 +19,7 @@ where eprintln!("All devices"); for device in driver.list_devices()? { - eprintln!("\t{} ({:?})", device.name(), device.device_type()); + eprintln!("\t{}", device.name()); } Ok(()) } diff --git a/src/backends/alsa/device.rs b/src/backends/alsa/device.rs index 5b54534..f5fe38d 100644 --- a/src/backends/alsa/device.rs +++ b/src/backends/alsa/device.rs @@ -1,8 +1,11 @@ -use crate::{backends::alsa::stream::AlsaStream, device::AudioDuplexDevice, duplex::AudioDuplexCallback, SendEverywhereButOnWeb}; use crate::backends::alsa::AlsaError; use crate::device::Channel; use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice, DeviceType}; use crate::stream::{AudioInputCallback, AudioOutputCallback, StreamConfig}; +use crate::{ + backends::alsa::stream::AlsaStream, device::AudioDuplexDevice, duplex::AudioDuplexCallback, + SendEverywhereButOnWeb, +}; use alsa::{pcm, PCM}; use std::borrow::Cow; use std::fmt; @@ -49,13 +52,6 @@ impl AudioDevice for AlsaDevice { Cow::Borrowed(self.name.as_str()) } - fn device_type(&self) -> DeviceType { - match self.direction { - alsa::Direction::Playback => DeviceType::Output, - alsa::Direction::Capture => DeviceType::Input, - } - } - fn is_config_supported(&self, config: &StreamConfig) -> bool { self.get_hwp(config) .inspect_err(|err| { @@ -207,10 +203,6 @@ impl AudioDevice for AlsaDuplexDevice { Cow::Owned(format!("{} / {}", self.input.name(), self.output.name())) } - fn device_type(&self) -> DeviceType { - DeviceType::Duplex - } - fn is_config_supported(&self, config: &StreamConfig) -> bool { let Ok((hwp, _, _)) = self.output.apply_config(config) else { return false; diff --git a/src/backends/alsa/duplex.rs b/src/backends/alsa/duplex.rs index e428333..2f44d25 100644 --- a/src/backends/alsa/duplex.rs +++ b/src/backends/alsa/duplex.rs @@ -1,9 +1,13 @@ -use crate::{audio_buffer::{AudioMut, AudioRef}, backends::alsa::AlsaError, stream::{AudioCallbackContext, AudioInput, AudioOutput, StreamConfig}}; use crate::channel_map::{Bitset, ChannelMap32}; use crate::duplex::AudioDuplexCallback; use crate::prelude::alsa::device::AlsaDevice; use crate::prelude::alsa::stream::AlsaStream; use crate::timestamp::Timestamp; +use crate::{ + audio_buffer::{AudioMut, AudioRef}, + backends::alsa::AlsaError, + stream::{AudioCallbackContext, AudioInput, AudioOutput, StreamConfig}, +}; use alsa::{pcm, PollDescriptors}; use std::sync::Arc; use std::time::Duration; diff --git a/src/backends/alsa/mod.rs b/src/backends/alsa/mod.rs index 0c21dc8..ac7d3ae 100644 --- a/src/backends/alsa/mod.rs +++ b/src/backends/alsa/mod.rs @@ -5,8 +5,8 @@ //! (PulseAudio, PipeWire) offer ALSA-compatible APIs so that older software can still access the //! audio devices through them. -use crate::{device::DeviceType, driver::AudioDuplexDriver}; use crate::driver::AudioDriver; +use crate::{device::DeviceType, driver::AudioDuplexDriver}; use alsa::device_name::HintIter; use device::{AlsaDevice, AlsaDuplexDevice}; use std::borrow::Cow; diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index bf5c6c0..94e066d 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -19,14 +19,15 @@ use thiserror::Error; use crate::audio_buffer::{AudioBuffer, Sample}; use crate::channel_map::Bitset; +use crate::channel_map::ChannelMap32; use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice, Channel, DeviceType}; use crate::driver::AudioDriver; -use crate::prelude::ChannelMap32; use crate::stream::{ - AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, + AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, + AudioStreamHandle, StreamConfig, }; use crate::timestamp::Timestamp; -use crate::{AudioInput, AudioOutput, SendEverywhereButOnWeb}; +use crate::SendEverywhereButOnWeb; /// Type of errors from the CoreAudio backend #[derive(Debug, Error)] @@ -118,10 +119,6 @@ impl AudioDevice for CoreAudioDevice { } } - fn device_type(&self) -> DeviceType { - self.device_type - } - fn is_config_supported(&self, _config: &StreamConfig) -> bool { true } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 89da2ce..f9c2c2d 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -130,7 +130,9 @@ pub fn default_duplex_device() -> impl crate::device::AudioDuplexDevice { /// Returns the default duplex device for the given audio driver. /// /// The default device is usually the one the user has selected in its system settings. -pub fn default_duplex_device_from(driver: &D) -> D::DuplexDevice +pub fn default_duplex_device_from( + driver: &D, +) -> D::DuplexDevice where D::Device: AudioInputDevice + AudioOutputDevice, { diff --git a/src/backends/pipewire/device.rs b/src/backends/pipewire/device.rs index ec34c15..71b49ee 100644 --- a/src/backends/pipewire/device.rs +++ b/src/backends/pipewire/device.rs @@ -34,10 +34,6 @@ impl AudioDevice for PipewireDevice { } } - fn device_type(&self) -> DeviceType { - self.device_type - } - fn is_config_supported(&self, _config: &StreamConfig) -> bool { true } diff --git a/src/backends/wasapi/device.rs b/src/backends/wasapi/device.rs index 4f4845e..17398b4 100644 --- a/src/backends/wasapi/device.rs +++ b/src/backends/wasapi/device.rs @@ -37,10 +37,6 @@ impl AudioDevice for WasapiDevice { } } - fn device_type(&self) -> DeviceType { - self.device_type - } - fn is_config_supported(&self, config: &StreamConfig) -> bool { match self.device_type { DeviceType::Output => stream::is_output_config_supported(self.device.clone(), config), diff --git a/src/device.rs b/src/device.rs index 17a0939..796ce79 100644 --- a/src/device.rs +++ b/src/device.rs @@ -13,9 +13,6 @@ pub trait AudioDevice { /// Device display name fn name(&self) -> Cow; - /// Device type. Either input, output, or duplex. - fn device_type(&self) -> DeviceType; - /// Not all configuration values make sense for a particular device, and this method tests a /// configuration to see if it can be used in an audio stream. fn is_config_supported(&self, config: &StreamConfig) -> bool; diff --git a/src/duplex.rs b/src/duplex.rs index c10784a..6cd4e9b 100644 --- a/src/duplex.rs +++ b/src/duplex.rs @@ -2,16 +2,16 @@ //! //! This module includes a proxy for gathering an input audio stream, and optionally process it to resample it to the //! output sample rate. -use crate::{audio_buffer::AudioRef, channel_map::Bitset, device::AudioDevice}; use crate::device::{AudioInputDevice, AudioOutputDevice}; use crate::stream::{ AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; use crate::stream::{AudioInput, AudioOutput}; use crate::SendEverywhereButOnWeb; +use crate::{audio_buffer::AudioRef, channel_map::Bitset, device::AudioDevice}; +use fixed_resample::{PushStatus, ReadStatus, ResamplingChannelConfig}; use std::error::Error; use std::num::NonZeroUsize; -use fixed_resample::{PushStatus, ReadStatus, ResamplingChannelConfig}; use thiserror::Error; const MAX_CHANNELS: usize = 64;