Skip to content

Releases: drewburchfield/macos-mic-keepwarm

v0.9.2 - Bluetooth Deadlock Resilience

16 Feb 20:36

Choose a tag to compare

Improves Bluetooth deadlock resilience with a targeted fix based on spindump evidence from a live deadlock capture.

Root cause (refined)

v0.9.1 dispatched stopRunning() to a background thread to avoid blocking the main thread during Bluetooth teardown. This worked, but the leaked background thread still held a CMIO semaphore that coreaudiod was waiting on, creating a circular deadlock chain:

  1. coreaudiod IO thread stuck in HALB_Guard::WaitFor() on dead Bluetooth device (96% of spindump samples)
  2. BTAudioHALPlugin blocked by mutex held by stuck IO thread
  3. coreaudiod AudioDeviceManager blocked by CMIO semaphore owned by mic-warm
  4. mic-warm's CMIO thread blocked waiting for coreaudiod to complete teardown

Fixes

  • CMIO pipeline teardown: removeInput() / removeOutput() called on the main thread before dispatching stopRunning() to background, releasing the semaphore coreaudiod blocks on. Measured teardown at 30-44ms with no deadlock (vs potentially hanging forever)
  • Stale delegate cleanup: old session's setSampleBufferDelegate(nil, queue: nil) called during teardown to prevent stale callbacks from corrupting new session's sample count
  • Thread-safe logging: replaced DateFormatter with ISO8601DateFormatter (thread-safe by design) to prevent crashes from concurrent logging across main and background threads

Docs

  • README updated: describes both delay triggers (hardware sleep after ~60s idle AND Bluetooth device switching regardless of idle state), documents deadlock resilience approach
  • Apple Feedback draft updated: includes precise spindump call stack evidence and kTCCServiceAudioCapture finding on macOS 26.2

Full Changelog: v0.9.1...v0.9.2

v0.9.1 - Bluetooth Deadlock Fix

15 Feb 03:16

Choose a tag to compare

Fixes a deadlock that caused mic-warm to freeze when Bluetooth audio devices (AirPods, etc.) disconnect.

Root cause

When AirPods disconnect while mic-warm is running, the debounced restart calls AVCaptureSession.stopRunning() on the old session. CoreAudio hangs in AudioObjectRemovePropertyListener waiting on a condition variable for the now-dead Bluetooth device. The main thread blocks forever, mic goes cold, and push-to-talk gets the activation delay again.

Fixes

  • Bluetooth deadlock fix: stopRunning() dispatched to a background thread so a hung Bluetooth teardown is a leaked thread, not a dead process
  • Auto-recovery: heartbeat timer detects stalled streams (>15s no samples) or zero-sample sessions (>30s) and automatically restarts the capture session
  • Signal handler safety: removed stopRunning() from SIGTERM/SIGINT handler to prevent deadlock on shutdown
  • Data race fix: sample counting moved to main queue to prevent race between audio callback and heartbeat
  • KVO crash fix: removeObserver guard matched to addObserver to prevent crash on session restart
  • Watchdog: logs warning if background stopRunning() hangs for >10s (observability for the Bluetooth deadlock)
  • DateFormatter thread safety: replaced DateFormatter with thread-safe ISO8601DateFormatter to prevent crashes from concurrent logging across main and background threads
  • Stale delegate fix: old session's sample buffer delegate is nil'd before dispatching stopRunning() to prevent stale callbacks from corrupting new session's sample count

Instrumentation

All diagnostic code (per-device CoreAudio listeners, AVFoundation session lifecycle notifications, verbose heartbeat logging) is retained behind #if false blocks for future debugging. Zero runtime cost.

Full Changelog: v0.9.0...v0.9.1

v0.9.0 - Native Swift Binary

14 Feb 23:00

Choose a tag to compare

Replaces ffmpeg + SwitchAudioSource with a single native Swift binary. No Homebrew dependencies.

Why this release

Homebrew upgrades move ffmpeg to a new versioned path (e.g. 8.0.1_2 to 8.0.1_3). macOS TCC tracks mic permissions by binary path for unsigned binaries, so every brew upgrade silently breaks the mic permission. The native binary at ~/.local/bin/mic-warm with ad-hoc code signing provides a stable identity that persists across updates.

What's new

  • Native Swift binary using AVCaptureSession to hold the mic open (replaces ffmpeg)
  • Event-driven device detection via CoreAudio property listener (replaces SwitchAudioSource polling)
  • Universal binary (ARM + Intel) built and released automatically via GitHub Actions
  • One-line install: curl -fsSL https://raw.githubusercontent.com/drewburchfield/macos-mic-keepwarm/master/install.sh | bash
  • Automatic migration from the old ffmpeg-based method
  • Signal-safe shutdown on SIGTERM/SIGINT with PID file cleanup
  • Recovery loop handles coreaudiod restarts and missing audio devices

What's removed

  • No more Homebrew dependency
  • No more ffmpeg or SwitchAudioSource requirements
  • No more PATH-dependent LaunchAgent configuration

Install

curl -fsSL https://raw.githubusercontent.com/drewburchfield/macos-mic-keepwarm/master/install.sh | bash

Uninstall

curl -fsSL https://raw.githubusercontent.com/drewburchfield/macos-mic-keepwarm/master/uninstall.sh | bash

Compatibility

  • macOS 13 Ventura through macOS 26 Tahoe
  • Apple Silicon and Intel Macs

v0.2.0 - Device Change Debouncing

14 Feb 23:08

Choose a tag to compare

What's new

  • Debounced device-change detection: 3-second settle window prevents rapid restarts when Bluetooth devices (AirPods, headsets) are connecting. macOS often fires multiple device-change events during a Bluetooth handoff; the debounce waits for things to settle before restarting the audio stream.
  • Integration test suite: Automated tests for process lifecycle, signal handling, and device switching.

Requirements

  • Homebrew, ffmpeg, SwitchAudioSource (same as v0.1.0)

v0.1.0 - Initial Release

14 Feb 23:08

Choose a tag to compare

Initial release of macos-mic-keepwarm.

What it does

Keeps the macOS microphone hardware warm to eliminate the 2-5 second push-to-talk activation delay on Apple Silicon Macs.

Implementation

  • Shell script (keep-mic-warm.sh) using ffmpeg to hold the mic input stream open
  • SwitchAudioSource for device detection
  • LaunchAgent for automatic startup
  • PID file management for clean process lifecycle

Requirements

  • Homebrew
  • ffmpeg (brew install ffmpeg)
  • SwitchAudioSource (brew install switchaudio-osx)