#[must_use]
pub fn available_backend() -> &'static str {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
return "ALSA";
}
#[cfg(all(target_os = "macos", feature = "audio-coreaudio"))]
{
return "CoreAudio";
}
#[cfg(all(target_os = "windows", feature = "audio-wasapi"))]
{
return "WASAPI";
}
#[cfg(all(target_arch = "wasm32", feature = "audio-webaudio"))]
{
return "WebAudio";
}
#[allow(unreachable_code)]
"None (enable audio-alsa, audio-coreaudio, audio-wasapi, or audio-webaudio feature)"
}
#[must_use]
pub fn has_native_backend() -> bool {
#[cfg(any(
all(target_os = "linux", feature = "audio-alsa"),
all(target_os = "macos", feature = "audio-coreaudio"),
all(target_os = "windows", feature = "audio-wasapi"),
all(target_arch = "wasm32", feature = "audio-webaudio")
))]
{
return true;
}
#[allow(unreachable_code)]
false
}
#[derive(Debug, Clone)]
pub struct CaptureConfig {
pub sample_rate: u32,
pub channels: u16,
pub buffer_size: usize,
pub exclusive: bool,
}
impl Default for CaptureConfig {
fn default() -> Self {
Self {
sample_rate: 16000, channels: 1, buffer_size: 1600, exclusive: false,
}
}
}
impl CaptureConfig {
#[must_use]
pub fn whisper() -> Self {
Self::default()
}
#[must_use]
pub fn stereo() -> Self {
Self {
channels: 2,
..Self::default()
}
}
pub fn validate(&self) -> Result<(), AudioError> {
if self.sample_rate == 0 {
return Err(AudioError::InvalidConfig(
"sample_rate must be > 0".to_string(),
));
}
if self.channels == 0 {
return Err(AudioError::InvalidConfig(
"channels must be > 0".to_string(),
));
}
if self.buffer_size == 0 {
return Err(AudioError::InvalidConfig(
"buffer_size must be > 0".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AudioDevice {
pub id: String,
pub name: String,
pub max_sample_rate: u32,
pub input_channels: u16,
pub is_default: bool,
}
pub fn list_devices() -> Result<Vec<AudioDevice>, AudioError> {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
return AlsaBackend::list_devices();
}
#[allow(unreachable_code)]
Ok(vec![])
}
pub fn default_device() -> Result<Option<AudioDevice>, AudioError> {
Ok(list_devices()?.into_iter().find(|d| d.is_default))
}
pub struct AudioCapture {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
backend: AlsaBackend,
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
config: CaptureConfig,
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
device_id: Option<String>,
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
running: bool,
}
impl AudioCapture {
pub fn open(device: Option<&str>, config: &CaptureConfig) -> Result<Self, AudioError> {
config.validate()?;
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
let backend = AlsaBackend::open(device, config)?;
return Ok(Self { backend });
}
#[allow(unreachable_code)]
{
let _ = device;
Err(AudioError::NotImplemented(
"Audio capture requires platform-specific backend (enable audio-alsa on Linux)"
.to_string(),
))
}
}
pub fn read(&mut self, buffer: &mut [f32]) -> Result<usize, AudioError> {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
return self.backend.read(buffer);
}
#[allow(unreachable_code)]
{
let _ = buffer;
Err(AudioError::NotImplemented("read".to_string()))
}
}
#[must_use]
#[allow(clippy::needless_return)] pub fn config(&self) -> &CaptureConfig {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
return &self.backend.config;
}
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
&self.config
}
#[must_use]
#[allow(clippy::needless_return)] pub fn is_running(&self) -> bool {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
return true; }
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
self.running
}
pub fn close(self) -> Result<(), AudioError> {
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
{
let mut backend = self.backend;
return backend.close();
}
#[allow(unreachable_code)]
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "audio-alsa"))]
impl std::fmt::Debug for AudioCapture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioCapture")
.field("backend", &"AlsaBackend")
.field("config", &self.backend.config)
.finish()
}
}
#[cfg(not(all(target_os = "linux", feature = "audio-alsa")))]
impl std::fmt::Debug for AudioCapture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioCapture")
.field("config", &self.config)
.field("running", &self.running)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum MockSignal {
#[default]
Silence,
Sine { frequency: f32, amplitude: f32 },
WhiteNoise { amplitude: f32 },
Impulse,
Square { frequency: f32, amplitude: f32 },
}
#[derive(Debug)]
pub struct MockCaptureSource {
config: CaptureConfig,
signal: MockSignal,
sample_index: u64,
rng_state: u64,
}