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

Meetily implements platform-specific audio capture through a modular trait-based architecture in 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 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
Platform Implementations Provides concrete ALSA/PulseAudio backends 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
Audio Pipeline Mixes streams and runs VAD/Whisper transcription 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. 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. 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:

// 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.

Configuring Backend Selection at Runtime

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

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:

TypeScript frontend:

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

Rust backend:

#[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:

    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:

    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 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 releases previous streams before re-initializing.

Summary

  • Meetily isolates platform-specific audio capture in 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 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 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 and implemented by platform-specific modules including linux.rs, macos.rs, and 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 which spawn mock ALSA devices, or use virtual audio sinks like PulseAudio null sinks to simulate system audio input.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →