How Audio Device Discovery Works Across macOS, Windows, and Linux in Meetily

Meetily uses the cpal crate as a cross-platform audio abstraction layer with thin platform-specific wrappers to enumerate all available input and output devices.

Audio device discovery is a critical first step in any meeting application. In Meetily, this process is handled by a unified Rust module that adapts to each operating system's native audio stack. The implementation leverages the cpal (Cross-Platform Audio Library) crate for portability while adding targeted platform code where cpal's defaults fall short.

Overview of the Discovery Architecture

The central entry point is list_audio_devices() in frontend/src-tauri/src/audio/devices/discovery.rs. This async function coordinates platform-specific enumeration and returns a consolidated list of AudioDevice structs.

pub async fn list_audio_devices() -> Result<Vec<AudioDevice>> {
    let host = cpal::default_host();

    // Platform-specific enumeration
    #[cfg(target_os = "windows")] { platform::configure_windows_audio(&host)? }
    #[cfg(target_os = "linux")]   { platform::configure_linux_audio(&host)? }
    #[cfg(target_os = "macos")]   { platform::configure_macos_audio(&host)? }
}

After the platform-specific configuration runs, the function adds any remaining devices reported by the default host. This two-phase approach ensures no device is missed even when platform-specific logic is active.

Windows Audio Device Discovery

On Windows, Meetily prioritizes WASAPI (Windows Audio Session API) for its loopback capture capabilities.

The implementation lives in frontend/src-tauri/src/audio/devices/platform/windows.rs. It attempts to create a WASAPI host explicitly:

let wasapi_host = cpal::host_from_id(cpal::HostId::Wasapi)?;

WASAPI enumeration covers both output devices (including loopback for system audio) and input devices. If WASAPI initialization fails, the code falls back to the default host's input_devices() and output_devices() methods.

A critical reliability feature: when no devices are discovered through enumeration, the implementation guarantees a default input and output device by querying host.default_input_device() and host.default_output_device(). This prevents empty device lists on systems with unusual driver configurations.

macOS Audio Device Discovery

macOS uses CoreAudio through cpal's default host. The platform implementation in frontend/src-tauri/src/audio/devices/platform/macos.rs handles two distinct concerns.

Input devices are enumerated directly via host.input_devices() with no filtering.

Output devices require special handling: built-in speakers are explicitly filtered out because they cannot capture system audio. However, Bluetooth devices such as AirPods are preserved since they appear as both input and output endpoints in CoreAudio.

System audio capture on macOS is handled separately via ScreenCaptureKit. The device discovery module only needs to expose device names for selection; the actual audio capture flow bypasses cpal for screen recording scenarios.

Linux Audio Device Discovery

Linux presents the most complex environment due to the ALSA/PulseAudio/PipeWire ecosystem. The implementation in frontend/src-tauri/src/audio/devices/platform/linux.rs addresses this with a dual-host strategy.

Input devices come from the default host (typically PulseAudio or PipeWire via ALSA compatibility).

Output/system-audio devices require querying the ALSA host directly:

let alsa_host = cpal::host_from_id(cpal::HostId::Alsa)?;

The ALSA host exposes monitor sources such as "PulseAudio Monitor" or "Monitor of Built-in Audio Analog Stereo." These are registered as Output device types, enabling system audio capture without additional kernel modules or Discord-style workarounds.

Device Representation and Configuration

All three platform implementations populate AudioDevice structs defined in frontend/src-tauri/src/audio/devices/configuration.rs:

pub struct AudioDevice {
    pub name: String,
    pub device_type: DeviceType,
}

pub enum DeviceType {
    Input,
    Output,
}

The name field contains the human-readable device label shown in the UI. The device_type enum determines whether the device appears in the microphone dropdown or the system audio dropdown.

Permission Handling

macOS and Windows require explicit user permission before accessing audio devices. Meetily's trigger_audio_permission() function—located in the same discovery.rs file—creates a short-lived input stream to provoke the OS permission dialog:

pub fn trigger_audio_permission() -> Result<bool> {
    // Create minimal stream to trigger permission prompt
    let host = cpal::default_host();
    let device = host.default_input_device()
        .ok_or("No input device available")?;
    let config = device.default_input_config()?;
    
    // Attempt to build stream; success = permission granted
    let stream = device.build_input_stream(
        &config.into(),
        |_data: &[f32], _: &_| {},
        |err| eprintln!("Stream error: {}", err),
    )?;
    
    stream.play()?;
    // Stream dropped immediately after triggering dialog
    Ok(true)
}

The function returns true only when the stream starts successfully, indicating permission was granted.

Usage Examples

Retrieving devices for the UI:

use crate::audio::devices::discovery::list_audio_devices;

#[tokio::main]
async fn main() {
    match list_audio_devices().await {
        Ok(devices) => {
            for d in devices {
                println!("{} ({:?})", d.name, d.device_type);
            }
        }
        Err(e) => eprintln!("Failed to enumerate devices: {}", e),
    }
}

Requesting permission on macOS/Windows:

use crate::audio::devices::discovery::trigger_audio_permission;

fn ensure_permission() {
    match trigger_audio_permission() {
        Ok(true) => println!("Permission granted"),
        Ok(false) => println!("Permission denied"),
        Err(e) => eprintln!("Error: {}", e),
    }
}

These patterns keep Meetily's audio pipeline—recording, mixing, VAD, and Whisper transcription—completely platform-agnostic.

Summary

  • cpal provides the foundation for cross-platform audio device enumeration in Meetily
  • WASAPI on Windows enables loopback capture with fallback for compatibility
  • CoreAudio on macOS filters non-capturable outputs while preserving Bluetooth devices
  • ALSA/PulseAudio on Linux uses dual-host enumeration to expose monitor sources
  • Two-phase discovery (platform-specific then default host) guarantees complete device coverage
  • Permission triggers use minimal streams to activate OS dialogs without user confusion

Frequently Asked Questions

What audio backend does Meetily use for cross-platform support?

Meetily uses the cpal (Cross-Platform Audio Library) crate as its primary abstraction. According to the Zackriya-Solutions/meetily source code, cpal handles the heavy lifting of host enumeration while platform-specific modules in frontend/src-tauri/src/audio/devices/platform/ fill gaps for loopback capture and device filtering.

How does Meetily capture system audio on each platform?

Windows uses WASAPI loopback devices. macOS delegates to ScreenCaptureKit for system audio (device discovery only lists selectable outputs). Linux surfaces ALSA monitor sources from PulseAudio/PipeWire as output devices. The implementation varies because each OS exposes system audio through different APIs.

Why does the Linux implementation query two different hosts?

The default host on Linux (often PulseAudio or PipeWire) does not always expose monitor sources for capture. By additionally querying the ALSA host via cpal::host_from_id(cpal::HostId::Alsa), Meetily discovers "Monitor of..." devices that function as system audio inputs. This dual-host approach is implemented in frontend/src-tauri/src/audio/devices/platform/linux.rs.

What happens if no audio devices are detected on Windows?

The Windows platform code in frontend/src-tauri/src/audio/devices/platform/windows.rs guarantees a fallback by calling host.default_input_device() and host.default_output_device() when enumeration returns empty. This prevents UI failures on systems with unusual audio driver configurations.

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 →