tl;dr — AirPods jump between your iPhone, iPad & Mac by juggling two Bluetooth links (Classic for audio, BLE for control), iCloud-synced keys, Continuity advertisements and a secret L2CAP channel (AAP). This post explains every layer and shows how to watch the magic with Python & Wireshark.
1 · Architecture in 90 seconds
Layer | What AirPods use | Why it matters |
---|---|---|
Bluetooth Classic | A2DP (music) · HFP (calls) | Actual audio |
Bluetooth LE | Custom service ➜ L2CAP PSM 0x1001 (AAP) |
Battery, sensors, ANC, control |
Continuity BLE ads | “Proximity Pairing” & device-activity beacons | Lets every Apple device know who’s doing what |
iCloud Keychain | Shares pairing keys across your Apple ID | Zero-config pairing on all devices |
H1/H2 chip | Fast link setup & encrypted state | Switch in ~1 s instead of normal 5–10 s |
The result: only one audio stream at a time, but the OS hands off ownership so quickly that it feels like multipoint.
2 · Continuity BLE: Finding Your Pods in the Ether
Apple devices broadcast & consume proprietary BLE packets (doc-type 0x0220 for AirPods).
Open a terminal and run:
sudo timeout 15s btmon | grep -i -A2 -B2 0x004c
You’ll catch lines like:
> ADV_IND, Apple, Inc. (0x004C), RSSI -43
22 20 19 aa bb cc dd ee ... # 0x2220 = AirPods advert
Fields decoded by Martin et al. (PETS 2020):
Byte(s) | Meaning |
---|---|
+0 -+1
|
Message type 0x0220
|
+4 -+6
|
Serial hash |
+7 |
Lid-open counter |
+8 |
Battery L |
+9 |
Battery R |
+10 |
Battery Case + flags |
Apple devices listen; when your Mac starts media playback it yells “I need the Pods!” and your iPhone politely disconnects.
3 · AAP: The Secret L2CAP Channel (0x1001)
Reverse-engineered by LibrePods.
3.1 Handshake
import socket, bluetooth # PyBluez
ADDR = "XX:XX:XX:XX:XX:XX" # AirPods MAC from `bluetoothctl`
PSM = 0x1001 # Apple Audio Protocol
sock = bluetooth.BluetoothSocket(bluetooth.L2CAP)
sock.connect((ADDR, PSM))
# Stage-1: 4-byte magic
sock.send(b'\x01\x00\x00\x00')
resp = sock.recv(64)
print("Handshake-1:", resp.hex())
Full spec: (see “AAP Definitions”).
3.2 Subscribing for notifications
# Tell Pods we want battery/in-ear updates
sock.send(bytes.fromhex("07 00 00 00 00 00 0A 00 01 00"))
while True:
pkt = sock.recv(128)
print("Notify:", pkt.hex())
A battery frame looks like:
08 00 07 00 <left%> <right%> <case%> <flags>
.
4 · What Actually Triggers a Switch?
Trigger | BLE/Wi-Fi Message | Priority |
---|---|---|
Incoming phone call | Telephony Continuity ad | 🚨 High |
Media play on other device | “Activity-level” Active=YES
|
Medium |
User taps device in AirPlay menu | UI event → OS forces takeover | Guaranteed |
Current host drops AAP & A2DP → target host connects in ~1 s.
No real “multipoint”: just lightning-fast hand-offs.
5 · Watch It Live: Python + Bleak
import asyncio, struct
from bleak import BleakScanner
APPLE_MFG = 0x004C
TYPE_AIRPODS = b'\x22\x20'
def parse_airpods(data: bytes):
serial = data[3:7].hex()
lid = data[7]
left, right, case = data[8], data[9], data[10]
return serial, lid, left, right, case
async def main():
async with BleakScanner() as scanner:
while True:
dev, adv = await scanner.get_discovered_device()
if adv and adv.manufacturer_data.get(APPLE_MFG, b'').startswith(TYPE_AIRPODS):
serial, lid, l, r, c = parse_airpods(adv.manufacturer_data[APPLE_MFG])
print(f"AirPods {serial} lid={lid} L={l}% R={r}% Case={c}%")
asyncio.run(main())
Hit Play on your Mac → watch the adverts stop (Pods connected), then resume from iPhone when call ends.
6 · Opening AAP on Linux (BlueZ ≥ 5.66)
# kernel must allow PSM 0x1001
echo 1 | sudo tee /sys/kernel/debug/bluetooth/allowed_psm/0x1001
python3 aap_client.py # from LibrePods
aap_client.py
dumps live events: ANC mode, in-ear detect, etc.
Great starting point if you’re building multipoint for Android (CAPod is doing exactly that).
7 · FAQ
Q · Can I keep two devices connected at once?
Not today. Only one A2DP stream. Apple optimises switching speed, not true multipoint.
Q · Why don’t W1 (1st-gen) Pods auto-switch?
The W1 lacks BLE packet-routing logic & enough RAM for Continuity states. H1/H2 only.
Q · Will clones work?
Many fakes replay the same Continuity advert, so the pop-up appears, but they can’t speak AAP or fast-switch.
8 · Further Reading & Credits
Resource | What you’ll find |
---|---|
“Handoff All Your Privacy” (PETS 2020) https://petsymposium.org/2020/files/papers/issue4/popets-2020-0067.pdf | Continuity advert dissections |
LibrePods https://github.com/kavishdevar/librepods | AAP definitions, sample clients |
CAPod https://github.com/d4rken-org/capod | A companion app for AirPods on Android. |
SoundGuys: H1 vs H2 deep dive https://www.soundguys.com/how-does-apple-h1-chip-work-21049/ | Chipset capabilities |
9to5Mac guide https://9to5mac.com/2020/09/27/disable-airpods-automatic-switching/ | How to disable auto-switch per device |
Disclaimer: Educational purposes only. Respect Apple’s terms and local laws when experimenting.
Top comments (0)