Skip to content

fix: restart mic capture when a call app reconfigures the input device - #2

Open
jayfrid-bot wants to merge 1 commit into
digimata:masterfrom
jayfrid-bot:fix-mic-dies-on-input-reconfigure
Open

fix: restart mic capture when a call app reconfigures the input device#2
jayfrid-bot wants to merge 1 commit into
digimata:masterfrom
jayfrid-bot:fix-mic-dies-on-input-reconfigure

Conversation

@jayfrid-bot

Copy link
Copy Markdown

The bug

When another process takes the microphone with voice processing — FaceTime, Zoom, any call app — macOS reconfigures the input device, and the AVAudioEngine driving the mic tap stops delivering buffers. Silently: no error, no failed write, nothing in the log. The mic track just ends while the system track keeps recording.

Real-world repro that led me here: a 19-minute FaceTime call recorded a 1.7-second mic.caf and a full-length system.caf. The call connected ~2s into the session and killed the mic capture; the transcript came out all "them", no "me". Since recording calls is presumably quill's core use case, this seemed worth fixing properly.

The fix

MicRecorder now subscribes to AVAudioEngineConfigurationChange and, after a 0.5s debounce (reconfiguration storms post several notifications back-to-back), rebuilds the engine and re-attaches the tap to the same open AVAudioFile:

  • The dead span is padded with zeroed frames so the track stays wall-clock true and transcript timestamps don't drift relative to the system track.
  • The restart path records raw (no voice processing) — during a call the call app owns echo cancellation, and re-engaging VP mid-call is what causes fights over the device in the first place.
  • The tap converts through the file's original processing format, so a device that comes back at a different sample rate (AirPods switching to their call profile) still writes cleanly — the rate-mismatched case goes through the block-based AVAudioConverter API since the one-shot convenience only handles equal rates.
  • A failed restart (device mid-reconfigure) retries every 2s for the rest of the session.

Verification

Tested on a live FaceTime call on macOS 15 / Apple Silicon: the log shows capture restarting on both the call-start and call-end reconfigurations, and a 39s test session produced a 36s mic track (the ~3s being the two debounce/re-attach windows, silence-padded) with speech correctly transcribed and speaker-tagged on both sides of the call boundary:

mic: input device reconfigured (call app?) — restarting capture
mic: input device reconfigured (call app?) — restarting capture
○ stopped · 0:39 · …/2026.07.28-1243

🤖 Generated with Claude Code

When another process takes the microphone with voice processing (FaceTime,
Zoom), macOS reconfigures the input device and the AVAudioEngine driving
the mic tap stops delivering buffers — silently, with no error. The mic
track just ends there while the system track keeps going: a real 19-minute
call session produced a 1.7-second mic.caf and a full-length system.caf,
with nothing in the log between "recording" and "stopped".

Subscribe to AVAudioEngineConfigurationChange and, after a short debounce
(reconfiguration storms post several notifications), rebuild the engine
and re-attach the tap to the same open AVAudioFile. The dead span is
padded with zeroed frames so the track stays wall-clock true and
transcript timestamps don't drift. The restart path records raw — during
a call the call app owns echo cancellation — and converts through the
file's original format, so a device that comes back at a different sample
rate (AirPods) still writes cleanly. Failed restarts retry every 2s for
the rest of the session.

Verified on a live FaceTime call: capture restarted on both the call-start
and call-end reconfigurations; a 39s session got a 36s mic track with
speech transcribed on both sides of the call boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dremnik

dremnik commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Appreciate the PR! will test this and merge later today

FernandoGomes83 added a commit to FernandoGomes83/quill that referenced this pull request Jul 31, 2026
…onfigures the input device

Conflict with digimata#18 in MicRecorder: kept digimata#18's OSAllocatedUnfairLock-backed
LockedState and routed digimata#2's code through the computed properties. digimata#2's new
lastBufferAt has the same cross-thread access pattern (written from the tap,
read on main during the restart), so it joins the same locked state rather
than sitting outside it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants