13 KiB
iMac19,2 internal mic: silent capture despite maxed gain
TL;DR
The internal mic shows up normally in PipeWire/ALSA/Discord, with fully
adjustable gain controls, but captures almost nothing regardless of how high
any gain is pushed. Root cause: the CS8409 codec driver's HDA auto-config
binds the exposed capture PCM stream to the wrong ADC node, orphaning the
real internal-mic ADC entirely — its mixer controls work, but nothing is
ever routed through them. A fix for this exact bug, confirmed on a sibling
Mac model, is packaged into this repo's build (patches/snd-hda-codec-cs8409/)
but not yet verified on this exact iMac19,2 hardware — see
Current verification status.
Hardware / software identification
- Model: Apple iMac19,2 (2019, 21.5")
- Distro: Aurora (uBlue/Fedora Kinoite bootc image), Kinoite variant
44.20260908.1 - Kernel:
7.1.8-200.fc44.x86_64 - Audio codec: Cirrus Logic CS8409, HDA vendor id
0x10138409, subsystem id0x106b0f00(confirmed via/proc/asound/card0/codec#0) - Secure Boot: unsupported/disabled on this hardware (
mokutil --sb-state) — a documented non-goal of this fix; see Open risks.
Symptom
Mic appears as a normal PipeWire source (Built-in Audio Analog Stereo) and
ALSA capture device (CS8409 Analog, card 0). All of the following were
confirmed at 100% (or higher) with no effect on captured level:
- ALSA
Internal Miccapture volume: 100% (63/63), unmuted - ALSA
Internal Mic Boost: 100% (2/2) - PipeWire source software volume: tested at 1.0, 4.0, and 8.0 (up to +18dB)
- Discord's own in-app input-volume slider: maxed
Direct measurement, bypassing Discord and any GUI entirely, via
pw-record --target <mic source> / arecord, analyzed with
ffmpeg -af volumedetect:
| Gain setting | mean_volume | max_volume |
|---|---|---|
| PipeWire source @ 1.0x (100%, all ALSA gain also maxed) | -80.8 dB | -71.2 dB |
| PipeWire source @ 4.0x | -59.7 dB | -50.3 dB |
| PipeWire source @ 8.0x | -59.7 dB | -50.3 dB (uncontrolled re-test, not a true ceiling — see note below) |
The 1x → 4x jump is real, reproducible signal (matches the expected amplification). The identical 4x/8x reading is not evidence of a hard ceiling — it came from two takes that weren't spoken into with controlled, identical volume/distance, so it's not a clean A/B. Even at its best measured point (-50dB max), this is far too quiet for usable speech (normal speech should peak around -12 to -6dB) — consistent with "gain is working, but there's very little real signal to amplify in the first place."
Ruled out, with evidence
- Wrong/muted output device — a real but separate issue found early on:
the output sink was muted at 0% (unrelated to the mic). Fixed
independently (
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0). - PipeWire/ALSA mute or wrong default device —
wpctl status,pactl list sources, andamixerall independently agreed the mic source was unmuted and at 100%+ from three different vantage points. - Discord-side gain/config — Discord's own input-volume slider was
already at maximum; its
settings.jsondoesn't store per-account voice/mic settings in plaintext (those sync via Discord's own account storage), so this was ruled out by the maxed-slider observation rather than a config file. - The camera providing the audio instead of the codec —
arecord -lshows exactly one ALSA capture device (CS8409 Analog, card 0); the FaceTime HD Camera (USB05ac:8511) exposes no audio interface to Linux at all (checked vialsusb+ absence fromarecord -l/arecord -L). - Apple T2 chip audio routing — ruled out: no Apple PCI bridge/T2 device
present (
lspci -nn), and iMac19,2 (2019 21.5") predates T2's presence in Macs' internal-mic path regardless. - Missing Flatpak/portal permission for Discord's mic access —
wpctl statusshowed Discord's ownWEBRTC VoiceEngineclient (verified by cross-referencing the sandboxed PID against the host process list) with an active capture stream connected to the real hardware node — a permission denial would show no connected stream at all, not merely low level.
Root cause
Traced to a specific, very recent (2026-08-19) upstream bug report and fix on a sibling Mac model:
- Issue:
egorenar/snd-hda-codec-cs8409#5, "mic input" - Diagnostic dead-end + panic warning + D3 discovery (2026-08-17): https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5314393975
- Root cause + fix, confirmed working (2026-08-19): https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5338459155
- Fork/PR carrying Linux 6.17+ HDA API compatibility:
PR #30,
daviddoll/snd-hda-codec-cs8409@kernel-6.17-hda-compat, open/unmerged.
In patch_cirrus_apple.h, function cs_8409_apple_boot_init(), the
SNDRV_PCM_STREAM_CAPTURE branch only handles two cases for the capture
stream's hinfo->nid:
hinfo->nid == spec->intmike_adc_nid— the real internal-mic ADC, set up correctly.hinfo->nid == 0x1a— the headset-mic node, set up correctly.
There's no fallback for when HDA auto-config lands the exposed capture PCM
stream on neither — e.g. landing on node 0x12, wired to an unconfigured
external Line-In pin (0x34) with no physical signal path at all. When this
happens, the real internal-mic ADC (spec->intmike_adc_nid, 0x23 for our
subsystem-id group) has all its mixer/gain/switch controls correctly present
and adjustable — which is exactly why raising every gain control had no
effect — but is never attached to any PCM stream, so it's completely
orphaned. Confirmed on the sibling model via journalctl -kf during a live
arecord test: the internal-mic-specific prepare hook was never called,
only the generic default path ran.
Our subsystem id (0x106b0f00) is grouped together with the sibling model's
family (0x106b1000, 0x106b0f00, 0x106b0e00) in this same file's
intmike_adc_nid assignment logic — all three get intmike_nid = 0x45,
intmike_adc_nid = 0x23 — matching our own kernel log
(snd_hda_codec_cs8409 hdaudioC0D0: ... Internal Mic=0x45). So this is a
plausible direct match, though confirmed only on iMac18,1, not this exact
iMac19,2 — see Open risks.
On the sibling model, applying the fix took capture from -91dB pure digital silence to a real -16.9dB mean / -0.0dB max signal.
The fix
See patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch
for the actual diff (source of truth — not duplicated here). In short: if
the capture stream's hinfo->nid matches neither the internal-mic ADC nor
the headset-mic node, force it onto the internal-mic ADC before the existing
setup code runs.
A second, related issue (defensive, unconfirmed on this model)
On the sibling model, the internal-mic ADC node also came up in HDA power
state D3 (suspended) after cold boot, failing capture with an I/O error
until forced to D0 via hda-verb ... 0x23 SET_POWER_STATE 0x0 — not
persistent across reboots. This repo includes a defensive systemd unit
(cs8409-intmike-power-fixup.service, running
overlay/usr/libexec/quine-linux/cs8409-intmike-power-fixup.sh) that logs
the power state and retries forcing D0 a few seconds after boot. Whether
iMac19,2 actually hits this at all is unconfirmed — its own journal output
after the real test boot is the evidence that will settle it.
Packaging approach
The fix is built into this repo's own bootc image rather than hand-patched
live on the running system, specifically to avoid a documented panic risk
(see Open risks) and to get free, cheap rollback via
bootc rollback/rpm-ostree rollback if anything goes wrong.
patches/snd-hda-codec-cs8409/vendor/— a verbatim copy of the 16 files from the pinned upstream commit (daviddoll/snd-hda-codec-cs8409@14e00de4290e090afd7632bc6d5c724131a19e7b), vendored rather than fetched at build time because this is an unmerged personal-fork branch that could be force-pushed or deleted with no notice. SeePROVENANCE.mdfor the full origin/license/rationale writeup (license:GPL-2.0-or-later, one fileGPL-2.0-only, per each file's own original SPDX header).patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch— the actual functional change, kept separate from the vendored copy so it's easy to re-diff if/when PR #30 lands upstream and this vendoring can be dropped.Containerfile— a newkmod-builderstage installs kernel headers matching the exact kernel shipped in theaurora:stablebase image (pinned by digest, shared between both stages via a singleARG, so they can't drift apart), applies the patch, and builds the module with the vendoredMakefile. The final stage copies the resulting.kointo/usr/lib/modules/<kernel-version>/extra/(the same convention Fedora's ownkmod-*subpackages use to override an in-tree module of the same name/modalias — no blacklisting or renaming needed) and re-runsdepmodagainst the final image's own module tree.- DKMS was deliberately not used: it exists to rebuild modules at
boot/install time when a kernel updates later on a mutable host. On this
bootc image, a new kernel only ever arrives bundled inside a freshly built
image, at which point the
kmod-builderstage reruns from scratch anyway — DKMS's rebuild-on-boot machinery is redundant weight here.
Current verification status
Not yet tested on real hardware as of 2026-09-26. The patch has been
verified to apply cleanly (git apply --check) and produce the intended
diff against the vendored source. The Containerfile has been written and a
local podman build sanity check (module compiles, vermagic matches the
target kernel) is the next step. Actually switching this machine to boot the
built image (bootc switch/rpm-ostree rebase + reboot) is a separate,
explicitly confirmed step, not yet taken, given this hardware/kernel/fix
combination is entirely unverified. This section will be updated with real
results (measured dB, Discord input-meter behavior, and whether the D3
power-state unit actually fired) once that test happens.
Open risks / unknowns
- Kernel API mismatch risk: the vendored fork targets Linux 6.17+ HDA
API; our kernel is 7.1.8. Untested whether it compiles cleanly without
further porting — but this fails safely at
podman buildtime, before ever touching the running system. - Unconfirmed on this exact model: the fix is confirmed only on
iMac18,1, not this iMac19,2/subsystem-id combination, even though the
intmike_adc_nidcode path is shared across both per direct source inspection. - D3 power-state issue unconfirmed on this model — only confirmed on the sibling; the defensive unit's journal is the intended way to settle this.
- Module search-path precedence (
extra/overriding in-treekernel/...) is asserted from documented Fedora/kmod convention, not yet verified on this image — checkmodinfo -F filename snd_hda_codec_cs8409after the real test boot. - Live-unbind panic risk: a live
unbind/rmmod/modprobe/bindcycle of this module while PipeWire has the sink active caused an unrecoverable freeze on the sibling model. This is exactly why all testing here goes through a full image build + reboot, never a live module reload. - Secure Boot non-goal: this approach assumes Secure Boot stays
disabled on this hardware. If it's ever enabled, this entire approach
breaks (an unsigned out-of-tree
.kogets rejected at load) and would need MOK enrollment + module signing added to the builder stage. - Personal-fork provenance risk: the ultimate origin of the 6.17+ compatibility work is an unmerged personal-fork PR (#30). Vendoring mitigates the "it could disappear" risk but doesn't change that this hasn't gone through upstream review.
quine-linux's ownrpm-ostree-cleanup.timerprunes the rollback deployment weekly — worth being aware of before testing an unverified change on this hardware, though no action has been taken on this yet.
References
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5314393975
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5338459155
- https://github.com/egorenar/snd-hda-codec-cs8409/pull/30
- https://github.com/daviddoll/snd-hda-codec-cs8409/tree/kernel-6.17-hda-compat
- https://github.com/rojoxstar/imac19-2-cs8409-linux (iMac19,2-specific project; primarily speaker/playback and Apple-DSP research, explicitly does not claim mic-capture support — investigated and set aside in favor of the fix above, which directly targets the observed symptom)