Skip to content

Commit 0dbf648

Browse files
Initial commit: macOS mic keep-warm fix for push-to-talk delay
0 parents  commit 0dbf648

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed

README.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# macos-mic-keepwarm
2+
3+
Fix the 2-5 second push-to-talk activation delay on macOS.
4+
5+
If you use voice transcription apps like SuperWhisper, WhisperFlow, Wispr Flow, or any push-to-talk tool and experience a delay before recording starts, especially with AirPods or Bluetooth audio, this is for you.
6+
7+
## The Problem
8+
9+
macOS aggressively power-manages the microphone hardware on Apple Silicon Macs (M1/M2/M3/M4). When no app is actively using the mic, the hardware goes to sleep. The next time a push-to-talk app tries to record, it has to wake the mic hardware first, causing a 2-5 second delay.
10+
11+
This means:
12+
- You press your push-to-talk key and start talking
13+
- The first 2-5 seconds of speech are lost or the app appears frozen
14+
- If you use it again quickly (within ~30-60 seconds), it's instant
15+
- Wait a minute, and the delay is back
16+
17+
This is especially bad with AirPods and Bluetooth headsets, where the audio routing adds even more wake-up latency.
18+
19+
## What I Tried (So You Don't Have To)
20+
21+
I spent hours debugging this across SuperWhisper and WhisperFlow on macOS Tahoe 26.2 (M4 MacBook Air). Here's everything that did NOT fix the activation delay:
22+
23+
- Changing the push-to-talk hotkey (tried Function key, Option+Space, Command+Shift+R)
24+
- Restarting `coreaudiod` and `corespeechd`
25+
- Increasing audio buffer sizes (`HALInputBufferSizeFrames`)
26+
- Changing audio sample rates
27+
- Resetting CoreAudio preferences
28+
- Disabling Continuity Camera
29+
- Granting Input Monitoring and Accessibility permissions
30+
- Changing microphone input sources
31+
- Restarting the Mac
32+
- pmset power management tweaks
33+
- nvram boot-args (not applicable on Apple Silicon)
34+
35+
None of these address the hardware-level mic sleep behavior.
36+
37+
### Related Issue: Recording Cutoff at 5-7 Seconds
38+
39+
During debugging I also discovered that **Siri's Built-In Voice Trigger** (`CSBuiltInVoiceTrigger`) can interfere with push-to-talk apps, causing recordings to cut off after 5-7 seconds. If you're experiencing that issue too, disable Siri voice activation:
40+
41+
1. System Settings > Siri & Spotlight
42+
2. Turn OFF "Listen for 'Siri'" / "Listen for 'Hey Siri'"
43+
3. Turn OFF "Press function key for Siri"
44+
45+
### Related Issue: Virtual Audio Plugins Cause Delay
46+
47+
Third-party audio drivers from Teams, Zoom, and other conferencing apps (installed at `/Library/Audio/Plug-Ins/HAL/`) can add startup latency. If you have `MSTeamsAudioDevice.driver`, `ZoomAudioDevice.driver`, or similar, try disabling them:
48+
49+
```bash
50+
sudo mv /Library/Audio/Plug-Ins/HAL/MSTeamsAudioDevice.driver /Library/Audio/Plug-Ins/HAL/MSTeamsAudioDevice.driver.disabled
51+
sudo mv /Library/Audio/Plug-Ins/HAL/ZoomAudioDevice.driver /Library/Audio/Plug-Ins/HAL/ZoomAudioDevice.driver.disabled
52+
sudo killall coreaudiod
53+
```
54+
55+
Teams and Zoom still work for calls without these custom drivers.
56+
57+
## The Fix
58+
59+
A single lightweight background process that holds the microphone input stream open. The mic hardware stays powered on and ready, so push-to-talk activation is always instant.
60+
61+
No virtual audio devices needed. No BlackHole, Loopback, or SoundFlower. Just ffmpeg reading from the mic and discarding the audio.
62+
63+
### How It Works
64+
65+
```
66+
ffmpeg -f avfoundation -i ":0" -f null /dev/null
67+
```
68+
69+
That's it. ffmpeg opens the default audio input device and sends the audio to `/dev/null` (nowhere). Nothing is recorded, stored, or transmitted. The only effect is that the microphone hardware stays awake.
70+
71+
- CPU usage: ~0%
72+
- Battery impact: negligible
73+
- Privacy: no audio is captured or stored anywhere
74+
- Works with: built-in mic, AirPods, Bluetooth headsets, USB mics, any input device
75+
76+
### Note on the Orange Dot
77+
78+
macOS will show the orange microphone indicator dot in the menu bar, attributed to "ffmpeg". This is accurate: ffmpeg has the mic open. But it's not listening to you. The audio goes straight to `/dev/null`.
79+
80+
## Installation
81+
82+
### Prerequisites
83+
84+
Install ffmpeg if you don't have it:
85+
86+
```bash
87+
brew install ffmpeg
88+
```
89+
90+
### Quick Start (Run Once)
91+
92+
```bash
93+
chmod +x keep-mic-warm.sh
94+
./keep-mic-warm.sh
95+
```
96+
97+
### Persistent Install (Survives Reboots)
98+
99+
```bash
100+
chmod +x install.sh
101+
./install.sh
102+
```
103+
104+
This creates a LaunchAgent that:
105+
- Starts automatically on login
106+
- Restarts automatically if killed
107+
- Runs silently in the background
108+
109+
macOS will prompt you to grant ffmpeg microphone access on first run. Click "Allow".
110+
111+
### Uninstall
112+
113+
```bash
114+
chmod +x uninstall.sh
115+
./uninstall.sh
116+
```
117+
118+
## Why Don't Transcription Apps Do This?
119+
120+
They should. SuperWhisper's own changelog acknowledges "handling push to talk shortcut if microphone is slow to start." The correct engineering solution is to keep the audio input stream open between recordings and use a ring buffer with lookback. When the user presses push-to-talk, start reading from the buffer, including audio captured just before the keypress.
121+
122+
The likely reason they don't: the orange microphone indicator dot. Apps don't want users seeing "SuperWhisper is using your microphone" 24/7, even though the alternative is a broken user experience.
123+
124+
Apple could fix this by providing a fast-wake API or a low-power standby mode for the mic hardware. As of macOS Tahoe 26.2, no such API exists.
125+
126+
## Why Not Use BlackHole or a Virtual Audio Device?
127+
128+
You don't need one. BlackHole, Loopback, and SoundFlower create virtual audio routing devices, which adds complexity and can introduce their own latency and compatibility issues. This fix works directly with your real microphone hardware. It's simpler and has fewer things that can break.
129+
130+
## Affected Apps
131+
132+
This delay affects any push-to-talk or voice transcription app on macOS, including but not limited to:
133+
- SuperWhisper
134+
- WhisperFlow
135+
- Wispr Flow
136+
- macOS Dictation
137+
- Any app that activates the microphone on-demand rather than continuously
138+
139+
## System Requirements
140+
141+
- macOS (tested on Tahoe 26.2, likely affects Sequoia and earlier)
142+
- Apple Silicon Mac (M1/M2/M3/M4) - Intel Macs may also be affected
143+
- ffmpeg (`brew install ffmpeg`)
144+
- Microphone permission for ffmpeg
145+
146+
## License
147+
148+
MIT

install.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/bin/bash
2+
# install.sh
3+
# Installs keep-mic-warm as a persistent background service (LaunchAgent).
4+
# Runs on login, restarts automatically if killed.
5+
6+
set -e
7+
8+
FFMPEG_PATH=$(which ffmpeg 2>/dev/null)
9+
if [ -z "$FFMPEG_PATH" ]; then
10+
echo "Error: ffmpeg is required. Install with: brew install ffmpeg"
11+
exit 1
12+
fi
13+
14+
PLIST_PATH="$HOME/Library/LaunchAgents/com.user.keep-mic-warm.plist"
15+
16+
# Unload existing if present
17+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
18+
19+
cat > "$PLIST_PATH" << EOF
20+
<?xml version="1.0" encoding="UTF-8"?>
21+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
22+
<plist version="1.0">
23+
<dict>
24+
<key>Label</key>
25+
<string>com.user.keep-mic-warm</string>
26+
<key>ProgramArguments</key>
27+
<array>
28+
<string>${FFMPEG_PATH}</string>
29+
<string>-f</string>
30+
<string>avfoundation</string>
31+
<string>-i</string>
32+
<string>:0</string>
33+
<string>-f</string>
34+
<string>null</string>
35+
<string>/dev/null</string>
36+
</array>
37+
<key>RunAtLoad</key>
38+
<true/>
39+
<key>KeepAlive</key>
40+
<true/>
41+
<key>StandardErrorPath</key>
42+
<string>/dev/null</string>
43+
<key>StandardOutPath</key>
44+
<string>/dev/null</string>
45+
</dict>
46+
</plist>
47+
EOF
48+
49+
launchctl load "$PLIST_PATH"
50+
51+
echo "Installed and running."
52+
echo "The mic will stay warm across reboots."
53+
echo ""
54+
echo "macOS will ask you to grant ffmpeg microphone access on first run."
55+
echo "Click 'Allow' when prompted."
56+
echo ""
57+
echo "To uninstall: ./uninstall.sh"

keep-mic-warm.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/bash
2+
# keep-mic-warm.sh
3+
# Prevents macOS from sleeping the microphone hardware between uses.
4+
# Solves the 2-5 second activation delay in push-to-talk transcription apps.
5+
6+
set -e
7+
8+
# Check for ffmpeg
9+
if ! command -v ffmpeg &>/dev/null; then
10+
echo "Error: ffmpeg is required. Install with: brew install ffmpeg"
11+
exit 1
12+
fi
13+
14+
# Kill any existing instance
15+
if [ -f /tmp/mic-warm.pid ]; then
16+
OLD_PID=$(cat /tmp/mic-warm.pid)
17+
if [ -n "$OLD_PID" ] && ps -p "$OLD_PID" -o comm= 2>/dev/null | grep -q "ffmpeg"; then
18+
kill "$OLD_PID" 2>/dev/null || true
19+
sleep 0.2
20+
fi
21+
rm -f /tmp/mic-warm.pid
22+
fi
23+
24+
ffmpeg -f avfoundation -i ":0" -f null /dev/null 2>/dev/null &
25+
PID=$!
26+
sleep 0.5
27+
28+
if ps -p "$PID" > /dev/null 2>&1; then
29+
echo "$PID" > /tmp/mic-warm.pid
30+
echo "Mic keep-warm running (PID: $PID)"
31+
echo "To stop: kill \$(cat /tmp/mic-warm.pid)"
32+
else
33+
echo "Error: ffmpeg failed to start. Check microphone permissions in System Settings > Privacy & Security."
34+
exit 1
35+
fi

uninstall.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
# uninstall.sh
3+
# Removes the keep-mic-warm background service.
4+
5+
set -e
6+
7+
PLIST_PATH="$HOME/Library/LaunchAgents/com.user.keep-mic-warm.plist"
8+
9+
if [ -f "$PLIST_PATH" ]; then
10+
launchctl unload "$PLIST_PATH" 2>/dev/null || true
11+
rm -f "$PLIST_PATH"
12+
echo "Uninstalled. Mic will return to default sleep behavior."
13+
else
14+
echo "Keep-mic-warm is not installed (no plist found)."
15+
fi
16+
17+
rm -f /tmp/mic-warm.pid

0 commit comments

Comments
 (0)