# How to Implement Platform-Specific Audio Capture (ALSA/PulseAudio) in Meetily

> Learn how Meetily implements platform-specific audio capture using ALSA or PulseAudio. Discover the modular trait-based architecture and runtime backend selection for Linux in this technical guide.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: how-to-guide
- Published: 2026-07-14

---

**Meetily implements platform-specific audio capture through a modular trait-based architecture in [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs), allowing developers to choose between ALSA and PulseAudio backends at runtime by implementing the `AudioCapture` trait and configuring the backend preference in the Tauri command layer.**

According to the Meetily source code, the audio pipeline uses a platform-agnostic abstraction layer to handle system audio capture across Linux, macOS, and Windows. For Linux environments, the application supports both ALSA and PulseAudio through dedicated implementations in the Rust backend. This guide explains how to implement and configure these platform-specific audio capture backends to control microphone and system audio ingestion.

## Architecture Overview

The audio subsystem separates device discovery from platform-specific capture logic using a trait-based contract. The `AudioDevice` trait defined in [`frontend/src-tauri/src/audio/devices/mod.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/mod.rs) serves as the common interface, while OS-specific modules handle the actual stream initialization.

| Layer | Responsibility | Key Files |
|-------|----------------|-----------|
| Device Discovery | Enumerates input devices and selects default sources | [`frontend/src-tauri/src/audio/devices/discovery.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/discovery.rs) |
| Platform Implementations | Provides concrete ALSA/PulseAudio backends | [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs) |
| Capture Stream | Wraps low-level devices into async streams | [`frontend/src-tauri/src/audio/system_audio_stream.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/system_audio_stream.rs) |
| Audio Pipeline | Mixes streams and runs VAD/Whisper transcription | [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) |

## Implementing the Linux Audio Capture Backend

The **Linux implementation** lives in [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs). It currently uses **ALSA** (via the `cpal` crate) and falls back to **PulseAudio** when available.

### The AudioCapture Trait Contract

The platform layer implements the `AudioCapture` trait defined in [`frontend/src-tauri/src/audio/devices/mod.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/mod.rs). Concrete implementations must:

1. Request a 48 kHz, 16-bit stereo stream to match the pipeline requirements
2. Yield `AudioChunk` structs consumed by the mixing pipeline
3. Expose `start()` and `stop()` methods for stream control

### ALSA Implementation Example

To add explicit ALSA support, implement the `AudioCapture` trait using `cpal` with the ALSA host:

```rust
// frontend/src-tauri/src/audio/devices/platform/linux.rs
use crate::audio::devices::AudioCapture;
use cpal::traits::{DeviceTrait, HostTrait};

pub struct AlsaCapture {
    stream: cpal::Stream,
}

impl AlsaCapture {
    pub fn new(device_name: &str) -> anyhow::Result<Self> {
        // Obtain the ALSA host
        let host = cpal::host_from_id(cpal::HostId::Alsa)?;
        
        // Find device by name or fallback to default
        let device = host
            .devices()?
            .find(|d| d.name().ok().as_deref() == Some(device_name))
            .or_else(|| host.default_input_device())
            .ok_or_else(|| anyhow!("No ALSA input device found"))?;
            
        // Configure 48kHz, 16-bit format
        let config = device.default_input_config()?.into();
        
        // Build input stream
        let stream = device.build_input_stream(
            &config,
            move |data: &[i16], _: &cpal::InputCallbackInfo| {
                let chunk = AudioChunk::from_i16(data);
                AudioCapture::push_chunk(chunk);
            },
            move |err| log::error!("ALSA capture error: {}", err),
        )?;
        
        stream.play()?;
        Ok(Self { stream })
    }
}

impl AudioCapture for AlsaCapture {
    fn start(&self) -> anyhow::Result<()> { Ok(()) }
    fn stop(&self) -> anyhow::Result<()> { 
        self.stream.pause()?; 
        Ok(()) 
    }
}

```

### PulseAudio Fallback Logic

The module automatically detects the runtime environment. When `libpulse-simple` is available, it constructs a `PulseAudioCapture` object; otherwise, it creates an `AlsaCapture`. This selection happens during device initialization in [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs).

## Configuring Backend Selection at Runtime

To force ALSA instead of PulseAudio, modify the `select_backend` function:

```rust
fn select_backend(preferred: BackendPreference) -> Box<dyn AudioCapture> {
    match preferred {
        BackendPreference::Alsa => Box::new(AlsaCapture::new("default").unwrap()),
        BackendPreference::Pulse => Box::new(PulseAudioCapture::new(...).unwrap()),
        _ => Box::new(PulseAudioCapture::new(...).unwrap_or_else(|_| AlsaCapture::new("default").unwrap())),
    }
}

```

Expose this preference through the Tauri command layer in [`frontend/src-tauri/src/audio/recording_commands.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/recording_commands.rs):

**TypeScript frontend:**

```typescript
await invoke('start_recording', {
  mic_device_name: "Built-in Microphone",
  system_device_name: "BlackHole 2ch",
  meeting_name: "Team Sync",
  linux_backend: "alsa"
});

```

**Rust backend:**

```rust
#[tauri::command]
async fn start_recording(
    mic_device_name: Option<String>,
    system_device_name: Option<String>,
    meeting_name: Option<String>,
    #[serde(default)] linux_backend: Option<String>,
) -> Result<(), String> {
    let backend_pref = match linux_backend.as_deref() {
        Some("alsa") => BackendPreference::Alsa,
        Some("pulse") => BackendPreference::Pulse,
        _ => BackendPreference::Auto,
    };
    audio::devices::platform::linux::set_preference(backend_pref);
    // Continue with recording flow
    Ok(())
}

```

## Testing the Linux Capture Implementation

1. **Run with verbose logging:**

   ```bash
   RUST_LOG=frontend::audio=debug ./clean_run.sh
   ```

2. **Verify backend selection** in console output:
   
   ```

   [audio::devices::platform::linux] Selected backend: ALSA
   ```

3. **Inspect audio metrics** using the UI panel (`Cmd+I`) to confirm real-time RMS data from the Linux pipeline.

4. **Run automated tests:**

   ```bash
   cargo test --package meetily --test linux_capture
   ```

## Choosing Between ALSA and PulseAudio

| Situation | Recommended Backend |
|-----------|---------------------|
| Default desktop Linux (GNOME, KDE) | PulseAudio – better mixing and virtual device support |
| Low-latency or headless servers | ALSA – fewer layers, lower overhead |
| PulseAudio disabled | ALSA (automatic fallback) |
| Virtual audio sink capture | PulseAudio – provides virtual source support |

## Troubleshooting Common Pitfalls

- **Mismatched sample rates:** If the ALSA device cannot configure to 48 kHz, `cpal` returns an error. Use the `resampler` in [`audio/ffmpeg.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/audio/ffmpeg.rs) as a fallback.
- **Permission errors:** Users must belong to the `audio` group on some distributions. Check logs for `EACCES` warnings.
- **Concurrent access:** Only one capture stream may own an ALSA device at a time. Ensure [`fallback.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/fallback.rs) releases previous streams before re-initializing.

## Summary

- Meetily isolates platform-specific audio capture in [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs) using the `AudioCapture` trait
- Implement the trait to add custom ALSA or PulseAudio backends with 48 kHz, 16-bit PCM output
- Force specific backends at runtime by passing preference parameters through Tauri commands
- Verify implementations using `RUST_LOG=debug` and the built-in audio metrics panel
- Handle sample rate mismatches and permissions errors to ensure stable capture

## Frequently Asked Questions

### How do I force Meetily to use ALSA instead of PulseAudio?

Modify the `select_backend` function in [`frontend/src-tauri/src/audio/devices/platform/linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs) to return `AlsaCapture` explicitly, or pass a backend preference parameter through the Tauri command layer to override the auto-detection logic.

### What sample rate does Meetily require for audio capture?

The pipeline expects **48 kHz, 16-bit PCM** audio. If your ALSA device cannot natively support this rate, the system falls back to the resampler in [`audio/ffmpeg.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/audio/ffmpeg.rs) to ensure compatibility with the Whisper transcription engine.

### Where is the platform-specific audio capture trait defined?

The `AudioCapture` trait is defined in [`frontend/src-tauri/src/audio/devices/mod.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/mod.rs) and implemented by platform-specific modules including [`linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/linux.rs), [`macos.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/macos.rs), and [`windows.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/windows.rs) to provide consistent cross-platform behavior.

### How do I test audio capture without a real microphone?

Run the unit tests in [`frontend/src-tauri/src/audio/devices/platform/linux_tests.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux_tests.rs) which spawn mock ALSA devices, or use virtual audio sinks like PulseAudio null sinks to simulate system audio input.