menu bar: pick the input device (capture through AUHAL) - #14
Open
andredezzy wants to merge 7 commits into
Open
Conversation
andredezzy
force-pushed
the
feat/input-device-picker
branch
from
July 31, 2026 00:22
2df0031 to
2221adc
Compare
FernandoGomes83
added a commit
to FernandoGomes83/parrot
that referenced
this pull request
Jul 31, 2026
…VAudioEngine per recording
FernandoGomes83
added a commit
to FernandoGomes83/parrot
that referenced
this pull request
Jul 31, 2026
Transcripts no longer logged by default (timing/length only; --echo-transcripts opts in), LaunchAgent discards output unless --log-file (0600), doctor flags and install --purge-legacy-logs removes the old world-readable /tmp logs, event tap narrowed to flagsChanged only and re-enabled when the system disables it, install.sh verifies sha256 + attestation, release workflow signs build provenance. Also fixes MenuBarController super.init order from the digimata#14 merge.
andredezzy
force-pushed
the
feat/input-device-picker
branch
from
July 31, 2026 18:04
2221adc to
7d05c2f
Compare
andredezzy
force-pushed
the
feat/input-device-picker
branch
from
July 31, 2026 19:22
7d05c2f to
3e2158d
Compare
UNVERIFIED -- not reproduced locally, do not upstream as-is. A long-lived AVAudioEngine reports the input format it cached at construction, so after the default device changes installTap gets a format the bus no longer has and AVFAudio raises 'com.apple.coreaudio.avfaudio': "Failed to create tap due to format mismatch, <AVAudioFormat 1 ch, 16000 Hz, Float32>". That is an ObjC exception, so the daemon dies rather than throwing. Confirmed the format is stale (engine reported 48000 Hz while the device sat at 44100 Hz) but installTap tolerates a rate-only divergence, so the failing condition needs a device identity change to reproduce.
Records from the chosen microphone without touching the system default input, so other apps are unaffected. The preference is stored as the device UID, not the AudioDeviceID: CoreAudio reassigns the numeric id across reboots and reconnects, so a stored id can silently point at a different microphone. It resolves to an id per recording, and an absent device falls back to the system default until it reappears. AudioCapture binds the node with auAudioUnit.setDeviceID(_:) before reading the input format, since the node reports the format of whichever device it is bound to. The submenu is rebuilt in menuWillOpen so a newly plugged mic shows up without watching CoreAudio for device changes.
CoreAudio creates an aggregate device per audio client the moment an input node is touched, named CADefaultDeviceAggregate-<pid>-N. It is visible only to that client, reports input channels, and is not flagged hidden — so parrot was listing its own plumbing as a selectable microphone. The composition dictionary of those aggregates carries the private key, which is what separates them from an aggregate the user built in Audio MIDI Setup; the latter is not private and stays listed.
AVAudioEngine's input node binds to whatever input is current the moment the
node is first touched, and nothing reconfigures it afterwards. Measured against a
Bluetooth headset while the built-in mic stayed the system default:
auAudioUnit.setDeviceID(_:) noErr, 0 frames in 3s
AudioUnitSetProperty(CurrentDevice) noErr, 0 frames in 3s
setDeviceID + engine.reset() noErr, 0 frames in 3s
headset as system default, no binding 115200 frames, peak 0.0212
Every binding call reports success and then delivers silence, which is how the
menu's device picker could look correct and record nothing.
A standalone AUHAL unit honours kAudioOutputUnitProperty_CurrentDevice: same
headset, same conditions, 48000 frames in 3s. Verified end to end afterwards —
5.06 s captured at rms 0.028 through the headset with the system default left on
the built-in microphone.
Two things leave the code with AVAudioEngine. AUHAL converts to 16 kHz mono
itself, so AVAudioConverter is gone; and there is no installTap(onBus:format:),
so the uncatchable AVFAudio format-mismatch exception that could terminate the
daemon on a device change is no longer reachable. The unit is disposed on stop so
a Bluetooth headset does not keep its microphone link open.
AUHAL input does not resample. Asking a 48 kHz device to deliver 16 kHz renders nothing at all: the built-in microphone captured 0.00 s while a 16 kHz Bluetooth headset — whose native rate already matched the request — captured fine. The first version of this only ever ran against the headset, so the conversion path was never exercised. Read the device's own format from the input scope, ask AUHAL for float at that same rate, and put AVAudioConverter back in charge of reaching 16 kHz mono. AUHAL then does only what it can do: pick the device and hand over float. `AudioUnitRender` failures are now logged. Swallowing that status is what made a failed render look exactly like a microphone that heard nothing.
Left unwritten, kAudioOutputUnitProperty_CurrentDevice still reports the right device and AudioOutputUnitStart returns noErr, but the unit renders zero frames. Measured: 0 frames in 2 s unbound against 30400 bound to the same device id. The system default input is now resolved and written like any other choice, so the default preference is no longer the broken one.
andredezzy
force-pushed
the
feat/input-device-picker
branch
from
August 1, 2026 20:34
3e2158d to
b00b3fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pick the input device from the menu bar, and record from the one you picked.
AVAudioEngine's input node follows the system default and ignores a device set on the engine, so honouring a choice at all needs AUHAL. Capture goes throughkAudioUnitSubType_HALOutputwith the device written explicitly, bound at the device's own sample rate and resampled here, because a Bluetooth headset that reports 16 kHz will not accept 48.Seven commits, each with the reproduction in the body:
Measured on this machine with a Baseus BP1 Pro over Bluetooth and the built-in microphone.