Skip to content

Windows: native hi-res AirPods microphone (AAC-ELD virtual mic) - #1

Merged
arctumn merged 48 commits into
cross-platformfrom
windows-hires-mic
Aug 7, 2026
Merged

Windows: native hi-res AirPods microphone (AAC-ELD virtual mic)#1
arctumn merged 48 commits into
cross-platformfrom
windows-hires-mic

Conversation

@arctumn

@arctumn arctumn commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Native Windows hi-res AirPods microphone 🎙️

Exposes the AirPods' hi-res AAC-ELD microphone as a native Windows input,
so any app (Teams, Zoom, Discord, OBS…) can use it — the Windows counterpart to
the Linux side's PR #655,
built with a self-contained virtual audio driver instead of PipeWire.

Validated end-to-end on real AirPods Pro (voice recorded clean & in tune).

What's in it

New driver — LibrePodsMic (windows-driver/LibrePodsMic/): a capture-only
virtual audio device (ACX, based on the MS AudioCodec sample). A spin-locked PCM
ring + control device \\.\LibrePodsMic (IOCTLs for PCM write + a capture-activity
counter) lets user mode feed it audio.

Tray pipeline (windows-app/librepods-tray/): reads the AAP 0x58 uplink
packets, decodes AAC-ELD via an FFmpeg/libavcodec C shim (LGPL), resamples
64 kHz → 48 kHz, and streams PCM into the driver.

Plug-and-play polish (Phase 4):

  • Auto-activate — the hi-res stream turns on when an app records from the mic
    and off (debounced) when it stops, via the driver's capture counter. + manual mode.
  • A2DP auto-reset — mic mode degrades playback to mono; we toggle the AirPods'
    AudioSink service to restore stereo when the mic stops.
  • Make-up gain — ×3 with a tanh soft-limit (the mic ran quiet).
  • Dynamic mic name — the mic shows the connected device's name
    ("AirPods Pro de …", and Beats too), set automatically with no UAC prompt
    via lp-mic-rename + an elevated on-demand scheduled task the tray triggers.
  • Minimal FFmpeg — rebuilt aac-only: avcodec 69 MB → 0.7 MB.
  • Single-instance guard; fixed a receive-loop CPU spin that caused audio crackle.

Key reverse-engineering findings

  • Mic frames are 480 samples @ 64 kHz (the 4-byte ASC lies; confirmed by the
    AU timestamp = a 24 kHz clock over 7.5 ms frames) — decoding as 48 k played deep
    & clicky.
  • The Sound "Rename" UI persists the name in PKEY_Device_DeviceDesc as a plain
    REG_SZ string
    (not FriendlyName, not a REG_BINARY PROPVARIANT blob) — that was
    the last blocker for the dynamic name.

Notes

  • Big diff: includes the imported ACX driver sample + vendored minimal FFmpeg libs.
  • Linux is untouched; all changes are under crossplatform/ Windows paths.
  • Docs: crossplatform/README.md (regrouped + a hi-res mic section),
    crossplatform/hires-mic/PLAN.md.

arctumn and others added 30 commits August 6, 2026 17:23
…phone

New feature branch (off cross-platform, merges back when ready) to expose the
AirPods' hi-res AAC-ELD mic as a native Windows microphone via our own virtual
audio driver (SYSVAD-based) instead of a third-party VB-Cable — the Windows
counterpart to Linux PR librepods-org#655. crossplatform/hires-mic/PLAN.md lays out the
architecture (AAP driver -> app decode -> LibrePodsMic virtual driver -> Windows
mic), the incremental phases, risks (audio-driver complexity, AAC-ELD patents),
and references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Codec) — builds

Import the Microsoft ACX AudioCodec sample (MIT, from Windows-driver-samples
audio/Acx/Samples) as the base for the virtual microphone driver: a
ROOT-enumerated (software, no-hardware) audio device with a capture circuit,
built on ACX over KMDF — the same framework family as LibrePodsAAP (vs SYSVAD's
PortCls). Confirmed it BUILDS with VS2026 + WDK 28000 (AudioCodec.sys, EXIT 0) —
resolving the biggest Phase-1 unknown. README notes origin/license + status.
Next: install the ROOT device + verify a virtual mic appears, then trim to
capture-only and add the PCM bridge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iver

Test-signs the built AudioCodec.sys, generates+signs a catalog (inf2cat), trusts
a test cert, and creates the ROOT\AudioCodec device via devcon so a virtual
microphone endpoint appears. Elevated + Test Mode, like the AAP driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PowerShell read the UTF-8 em dash as garbage and broke string parsing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dows

Confirmed on hardware: install.ps1 signs+catalogs the ACX driver and creates the
ROOT\AudioCodec device via devcon; Windows shows 'Microphone (AudioCodec Device)'
in Sound -> Input. The virtual-mic foundation is proven. Next: Phase 2 (IOCTL PCM
bridge to feed the AirPods' decoded audio into the capture circuit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Device.cpp: don't create/add/remove the render (speaker) circuit — LibrePodsMic
is a virtual *microphone*, so exposing a render endpoint only risked Windows
grabbing the default output (the brief static on install) and cluttered the
device list. Capture circuit only. Builds clean.

install.ps1: `devcon remove ROOT\AudioCodec` before install so re-running updates
in place instead of stacking a second virtual device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The driver's ACL read (IOCTL_LP_RECEIVE) completes immediately with 0 bytes when
the channel is idle (ACL_SHORT_TRANSFER_OK), so the background receive loop was
spinning a core (~7.5% CPU, measured) and, worse, flooding the Bluetooth stack
with thousands of back-to-back ACL reads per second — which contends with the
A2DP audio stream and is the likely source of the crackle/static.

Yield the CPU (150 ms) whenever a read returns no data, so the loop can't spin.
Pushed events (battery / ANC / ear-detection) still arrive within ~150 ms since
we only sleep on an empty read. Measured after: 0.1% CPU.

A fully event-driven (async, kernel-pended) read would take idle reads to zero,
but that's a driver change (reinstall + reboot); this gets ~99% of the benefit
without touching the signed driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add MicPipe: a global byte ring buffer (spin-locked, single-instance mic) that
user mode fills with decoded PCM and the ACX capture stream engine drains one
packet per notification tick.

- Common/MicPipe.{h,cpp}: the ring + a driver-scoped control device
  (\\.\LibrePodsMic) exposing IOCTL_LIBREPODS_MIC_WRITE_PCM (0x0022A000,
  METHOD_BUFFERED). Write appends (drops oldest on overflow); Read fills a
  packet, zero-filling underrun. Read is DISPATCH_LEVEL-safe (non-paged, spin
  lock). World RW SDDL so a non-elevated app can push audio.
- StreamEngine.cpp ProcessPacket: fill the capture packet from MicPipeRead
  instead of the sample's WAV-file / tone dummy sources.
- Device.cpp: MicPipeInit + MicPipeCreateControlDevice at device add
  (best-effort; never fails enumeration).
- SamplesCommon.vcxproj: compile MicPipe.cpp.

Builds clean (EXIT 0). Format is mono 16-bit PCM at 44100/48000 Hz (whatever the
client opens the capture endpoint with). Next: user-mode feeder to validate the
pipe end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A tiny user-mode tool that opens \\.\LibrePodsMic and streams a 440 Hz sine
(mono 16-bit, 48 kHz by default) via IOCTL_LIBREPODS_MIC_WRITE_PCM, in real-time
10 ms chunks. Record from "Microphone (AudioCodec Device)" to hear it — proves
the user-mode -> IOCTL -> ring -> ACX capture path end to end. Raw kernel32 FFI,
no deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bridge proven end-to-end: lp-mic-test's 440 Hz sine recorded and audible on the
virtual mic, clean waveform with a single feeder. Phase 3 (AAP enable-mic +
AAC-ELD decode from PR librepods-org#655) is next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A virtual mic has a single audio source. Two processes writing the ring
concurrently interleave and sound like static (seen while testing with multiple
tone feeders running at once). WdfDeviceInitSetExclusive refuses a second open,
so this class of corruption can't happen — the right behavior for Phase 3 where
the app opens \\.\LibrePodsMic once and feeds the decoded AirPods audio.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ds')

Standalone tool that finds the AudioCodec capture endpoint and sets its
PKEY_Device_FriendlyName via IPolicyConfig (CPolicyConfigClient) — user-mode, no
admin, no reboot. So the virtual mic can show "AirPods Pro de Pedro" instead of
the driver's static "CustomName2 (AudioCodec Device)" in Discord/Teams/Zoom.
The app will call this at runtime in Phase 4; this proves the mechanism.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…an B)

The IPolicyConfig runtime path (lp-mic-rename) crashed on Win11 (the interface
vtable layout varies by Windows version). This is the reliable fallback: write
PKEY_Device_FriendlyName on the AudioCodec capture endpoint in the registry
(elevated) and restart AudioEndpointBuilder to apply without a reboot — the same
mechanism SoundVolumeView uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oints

MMDevices property values are REG_BINARY (4-byte type tag + UTF-16), not plain
strings, so the first version never matched. Decode/encode the PROPVARIANT
properly, match on decoded DeviceDesc OR FriendlyName, and list all capture
endpoints for visibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow the Windows theme instead of a fixed dark look:
- theme.rs: read AppsUseLightTheme / SystemUsesLightTheme from the registry.
- Overlay: restyle like the Win11 volume/brightness flyout — native DWM rounded
  corners + immersive dark-mode chrome + a subtle border, and a theme-aware fill
  (dark card/light text in dark mode, light card/dark text in light mode).
  Replaces the color-key rounding hack. Re-reads the theme on each show.
- Tray icon: draw the battery % digits dark on a light taskbar, white on a dark
  one (SystemUsesLightTheme) — otherwise white digits vanished on a light taskbar.

Note: the flyout's translucent acrylic *blur* isn't included (that needs
per-pixel-alpha layered compositing); this matches the shape, color, and theme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Overlay: add the Win11 acrylic backdrop (DWMWA_SYSTEMBACKDROP_TYPE =
  DWMSBT_TRANSIENTWINDOW) with the frame extended across the client, painting the
  client "glass" (black) so the translucent material shows through. Falls back to
  the solid theme card on OSes that don't support it (hr < 0).
- Context menu: Win32 menus ignore dark mode by default; opt in via the
  undocumented uxtheme SetPreferredAppMode (135) + FlushMenuThemes (136), keyed
  to the app theme (theme::apply_menu_theme), applied at startup and each refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The acrylic backdrop washed the card out and muddied the corner; the solid
theme-aware card with native rounded corners + border looks cleaner. Keep the
rounded corners, theme-following colors, and the dark/light context menu; drop
only the acrylic backdrop + glass fill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the AAP hi-res mic start/stop commands (from PR librepods-org#655) and a "Hi-res
microphone (test)" menu toggle. On enable, send START_AUDIO over the existing
AAP session; the receive loop counts the 0x58 uplink audio packets and pops a
"receiving audio" card once the stream is confirmed flowing. No decode yet — this
proves the enable command works on real AirPods before investing in the AAC-ELD
(FFmpeg) decode. aap.rs: START_AUDIO/STOP_AUDIO + is_audio_packet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tray now turns the AirPods' hi-res mic into real audio: enable → decode →
virtual mic. Wired end to end.

- eld_shim.c + build.rs: a tiny C shim over FFmpeg libavcodec (LGPL, native
  AAC-ELD) behind a stable ABI (eld_open/decode/close), compiled via cc against
  vendored FFmpeg headers and linked to the import libs. (libfdk-aac is non-free
  and won't cross-compile for mingw64, so FFmpeg native like PR librepods-org#655.)
- eld.rs: Rust wrapper; opens the decoder with the AirPods ASC F8 E6 30 00
  (AOT 39 ELD, 48 kHz mono), decodes AUs to i16.
- micpipe.rs: writes decoded PCM into \\.\LibrePodsMic (IOCTL 0x0022A000).
- aap.rs for_each_au: parse the 0x58 packet's AUs (22-byte header, len at off+4).
- main.rs: on the "Hi-res microphone" toggle, the receive loop decodes each 0x58
  packet's AUs and streams the PCM to the virtual mic.
- vendor/ffmpeg: committed headers + import libs; the ~75 MB runtime DLLs are
  gitignored (fetched, shipped alongside the exe).

Builds + runs on hardware (FFmpeg DLLs load). Pending: on-hardware voice test,
plus A2DP reset (playback drops to right-only mono while the mic is active) and
the 2s stall watchdog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nore)

The vendored .dll.a import libs are needed to link the AAC-ELD shim; the repo's
lib/ ignore rule was hiding them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AAC-ELD decoder outputs at its own rate (voice ELD is typically 24 kHz), but
we fed those samples into the 48 kHz capture endpoint -> playback ran ~2x slow
(deep 'censored voice'). Resample the decoded audio (whatever rate) to 48 kHz
mono s16 via libswresample before feeding the mic pipe, so the pitch is correct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… pitch)

Diagnosed from the user's recordings: the virtual mic was captured at 44100 Hz
(Voice Recorder's pick) while we feed audio resampled to 48000 Hz, so playback
ran ~8% slow → deep "robotic" voice (duration ratio 11.91/11.05 = 1.078 ~=
48000/44100). Drop the 44100 format from the capture circuit so every app opens
at 48000, matching our feed. (eld_shim: add eld_in_rate diagnostic helper.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Launching the tray while a copy was still starting left two icons fighting over
the single-open driver and the exclusive mic pipe. Bail on startup if the named
mutex already exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…licks)

Diagnosed via a dump of our decoded PCM: the decode is clean (no clicks), so the
periodic clicks in the recording came from the ring, not the codec. We feed in
per-packet bursts (~80 ms of audio at once) while the capture endpoint drains
steadily, so with no head start the ring underran between bursts — a click per
packet. Prime the ring with ~150 ms of silence on start-up to absorb the
burstiness. Also decode a whole packet's AUs and write once. Removed the debug
packet/PCM dumps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AirPods hi-res mic AAC-ELD frames are 480 samples at 64 kHz (7.5 ms), but the
4-byte ASC advertises samplingFrequencyIndex 3 (48000), so FFmpeg labels them
48000. Confirmed by the 0x58 AU timestamps incrementing +180 per frame — a
24 kHz clock over 7.5 ms frames (7.5ms * 24000 = 180) — and by the measured deep
pitch (~1.33x). Feeding 64 kHz content as 48 kHz played it ~0.75x slow (deep)
AND overfed the ring (the clicks). Resample from the true 64 kHz -> 48 kHz, which
fixes both at once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…are)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hi-res mic puts the AirPods into a bidirectional "call" mode that degrades
A2DP playback to mono/right and stays that way until reconnected (the user was
restarting Bluetooth each time). On mic-off, toggle the AirPods' A2DP service
(BluetoothSetServiceState disable→enable, off the UI thread) to reconnect the
audio link and restore stereo — the scoped, programmatic equivalent of
disconnect+reconnect. Best-effort (silently ignored if it needs elevation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing)

The A2DP audio service the AirPods actually expose is AudioSink (0x110B), not
AdvancedAudioDistribution (0x110D) — toggling 0x110D returned ERROR 1060
(service not installed). Enumerating the device's installed services found the
right GUID. Toggling 0x110B disable→enable (with a 1 s settle after the mic stop
and a 2 s teardown before reconnect) reliably restores stereo — no Bluetooth
restart needed. Removed the debug service-dump and the result-code overlay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
arctumn and others added 18 commits August 6, 2026 21:30
Measured from the user's recordings: the AirPods mic was ~4 dB quieter than a
reference with ~22 dB of headroom, and the 150 ms ring cushion added noticeable
start latency + clipped the tail on stop. Add a x3 (~+9.5 dB) make-up gain on the
decoded float (before swr, so loud transients clamp cleanly) and drop the cushion
to 80 ms (still covers the ~30 ms per-packet bursts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The x3 gain sounded blown out on loud transients (peaks hit -7.8 dB and louder
ones hard-clipped). Pass the gained float through tanh: near-linear x3 for normal
speech, smooth saturation on peaks instead of harsh clipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Driver: add a capture-activity counter (advances on each MicPipeRead) exposed via
IOCTL_LIBREPODS_MIC_STATUS (0x00226004). Tray: open the exclusive mic-pipe handle
once and share it between the receive thread (writes) and a new poll thread. The
poll reads the counter every 500 ms; when an app starts recording from the
virtual mic it auto-sends START_AUDIO (hi-res on), and when it stops (debounced
~1.5 s) it sends STOP_AUDIO + the A2DP stereo reset. No manual toggle needed:
replaced the "Hi-res microphone (test)" menu item with an auto-updating
"Microphone: idle / recording" status line. Needs a driver reinstall for the new
IOCTL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cut the settle+teardown from 1s+2s to 0.4s+1.4s so the stereo-restore reconnect
is a ~1.8 s blip instead of ~3 s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- a2dp reset trimmed to 150 ms settle + 1000 ms teardown (shorter playback blip;
  the reconnect handshake itself is Windows/BT and can't be sped up).
- Menu: "Auto-enable on recording" checkbox (default on) + a "Hi-res microphone
  (manual)" override. In manual mode the poll stops auto-managing and the user
  drives the mic directly (with the same STOP + stereo reset on off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Manual mic-off now uses the same 1.5 s debounce as auto (keeps decoding the
  tail before STOP), so it no longer clips the end of a recording.
- Show "Restoring stereo…" through the whole A2DP reconnect and "Stereo restored"
  when it finishes (both auto and manual), and bump the overlay auto-hide to
  4.5 s so the card stays up until the switch completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nect

Manual mic-off debounce 1.5s -> 3.5s (more tail margin, per feedback). a2dp reset
now waits ~1.5s after ENABLE for the reconnect handshake before returning, so the
'Restoring stereo...' card stays up through the whole reconnect and 'Stereo
restored' lands when the stereo is actually back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the BtbN full FFmpeg DLLs with a minimal FFmpeg 7.1 build (LGPL, only the
AAC decoder: --disable-everything --enable-decoder=aac...). avcodec 69 MB -> 0.7
MB; the three DLLs total ~1.6 MB (was ~73 MB), small enough to commit so the repo
is self-contained. Re-vendored include/ + import libs + DLLs (avcodec-61,
avutil-59, swresample-5); the tray relinks against them unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the capture endpoint from the sample's "CustomName2 (AudioCodec Device)"
to "LibrePods" (+ DeviceDesc "LibrePods", FriendlyName "LibrePods Microphone").
A device-agnostic static name — correct whether the user has AirPods, Beats, or
any Apple-protocol accessory. The exact per-device dynamic name (matching the
connected device) needs the IPolicyConfig runtime rename (crashes on Win11 without
a debugger to fix the vtable) — left as a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eds bFxStore)

The AV was the SetPropertyValue signature: it takes a leading BOOL bFxStore
(PCWSTR, BOOL, const PROPERTYKEY&, PROPVARIANT*), not the 3-arg form. With that
fixed the call returns cleanly — HRESULT 0x80070005 (E_ACCESSDENIED): renaming an
audio endpoint's friendly name needs elevation. So the rename works; it just has
to run elevated (a scheduled task will automate it without UAC). Also match the
new "LibrePods" mic name + log each step to a file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IPolicyConfig::SetPropertyValue(FriendlyName) returns E_ACCESSDENIED even
elevated (the property is protected against that path), so the registry write is
the reliable rename. Update the endpoint match for the new 'LibrePods' name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…SSDENIED)

IPolicyConfig::SetPropertyValue(FriendlyName) returns E_ACCESSDENIED even when
elevated, so write the endpoint's PKEY_Device_FriendlyName registry blob directly
(VT_LPWSTR: 0x1F 00 00 00 + UTF-16LE name + NUL) under
MMDevices\Audio\Capture\{endpoint}\Properties, then restart AudioEndpointBuilder.
The endpoint GUID comes from IMMDevice::GetId (trailing brace group). Must run
elevated. This is the reliable, byte-controlled rename (vs the finicky PowerShell
blob decode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The registry FriendlyName write was correct (verified: reads back
'AirPods Pro de Pedro'), but reinstalls leave several stale LibrePods
capture endpoints and the tool renamed only the first match — a stale
one, not the active endpoint Windows shows. Now it matches on the
constant DeviceDesc and renames every LibrePods endpoint, so the active
one is always covered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two fixes made the rename finally show in Sound/Discord:
1. Format: the Sound UI's Rename writes PKEY_Device_DeviceDesc ({a45c254e},2)
   as a plain REG_SZ string, NOT a REG_BINARY PROPVARIANT blob (which Windows
   ignores -> display falls back to the INF name). We now write REG_SZ.
2. Matching: identify our endpoint by STABLE, name-independent properties
   (device name {b3f8fa53},6 == 'LibrePods' from the INF, or hardware id
   {a8b865dd},8 contains 'AudioCodec') instead of the display name, so re-runs
   still find it after a rename.

Also: skip endpoints already named correctly and only restart AudioEndpointBuilder
when something changed (no needless audio blip). Dropped the COM/IMMDevice
dependency -> pure registry, smaller binary. Confirmed on hardware: the mic now
shows 'AirPods Pro de Pedro' in Sound > Input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tray now renames the virtual mic to whatever it's connected to, with no
UAC prompt:
- bt::find_airpods matches Beats too (Powerbeats/Fit Pro/Studio Buds/...), not
  just AirPods, since they share the AAP chip/endpoint -> dynamic name.
- New rename module: on launch it writes the device name to
  %LOCALAPPDATA%\LibrePods\micname.txt and fires an elevated on-demand scheduled
  task (schtasks /run) that runs lp-mic-rename with highest privileges.
- lp-mic-rename reads the name from that file when no CLI arg is given, and is
  idempotent (skips + no service restart when already named), so firing it every
  launch is cheap.
- install.ps1 copies lp-mic-rename.exe and registers the 'LibrePods Rename Mic'
  task (RunLevel Highest, interactive user, on-demand).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganize crossplatform/README.md by category (Control & status / Audio /
App & UX), add a dedicated hi-res microphone section (LibrePodsMic virtual
audio driver, AAC-ELD decode, auto-activate, A2DP reset, gain, dynamic name,
minimal FFmpeg), add the microphone row to the platform table, mention the
second driver + lp-mic-rename in the layout, and add a driver-signing note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@arctumn
arctumn merged commit 43ecf11 into cross-platform Aug 7, 2026
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.

1 participant