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

> Discover how Meetily finds audio devices on macOS, Windows, and Linux using the cpal crate for seamless cross-platform audio management.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: internals
- Published: 2026-08-01

---

**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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/discovery.rs)**. This async function coordinates platform-specific enumeration and returns a consolidated list of `AudioDevice` structs.

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/windows.rs)**. It attempts to create a WASAPI host explicitly:

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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:

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/configuration.rs)**:

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/discovery.rs)** file—creates a short-lived input stream to provoke the OS permission dialog:

```rust
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:**

```rust
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:**

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.