Summary
Since the #665 fix, FluidVoice no longer crashes on audio-device changes — it hangs instead. The window stays responsive, but dictation is dead: pressing the hotkey does nothing, because the audio pipeline is frozen. Only Force Quit clears it. I'm doing that ~10 times a day.
This is a different defect from #665 (that SIGABRT is fixed). Here the app spins/deadlocks inside AVAudioEngine start and stop, both stuck in Apple's hardware-format query _GetHWFormat.
Environment
- FluidVoice 1.6.5 (build 16)
- macOS 26.3.1 (25D2128), Apple Silicon, MacBook Pro (MacBookPro18,3), 16 GB
- Speech model: parakeet-tdt
- Mic: built-in —
PreferredInputDeviceUID = BuiltInMicrophoneDevice (nothing exotic selected)
ExperimentalDirectAudioCaptureEnabled = true, ExperimentalDirectAudioCaptureForcedOff = true — the app auto-disabled its direct-capture fast path after repeated failures, so it now always uses the AVAudioEngine ("compatibility") path, which is the one that hangs
- Audio devices that come and go here: iPhone Continuity mic, LG UltraFine Display Audio, Hue Sync Audio (4-ch), Microsoft Teams Audio, ZoomAudioDevice
Symptoms
- UI responds; dictation does not. Hotkey press does nothing.
- One thread pinned ~11% CPU; process otherwise idle.
- Never recovers on its own — requires Force Quit.
Evidence — two live sample captures
Both show the audio engine wedged in _GetHWFormat.
1) Hang while STARTING the engine (most recent):
ASRService audio-capture path
ASRService.startCompatibilityAudioCapture(reason:)
ASRService.startEngine()
AVAudioEngineImpl::Initialize(NSError**) <- ~85% of samples stuck here
AVAudioIOUnit queue (~100% CPU)
AVAudioIOUnit::IOUnitPropertyListener(...)
AVAudioIOUnit_OSX::_GetHWFormat(...) <- spinning
2) Hang while STOPPING/retiring the engine (earlier):
DispatchQueue app.fluidvoice.audio-engine-retirement (serial)
AudioEngineRetirementDrain.enqueueAndWait(_:) <- synchronous wait
-[AVAudioEngine dealloc] -> ~AVAudioIOUnit_OSX()
blocked on _dispatch_event_loop_wait_for_ownership
AVAudioIOUnit queue (~100% CPU)
AVAudioIOUnit::IOUnitPropertyListener(...)
AVAudioIOUnit_OSX::_GetHWFormat(...) <- spinning
In (2), the engine dealloc waits on the AVAudioIOUnit queue via enqueueAndWait, but that queue never yields because it's stuck in the hardware-format property listener. The process sat wedged like this for ~30 minutes.
Also observed (same subsystem)
Reproduction
- Run 1.6.5 on a machine with audio devices that appear/disappear.
- Trigger an audio-device change while using the app. Reliable triggers for me:
- Physically plugging or unplugging headphones or an external amplifier / audio interface.
- Virtual/multi-channel devices appearing or disappearing (Microsoft Teams, Zoom, Hue Sync).
- The iPhone Continuity mic or a display's audio waking up.
- FluidVoice hangs — either failing to start capture (hotkey does nothing) or freezing during engine teardown. Force Quit required. Happens even with the plain built-in mic selected.
Suggested directions
- The hang sits in AVAudioEngine start/stop, both blocked on
_GetHWFormat. Consider removing the IOUnit property listener before engine teardown, and not driving engine init/dealloc synchronously (enqueueAndWait) on a queue that the hardware-format listener can block.
- Add a timeout/watchdog around engine start and retirement so a stuck
_GetHWFormat degrades gracefully instead of freezing the whole capture path.
- Debounce device/format-change events; a burst currently triggers repeated teardown/rebuild.
- Direct-capture got force-disabled on this machine, so the app is permanently on the hanging path — worth revisiting that fallback so a machine that fails direct capture isn't stranded on a compatibility path that also hangs.
Full .ips crash reports and .hang spindumps available on request.
Summary
Since the #665 fix, FluidVoice no longer crashes on audio-device changes — it hangs instead. The window stays responsive, but dictation is dead: pressing the hotkey does nothing, because the audio pipeline is frozen. Only Force Quit clears it. I'm doing that ~10 times a day.
This is a different defect from #665 (that SIGABRT is fixed). Here the app spins/deadlocks inside AVAudioEngine start and stop, both stuck in Apple's hardware-format query
_GetHWFormat.Environment
PreferredInputDeviceUID = BuiltInMicrophoneDevice(nothing exotic selected)ExperimentalDirectAudioCaptureEnabled = true,ExperimentalDirectAudioCaptureForcedOff = true— the app auto-disabled its direct-capture fast path after repeated failures, so it now always uses the AVAudioEngine ("compatibility") path, which is the one that hangsSymptoms
Evidence — two live
samplecapturesBoth show the audio engine wedged in
_GetHWFormat.1) Hang while STARTING the engine (most recent):
2) Hang while STOPPING/retiring the engine (earlier):
In (2), the engine dealloc waits on the AVAudioIOUnit queue via
enqueueAndWait, but that queue never yields because it's stuck in the hardware-format property listener. The process sat wedged like this for ~30 minutes.Also observed (same subsystem)
ASRService.registerDefaultDeviceChangeListener()— beachball; macOS attributed the deadlock to a third-party agent (Logitech Logi Options+).Reproduction
Suggested directions
_GetHWFormat. Consider removing the IOUnit property listener before engine teardown, and not driving engine init/dealloc synchronously (enqueueAndWait) on a queue that the hardware-format listener can block._GetHWFormatdegrades gracefully instead of freezing the whole capture path.Full
.ipscrash reports and.hangspindumps available on request.