Skip to content

cortexuvula/MacAudio

Repository files navigation

MacAudio

A macOS menu bar app that captures microphone and system audio, mixes them together, and exposes the mix as a virtual input device. Any app that accepts audio input (QuickTime, Zoom, OBS, Discord, etc.) can select "MacAudio Virtual Device" to receive the combined stream.

Requires macOS 14.2+ (Sonoma) — uses the Core Audio Taps API for system audio capture.

How It Works

Mic (CoreAudio IOProc) ──┐
                          ├─→ AudioMixer ──→ SharedRingBuffer (shm) ──→ HAL Driver ──→ Virtual Input Device
System (CATapDescription) ┘

The app and driver run in separate processes. The Swift app captures and mixes audio, then writes interleaved Float32 samples to a POSIX shared memory ring buffer. The C driver (an AudioServerPlugin) reads from the same ring buffer and presents the data as a standard macOS input device.

Audio from each source is automatically resampled to 48 kHz before mixing, so devices running at different native sample rates (e.g. a 44.1 kHz USB mic) are handled transparently with no pitch or speed artifacts.

Install

Download the latest signed .pkg or .dmg from the Releases page. The installer places MacAudio.app in /Applications and the HAL driver in /Library/Audio/Plug-Ins/HAL/. After install, launch MacAudio from Applications — it will appear as a menu bar icon.

To verify a download against the published checksums:

shasum -a 256 -c MacAudio-<version>-SHA256SUMS.txt

Build Requirements

  • macOS 14.2 or later
  • Xcode 16.0+
  • xcodegen (brew install xcodegen)
  • Developer ID certificate for code signing (required on macOS 15+ for HAL driver loading)

Permissions

The app requests the following permissions on first launch:

  • Microphone — to capture mic input
  • Screen & System Audio Recording — to capture system audio via AudioHardwareCreateProcessTap

Grant these in System Settings > Privacy & Security.

Building

Generate the Xcode project

xcodegen generate

Note: After every xcodegen generate, you must manually restore:

  1. MacAudio/MacAudio.entitlements — xcodegen resets it. Restore the com.apple.security.device.audio-input entitlement.
  2. MacAudioDriver/Info.plist — xcodegen strips CFBundleExecutable. Re-add it.

Build the app (includes driver)

xcodebuild -project MacAudio.xcodeproj -target MacAudio -configuration Debug build \
  CODE_SIGN_IDENTITY="Developer ID Application" DEVELOPMENT_TEAM=RB4QV9W52C CODE_SIGN_STYLE=Manual

Build the driver only

xcodebuild -project MacAudio.xcodeproj -target MacAudioDriver -configuration Debug clean build \
  CODE_SIGN_IDENTITY="Developer ID Application" DEVELOPMENT_TEAM=RB4QV9W52C CODE_SIGN_STYLE=Manual

Installing the Driver

Using the install script

./Installer/install.sh

Manual install

sudo rm -rf /Library/Audio/Plug-Ins/HAL/MacAudioDriver.driver
sudo cp -R build/Debug/MacAudioDriver.driver /Library/Audio/Plug-Ins/HAL/
sudo xattr -rc /Library/Audio/Plug-Ins/HAL/MacAudioDriver.driver
sudo killall coreaudiod

Verify the driver is loaded:

system_profiler SPAudioDataType | grep -A5 "MacAudio"

Uninstall

./Installer/uninstall.sh

Usage

  1. Install the driver (see above) or use the signed installer from Releases
  2. Launch MacAudio — it appears as a menu bar icon
  3. Click the icon and press Start
  4. In any recording app, select MacAudio Virtual Device as the input device
  5. Use the sliders to adjust mic and system audio levels independently

Settings

The menu bar icon's Settings submenu has two opt-in toggles, both off by default:

  • Launch at Login — registers the app via SMAppService so it starts automatically when you log in.
  • Auto-Start Capturing — when launched (at login or otherwise), the app starts capturing immediately if the driver is installed and microphone permission is already granted. Enabling this also enables Launch at Login, since auto-start is only useful when the app is running. Turning Launch at Login off clears Auto-Start Capturing too.

If preconditions aren't met at login (driver missing, mic permission not yet granted), the app retries for ~10 seconds to ride out transient TCC delays before showing an error icon in the menu bar.

Project Structure

MacAudio/
├── MacAudio/                    # Swift menu bar app
│   ├── MacAudioApp.swift        # App entry point
│   ├── AudioEngine/
│   │   ├── AudioConstants.swift       # Shared constants (sample rate, buffer size, etc.)
│   │   ├── AudioMixer.swift           # Orchestrates capture and writes to ring buffer
│   │   ├── MicCapture.swift           # Mic input via CoreAudio IOProc
│   │   ├── SystemAudioCapture.swift   # System audio via CATapDescription + aggregate device
│   │   ├── SampleRateConverter.swift    # AVAudioConverter wrapper for real-time resampling
│   │   └── SharedRingBufferWriter.swift  # Swift wrapper for the C ring buffer API
│   ├── Models/
│   │   ├── AppState.swift             # Observable state for the UI
│   │   └── AudioDeviceManager.swift   # Enumerates audio devices
│   ├── Views/
│   │   ├── MenuBarView.swift          # NSMenu-based menu bar UI
│   │   ├── VolumeSlider.swift         # Gain sliders
│   │   └── DevicePickerView.swift     # Mic device selector
│   └── Utilities/
│       ├── Permissions.swift          # Permission checks
│       └── DriverInstaller.swift      # In-app driver installation
├── MacAudioDriver/              # C AudioServerPlugin HAL driver
│   ├── MacAudioDriver.c         # Full AudioServerPlugInDriverInterface implementation
│   ├── SharedRingBuffer.c       # Lock-free SPSC ring buffer (POSIX shm)
│   └── SharedRingBuffer.h       # Public header (shared with Swift via bridging)
├── Installer/
│   ├── install.sh
│   └── uninstall.sh
└── project.yml                  # xcodegen project definition

Key Constants

Constant Value
Sample rate 48000 Hz (sources at other rates are resampled automatically)
Channels 2 (stereo interleaved Float32)
Ring buffer 16384 frames
Shared memory /macaudio_ringbuffer
Virtual device UID MacAudioDevice_UID

Debugging

# Driver logs
log show --last 10s --predicate 'subsystem == "com.macaudio.driver"' --info --debug

# App logs
log show --last 10s --predicate 'subsystem == "com.macaudio.app"' --info --debug

# coreaudiod logs mentioning MacAudio
log show --last 10s --predicate 'process == "coreaudiod" AND message CONTAINS "MacAudio"'

# Check driver process
ps aux | grep MacAudioDriver

macOS Tahoe (26.x) Notes

On macOS Tahoe, HAL plugins load out-of-process via Core-Audio-Driver-Service.helper. This has two implications:

  • Driver signing is mandatory. Ad-hoc signed drivers are silently skipped. You must sign with a Developer ID certificate and remove the com.apple.provenance extended attribute after copying to /Library/Audio/Plug-Ins/HAL/.
  • New QueryInterface UUID. Tahoe sends UUID EEA5773D-CC43-49F1-8E00-8F96E7D23B17 in addition to the standard EAAF5B97-B965-4F68-9815-E718330862D5. The driver handles both.

License

All rights reserved.

About

macOS menu bar app that mixes mic + system audio into a virtual input device via CoreAudio HAL AudioServerPlugin

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors