Hearing assistance on Windows (AirPods Pro 3) — ATT/GATT unblocked, feature end-to-end - #8
Closed
arctumn wants to merge 3 commits into
Closed
Hearing assistance on Windows (AirPods Pro 3) — ATT/GATT unblocked, feature end-to-end#8arctumn wants to merge 3 commits into
arctumn wants to merge 3 commits into
Conversation
added 3 commits
August 10, 2026 19:25
…ts GATT
The hearing-aid audiogram lives on the AirPods' ATT/GATT (PSM 0x001F). The buds open
that channel INBOUND to the host, which bthport refuses ('PSM not supported') — and a
profile driver CANNOT register an L2CAP server on the reserved ATT PSM (BRB_L2CA_
REGISTER_SERVER returns STATUS_INVALID_PARAMETER 0xC000000D). The fix, matching Android's
ATTManager: OPEN the channel as a CLIENT (BRB_L2CA_OPEN_CHANNEL to PSM 0x001F) — the same
outbound path we use for AAP 0x1001. Connecting as a client to a reserved PSM is allowed;
the buds accept it and we then speak ATT.
Driver: LpConnectAtt (client open, called from LpConnect once the AAP link is up),
LpAttSend/LpAttReceive (BRB_L2CA_ACL_TRANSFER on the ATT channel) exposed via
IOCTL_LP_ATT_SEND/RECEIVE, LpCloseAttChannel on teardown, and ATT diagnostics surfaced
through IOCTL_LP_GET_STATUS (register/accept/channel-open status) since DebugView never
showed our KdPrint. The unused server-register/accept path is kept for reference.
Daemon: driver.rs att_send/att_recv/att_diag; run_receiver logs the ATT state to
daemon.log. Proven end-to-end: enabling hearing-assist (0x2C/0x33) then ATT-reading
handle 0x2A returns the 104-byte hearing-aid settings buffer (ATT Read Response 0x0B),
and CCCD notif-enable (0x2B) returns a Write Response (0x13). GATT discovery shows two
custom Apple services — no standard Heart Rate service, consistent with HR being AAP-only.
NB the current att_probe auto-enables hearing-assist + probes/discovers on every connect
— that is test scaffolding; the shipped feature needs a real SetHearingAid IPC command
and the audiogram write. Head-tracking (AAP 0x17, per upstream issue librepods-org#713) is a separate
promising lead, untested here.
Turn the proven ATT read/write into a real feature. New IPC command
Command::SetHearingAid { on, amplification, balance, conversation_boost }; daemon
hearing.rs applies it — enables hearing-assist over AAP (0x2C/0x33) then read-
modify-writes the ATT/GATT settings characteristic (handle 0x2A): patches per-ear
amplification (from amplification + balance), tone and conversation-boost as LE
f32 at the Android offsets, leaving the audiogram EQ bands untouched. Runs on its
own thread (the enable settle + ATT round-trips take >1 s). The connect-time
att_probe scaffolding is removed; the ATT status logging stays.
WinUI: Controls/HearingAidCard (on/off toggle + amplification/balance sliders +
conversation-boost, debounced), wired into DevicePage and DaemonClient.SetHearingAid;
localized in all four languages. Slider values map 0..100 -> 0..1 amplification and
-100..100 -> -1..1 balance.
Not yet hardware-validated (the ATT WRITE — only the read was proven; the AirPods
kept dropping late in the session); the enable + read path is confirmed working.
…fication is audible The ATT write succeeded but nothing was audible: the settings were written with a zero audiogram (nothing for the amplification to scale) and the buds weren't passing ambient. Now on enable we (1) switch noise control to Transparency (mode 3) so ambient sound comes through, and (2) synthesize a flat broadband audiogram across all 8 EQ bands per ear from the amplification slider. Confirmed audible on hardware — ambient noise is amplified and the tonal balance is clearly EQ-controllable (proving full audiogram control). BAND_GAIN (30) and the flat curve are first guesses; a natural hearing-aid curve (high-frequency emphasis) is the tuning left for next session.
Owner
Author
|
Superseded — the hearing-assistant work is folded into the native Windows stack in librepods-org#716. Closing. |
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.
Brings the AirPods Pro 3 hearing assistance (accessibility amplification) to the Windows client, end-to-end. Ready for review; one tuning step (the audiogram curve) is left for a follow-up.
The unlock
The hearing-aid audiogram lives on the AirPods' ATT/GATT (classic-Bluetooth L2CAP PSM 0x001F, handle
0x2A). The buds open that channel inbound to the host, whichbthportrefuses ("PSM not supported") — and a profile driver cannot register an L2CAP server on the reserved ATT PSM (BRB_L2CA_REGISTER_SERVER→STATUS_INVALID_PARAMETER).The fix, matching Android's
ATTManager: open the channel as a CLIENT (BRB_L2CA_OPEN_CHANNELto PSM 0x001F — the same outbound path we already use for AAP0x1001). Connecting as a client to a reserved PSM is allowed; the buds accept it and we then speak ATT.What works (confirmed on hardware)
0x2A): CCCD notify-enable returns a Write Response (0x13); reading returns the 104-byte settings buffer; writing returns0x13(write=ok).Controls/HearingAidCard— on/off + amplification/balance sliders + conversation-boost (debounced), wired intoDevicePage; localized in all four languages.Architecture
crossplatform/windows/drivers/aap/):LpConnectAtt(client open),LpAttSend/LpAttReceive(BRB_L2CA_ACL_TRANSFER on the ATT channel) viaIOCTL_LP_ATT_SEND/RECEIVE,LpCloseAttChannel, and ATT diagnostics throughIOCTL_LP_GET_STATUS(DebugView never surfaced the driver's KdPrint, so status is read back in user mode). The dead server-register path is kept for reference.daemon/src/hearing.rs):Command::SetHearingAid { on, amplification, balance, conversation_boost }→ enable hearing-assist over AAP (0x2C/0x33) → Transparency → ATT read-modify-write of handle0x2A(per-ear amplification from amplification+balance, a flat EQ audiogram, conversation-boost).SetHearingAidCmdDTO + the card.Left for follow-up
BAND_GAIN = 30sounds bass-heavy; a natural hearing-aid curve (high-frequency emphasis) needs a couple of hardware iterations.Not in scope