Skip to content

Protocol map for missing features: head tracking (AAP 0x17) solved, heart-rate pipeline mapped #713

Description

@IvanChanPing

Research: protocol map for the missing AirPods features (head tracking solved, heart-rate pipeline mapped)

I reverse-engineered the wire-level protocol for several of the features listed as missing, using the iOS 26.5 dyld_shared_cache and a cross-check against PodsLink's AAP implementation. Sharing the map so it can be built in LibrePods. No hardware was used.

1. Head-tracked spatial audio — fully mapped (AAP opcode 0x17)

The head-orientation stream is the same AAP/AACP transport LibrePods already speaks (L2CAP PSM 0x1001, frame prefix 04 00 04 00, little-endian uint16 opcode).

  • Start head tracking — send opcode 0x17 control packet:
    04 00 04 00 17 00 00 00 10 00 10 00 08 A1 02 42 0B 08 0E 10 02 1A 05 01 40 9C 00 00
  • Stop — the paired stop packet (same shape, disable body).
  • Sensor packets arrive with opcode 0x17, length >= 70, byte[8]==0x10, an orientation marker at byte 10.
  • Decode: three int16 orientation values o1/o2/o3 at payload byte offsets 43 / 45 / 47.
    • pitch = ((o2 + o3) / 2 / 32000) * 180, clamped to [-90, 90]
    • yaw = ((o2 - o3) / 2 / 32000) * 180, clamped to [-70, 70]
    • per-marker axis mapping / sign / swap applies (see PodsLink HeadTrackingOrientationDecoder).

This is enough to expose a TYPE_HEAD_TRACKER-style quaternion. Delivering it into the Android system spatializer needs a HAL/HID shim (root); a per-app OpenTrack/UDP feed needs no root.

2. Heart rate (AirPods Pro 3) — iOS pipeline mapped

On iOS the AirPods heart-rate sample is modeled as HRCHeartRateData and re-emitted as an IOHIDFamily HIDBiometricEvent. The construction selector is:

-[HRCHeartRateData initWithHeartRate:confidence:confidenceLevel:arbitrationStatus:context:hrContext:timestamp:sampleUuid:sourceType:streamingThrottleStatus:deviceUuid:device:sensorLocation:flags:] (impl in HeartRateCoordinator.framework)

Consumers/pipeline: HeartRateCoordinator -> HealthHeartRateStream / SeymourSessionServices (Apple's "Seymour" = AirPods sensor stack, e.g. makeHeartRateMetricStream) -> HealthKit. Standard BLE HR (HealthLEHeartRate) is a separate path and is not what AirPods use.

The remaining piece is the numeric AAP opcode + payload byte offset that carries the bpm. It is parsed by a cache-resident daemon in the Seymour/accessory-sensor stack. I'm tracing that now.

3. Others

  • Find My network: OpenBubbles' rustpush already solves report decrypt/auth; the blocker is extracting the AirPods offline-finding seed (distinct from the IRK/ENC proximity keys LibrePods reads today).
  • HQ two-way audio: iOS keeps A2DP for playback while receiving the mic stream over AACP (per LibrePods README). This is AACP-mic-RE feasible rather than an LE-Audio requirement.

I can open follow-up issues/PRs per feature. Head tracking is ready to implement from the spec above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions