# Technical Documentation: librepods-org/librepods

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/librepods-org/librepods)

### Overview (`README.md`)

> [!WARNING]
> librepods.org is not an official website of the LibrePods project. It inaccurately claims to be the official website of the project by claiming copyrights and using the LibrePods logo in the footer. And at the same time, they say that the project is not affiliated with the LibrePods project or its developers.
> 
> Please report any other such websites to [me@kavish.xyz](mailto:me@kavish.xyz)
 
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="./imgs/banner-dark.png" />
  <source media="(prefers-color-scheme: light)" srcset="./imgs/banner.png" />
  <img alt="LibrePods" src="./imgs/banner.png" />
</picture>

<div align="center" style="margin: 20px 0px;">
<a href="https://github.com/kavishdevar/librepods/releases/latest">
  <img src="https://img.shields.io/github/downloads/kavishdevar/librepods/total?label=GitHub%20Downloads" />
</a>
<a href="https://github.com/kavishdevar/librepods/actions/workflows/ci-android.yml">
  <img src="https://github.com/kavishdevar/librepods/actions/workflows/ci-android.yml/badge.svg" />
</a>
<a href="https://github.com/kavishdevar/librepods/actions/workflows/ci-linux-rust.yml">
  <img src="https://github.com/kavishdevar/librepods/actions/workflows/ci-linux-rust.yml/badge.svg" />
</a>
<a href="https://github.com/kavishdevar/librepods/issues">
  <img src="https://img.shields.io/github/issues/kavishdevar/librepods" />  
</a>
<a href="https://discord.gg/HhG4ycVum4">
  <img src="https://img.shields.io/discord/1441416992027574375?logoColor=white&color=5865F2&label=Discord" />
</a>
</div>

# What is LibrePods?

LibrePods allows you to use AirPods features that are exclusive to Apple devices. It implements the proprietary protocol used to exchange data between AirPods and Apple devices, enabling features like changing noise control modes, fast ear detection, accurate battery status, head gestures, conversational awareness, and more on non-Apple platforms.

# Feature availability

| Feature                                                     | Linux | Android |
| ----------------------------------------------------------- | ----- | ------- |
| Changing Listening Mode                                     | ✅     | ✅       |
| Ear detection                                               | ✅     | ✅       |
| Battery status                                              | ✅     | ✅       |
| Renaming AirPods <details><summary>Note for Android</summary>On Android, you need to re-pair your AirPods after renaming them because Android might not use the latest name.</details>                                            | ✅     | ✅       |
| Loud Sound Reduction                                        | 🔴     | ⚪       |
| Head Gestures                                               | ⛔     | ✅       |
| Conversational Awareness                                    | ✅     | ✅       |
| Automatically connect to AirPods                            | ✅     | ✅       |
| Hearing Aid                                                 | 🔴     | ⚪       |
| Transparency Mode customization                             | 🔴     | ⚪       |
| Multi-device connectivity (Bluetooth Multipoint; 2 devices only) | ⚪     | ⚪       |
| <details><summary>Other accessibility configs (click to expand)</summary><ul><li>Press speed</li><li>Press and Hold duration</li><li>Noise Cancellation with single AirPod</li><li>Volume control on swipe</li><li>Volume swipe speed</li></ul></details>       | 🔴     | ✅       |
| <details><summary>Other general configs</summary><ul><li>Press and Hold to cycle between listening modes/invoke digital assistant (invoking digital assistant needs a recent firmware)</li><li>Configure call controls</li><li>Personalized volume</li><li>Loud Sound Reduction (needs <a href="#vendorid-spoofing">VendorID spoofing</a>)</li><li>Microphone side</li><li>Pause media when falling asleep (needs a recent firmware)</li><li>Enable <code>Off listening mode</code> to switch to <code>Off</code></li></ul></details>                   | 🔴     | ✅       |
| [Head-tracked Spatial Audio](#spatial-audio)                | ❓     | ❓       |
| [Heart Rate Monitoring](#heart-rate-monitoring)             | 🔴     | 🟡       |
| [Find My](#find-my)                                         | ❓     | ❓       |
| [High quality two-way audio](#high-quality-two-way-audio)   | 🟡     | 🟡       |

| Symbol | Meaning                                                             |
| ------ | ------------------------------------------------------------------- |
| ✅     | Implemented and works well                                          |
| ⚪     | Needs [VendorID spoofing](#vendorid-spoofing); use at your own risk |
| 🟡     | Experimental/Work in progress                                       |
| 🔴     | Not implemented yet; planned                                        |
| ⛔     | Will not be implemented                                             |
| ❓     | Unknown                                                             |

## Find My

The following features related to Find My are planned, but require further RE and might need root on Android:

- Add your AirPods to the Find My network
- Play sound through charging case to find it
- Notify when leaving behind
- Toggle case charging sounds

## Spatial Audio

The app does not currently provide head tracking information to Android for the OS to perform HRTF. This has not been explored completely, and it might need root. 

Spatializing stereo sound is beyond this project's scope and will never be available. Many OEMs have an implementation of their own for this.

## Heart Rate Monitoring

### Android

Available in the `android/rewrite` branch. [builds](https://github.com/librepods-org/librepods/actions/workflows/ci-android.yml?query=branch%3Aandroid%2Frewrite)

### Linux

Not available yet (doesn't seem like something that would be useful on a computer anyway. If you have any suggestions, create a discussion.)

## High quality two-way audio
On iOS/iPadOS, you can continue using A2DP while AirPods send the hi-res (AAC-ELD mono 64kHz?) audio stream from its microphone over AACP.

### Android
Record audio within the app using the AirPods' microphone without switching to Headset/Handsfree profile degrading the playback quality. But, making this available to other apps/telephony might need root, if possible at all.

Recorder is work in progress; available in the `android/rewrite` branch. [builds](https://github.com/librepods-org/librepods/actions/workflows/ci-android.yml?query=branch%3Aandroid%2Frewrite)

### Linux

Check out [PR #655](https://github.com/librepods-org/librepods/pull/655).

# Installation

- [**Android**](/android/README.md)
- [**Linux**](/linux/README.md)

# VendorID Spoofing

Turns out, if you change the VendorID in DID Profile to that of Apple, you get access to several special features!

You can do this on Linux by editing the DeviceID in `/etc/bluetooth/main.conf`. Add this line to the config file `DeviceID = bluetooth:004C:0000:0000`. For android you can enable the `act as Apple device` setting in the app's settings (shown only when Xposed is available and LibrePods module is enabled).

## Multi-device Connectivity

Upto two devices can be simultaneously connected to AirPods, for audio and control both. Seamless connection switching. The same notification shows up on Apple device when Android takes over the AirPods as if it were an Apple device ("Move to iPhone"). Android also shows a popup when the other device takes over.

## Accessibility Settings and Hearing Aid

Accessibility settings like customizing transparency mode (amplification, balance, tone, conversation boost, and ambient noise reduction), and loud sound reduction can be configured.

All hearing aid customizations can be done from Android (linux soon), including setting the audiogram result. The app doesn't provide a way to take a hearing test because it requires much more precision. It is much better to use an already available audiogram result. 

# Protocol and Reverse Engineering

Please refer to the Wireshark dissector plugin by Nojus ([@pabloaul](https://github.com/pabloaul)) for more information on the protocols used: [pabloaul/apple-wireshark](https://github.com/pabloaul/apple-wireshark)

The dissector had not been used in LibrePods for most of the implementation; I had reverse engineered the protocol myself before this dissector was made. But many (future) features including two-way high quality audio and spatial audio would not have been possible without their RE efforts!

# Use of AI

## Android app

These parts of the app were completely AI-generated: 
- Head Gestures - all of it, including logic and the UI
- The offset setup with r2+the xposed module (both versions)
- Troubleshooter and LogCollector

Rest everything- the background service, the Bluetooth manager classes (AACP and ATT), the entire UI, even the smallest components were written manually.

Some parts of the UI components were borrowed from [Kyant0's demo app](https://github.com/Kyant0/AndroidLiquidGlass/tree/master/catalog), which is licensed under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).

## Linux (rewrite)

The `aacp.rs` and the `att.rs` files were translated from Kotlin to Rust with AI. Some parts of the `media_controller.rs` file, mainly the pulse integration, was also AI-generated.

# Supporters

A huge thank you to everyone supporting the project!

<table>
  <tr>
    <td align="center">
      <a href="https://github.com/davdroman">
        <img src="https://github.com/davdroman.png?size=48" width="48" height="48"alt="davdroman"/><br />
        @davdroman
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/tedsalmon">
        <img src="https://github.com/tedsalmon.png?size=48" width="48" height="48"alt="tedsalmon"/><br />
        @tedsalmon
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/wiless">
        <img src="https://github.com/wiless.png?size=48" width="48" height="48"alt="wiless"/><br />
        @wiless
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://github.com/SmartMsg">
        <img src="https://github.com/SmartMsg.png?size=48" width="48" height="48"alt="SmartMsg"/><br />
        @SmartMsg
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/lunaroyster">
        <img src="https://github.com/lunaroyster.png?size=48" width="48" height="48"alt="lunaroyster"/><br />
        @lunaroyster
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/ressiwage">
        <img src="https://github.com/ressiwage.png?size=48" width="48" height="48"alt="ressiwage"/><br />
        @ressiwage
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://github.com/kkjdroid">
        <img src="https://github.com/kkjdroid.png?size=48" width="48" height="48"alt="kkjdroid"/><br />
        @kkjdroid
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/CitrusJoules">
        <img src="https://github.com/CitrusJoules.png?size=48" width="48" height="48"alt="CitrusJoules"/><br />
        @CitrusJoules
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/DanielReyesDev">
        <img src="https://github.com/DanielReyesDev.png?size=48" width="48" height="48"alt="DanielReyesDev"/><br />
        @DanielReyesDev
      </a>
    </td>
  </tr>
  <tr>
    <td align="center">
      <a href="https://github.com/sumitduster">
        <img src="https://github.com/sumitduster.png?size=48" width="48" height="48"alt="sumitduster"/><br />
        @sumitduster
      </a>
    </td>
    <td align="center">
      <a href="https://github.com/GrifTheDev">
        <img src="https://github.com/GrifTheDev.png?size=48" width="48" height="48"alt="GrifTheDev"/><br />
        @GrifTheDev
      </a>
    </td>
  </tr>
</table>

# Special Thanks
- @tyalie for making the first documentation on the protocol! ([tyalie/AAP-Protocol-Definition](https://github.com/tyalie/AAP-Protocol-Defintion))
- @rithvikvibhu and folks over at lagrangepoint for helping with the hearing aid feature ([gist](https://gist.github.com/rithvikvibhu/45e24bbe5ade30125f152383daf07016))
- @devnoname120 for helping with the first root patch
- @timgromeyer for making the first version of the linux app
- @hackclub for hosting [High Seas](https://highseas.hackclub.com) and [Low Skies](https://low-skies.hackclub.com)!
- Of course, everyone who has contributed to the project in any way, including by testing, sharing feedback, or just showing interest!

# Alternates for other platforms:
- CAPod - A companion app for AirPods on Android. ([play store](https://play.google.com/store/apps/details?id=eu.darken.capod) | [source code](https://github.com/d4rken-org/capod)). Use this if you're using Android version 16 QPR3 or below and are not rooted.
- MagicPods for Steam Deck ([website](https://magicpods.app/steamdeck/))
- MagicPods - if you're looking for "LibrePods for Windows"  ([ms store](https://apps.microsoft.com/store/detail/9P6SKKFKSHKM) [installer](https://magicpods.app/installer/MagicPods.appinstaller) | [website](https://magicpods.app/))

# Star History

<a href="https://www.star-history.com/?repos=librepods-org%2Flibrepods&type=date&legend=top-left">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&theme=dark&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
   <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
 </picture>
</a>

# License

LibrePods - AirPods liberated from Apple’s ecosystem
Copyright (C) 2025 LibrePods contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

# Trademark Notice

The GPL does not grant any rights to use the LibrePods name, logo, or branding. The LibrePods name and logo may not be used for software, websites, domains, products, services, or other projects in a manner that suggests affiliation with, endorsement by, or association with the official LibrePods project without prior permission.

If you see any misuse of the LibrePods name or logo, please report it to [me@kavish.xyz](mailto:me@kavish.xyz).

The SF Pro font used in the Android app is the property of Apple Inc.. This will be removed in future versions of the app and replaced with an open alternative soon.

AirPods, AirPods Pro, AirPods Max, and the AirPods logo are trademarks of Apple Inc. The LibrePods project is not affiliated with or endorsed by Apple Inc. in any way.

---

### Overview (`android/README.md`)

## Root Requirement

LibrePods *may* require root depending on your device/OS and what features you want access to:

- Features requiring the VendorID hook ([the features marked with an asterisk here](https://github.com/kavishdevar/librepods#key-features)) will always require root regardless of your device/OS.
- On **ColorOS/OxygenOS 16 and realme UI 7.0** and **Pixel devices on Android 16 QPR3** (with the latest Google Play system update), LibrePods does not need root for most features.
- On other devices, LibrePods needs root because of a bug in the Android Bluetooth stack Fluoride/non-compliance of Apple with Bluetooth standards. You must have Xposed installed for the app to workaround this bug and connect to AirPods. [This issue is being tracked here](https://issuetracker.google.com/issues/371713238). **Please do not comment on the issue thread.** The issue has already been resolved and should be available in **Android 17** for all devices.

> [!IMPORTANT]
> This workaround with Xposed is not guaranteed to work on all devices.


## Installation

### Google Play Store

If you are using a supported device/OS combination, you can install LibrePods from the Google Play Store. You can use the VendorID hook features with root even from the Play Store version.

<a href="https://play.google.com/store/apps/details?id=me.kavishdevar.librepods"><img width="170" alt="GetItOnGooglePlay_Badge_Web_color_English" src="https://github.com/user-attachments/assets/2948308f-af92-443f-94d9-ee381c3a6ccc"/></a>

### GitHub Releases

If you need xposed because of the [root requirement](#root-requirement), you will have to use the apk/zip from the [GitHub releases](https://github.com/kavishdevar/librepods/releases/latest).

### As a system app (root module)

If you want LibrePods to have privileged Bluetooth permissions to 
- show battery status in the system settings and widgets
- show AirPods icon in the system settings (xposed is also currently required for this)
- switch audio to phone speakers when you are not wearing your AirPods

you can install the root module. This is optional and only provides extra features, but it is not required for the app to work.

> [!IMPORTANT]
> When using the root module, do not install the Play Store version. There might be issues because of the signature mismatch between the Play Store version and the root module.

## Nightly/Development Builds

Want to try the latest features before they're officially released? You can grab nightly builds from the [latest nightly release](https://github.com/kavishdevar/librepods/releases?q=nightly).

> [!WARNING]
> These builds are automatically generated from the latest code and may contain new features and bug fixes that haven't been included in a stable release yet. However, please note that they may also be less stable than official releases, so use them at your own risk.

## Screenshots

|                                                                                 |                                            |                                                                      |
| ------------------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- |
|                                             |        |  |
|                               |                                 |  |
|                                         |      |    |
|                   |  |                |


here's a very unprofessional demo video

https://github.com/user-attachments/assets/43911243-0576-4093-8c55-89c1db5ea533

### Troubleshooting steps for common errors
- Ensure the correct scope is set in LSPosed/Vector.
- Ensure there is no root-hiding module preventing the hook from loading on the Bluetooth app.
- Restart your phone after confirming the scope.

### A few notes

- Due to recent AirPods' firmware upgrades, you must enable `Off listening mode` to switch to `Off`. This is because in this mode, loud sounds are not reduced.

- When renaming your AirPods through the app, you'll need to re-pair them with your phone for the name change to take effect. This is a limitation of how Bluetooth device naming works on Android.

---

### Overview (`linux/README.md`)

# LibrePods on Linux

A new rewrite is being worked upon. Please look at the list of features in the root README to see what's supported in the new version. 

The rewrite can be found in the `linux/rust` branch [here](https://github.com/kavishdevar/librepods/tree/linux/rust/linux-rust). Follow the development in [PR #241](https://github.com/kavishdevar/librepods/pull/241). 


## Installation

### GitHub Releases

The app is ready to download as an AppImage or an executable. You can download the latest pre-release from the [GitHub releases](https://github.com/kavishdevar/librepods/releases?q="linux-v0").

### Nightly Builds (recommended)

You can also try the latest build of the new version from the [GitHub Actions artifacts](https://github.com/kavishdevar/librepods/actions/workflows/ci-linux-rust.yml). On the latest successful workflow run, download the **librepods-x86_64.AppImage** or **librepods** binary from **Artifacts**.




<details>
  <summary>README for the old version</summary>

# LibrePods Linux



A native Linux application to control your AirPods, with support for:

- Noise Control modes (Off, Transparency, Adaptive, Noise Cancellation)
- Conversational Awareness
- Battery monitoring
- Auto play/pause on ear detection
- Hearing Aid features
   - Supports adjusting hearing aid- amplification, balance, tone, ambient noise reduction, own voice amplification, and conversation boost
   - Supports setting the values for left and right hearing aids (this is not a hearing test! you need to have an audiogram to set the values)
- Seamless handoff between Android and Linux

## Prerequisites

1. Your phone's Bluetooth MAC address (can be found in Settings > About Device)
2. Qt6 packages

   ```bash
   # For Arch Linux / EndeavourOS
   sudo pacman -S qt6-base qt6-connectivity qt6-multimedia-ffmpeg qt6-multimedia

   # For Debian
   sudo apt-get install qt6-base-dev qt6-declarative-dev qt6-connectivity-dev qt6-multimedia-dev \
        qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates \
        qml6-module-qtquick-window qml6-module-qtquick-layouts

    # For Fedora
    sudo dnf install qt6-qtbase-devel qt6-qtconnectivity-devel \
        qt6-qtmultimedia-devel qt6-qtdeclarative-devel
   ```
3. OpenSSL development headers

    ```bash
    # On Arch Linux / EndevaourOS, these are included in the OpenSSL package, so you might already have them installed.
    sudo pacman -S openssl
    
    # For Debian / Ubuntu
    sudo apt-get install libssl-dev
    
    # For Fedora
    sudo dnf install openssl-devel
    ```
4. Libpulse development headers

    ```bash
    # On Arch Linux / EndevaourOS, these are included in the libpulse package, so you might already have them installed.
    sudo pacman -S libpulse

    # For Debian / Ubuntu
    sudo apt-get install libpulse-dev

    # For Fedora
    sudo dnf install pulseaudio-libs-devel
    ```
5. Cmake

    ```bash
    # For Arch Linux / EndeavourOS
    sudo pacman -S cmake

    # For Debian / Ubuntu
    sudo apt-get install cmake

    # For Fedora
    sudo dnf install cmake
    ```

## Setup

1. Build the application:

   ```bash
   mkdir build
   cd build
   cmake ..
   make -j $(nproc)
   ```

2. Run the application:

   ```bash
   ./librepods
   ```

## Troubleshooting

### Media Controls (Play/Pause/Skip) Not Working

If tap gestures on your AirPods aren't working for media control, you need to enable AVRCP support. The solution depends on your audio stack:

#### PipeWire/WirePlumber (Recommended)

Create `~/.config/wireplumber/wireplumber.conf.d/51-bluez-avrcp.conf`:

```conf
monitor.bluez.properties = {
  # Enable dummy AVRCP player for proper media control support
  # This is required for AirPods and other devices to send play/pause/skip commands
  bluez5.dummy-avrcp-player = true
}
```

Then restart WirePlumber:

```bash
systemctl --user restart wireplumber
```

> [!WARNING]
> Do NOT run `mpris-proxy` with WirePlumber - it will conflict and break media controls.

#### PulseAudio

If you're using PulseAudio instead of PipeWire, enable and start `mpris-proxy`:

```bash
systemctl --user enable --now mpris-proxy
```

## Usage

- Left-click the tray icon to view battery status
- Right-click to access the control menu:
  - Toggle Conversational Awareness
  - Switch between noise control modes
  - View battery levels
  - Control playback


## CLI Control

`librepods-ctl` is a small command-line tool that lets you access LibrePods from the terminal or via scripts, as long as the main application is running.

### Usage
```bash
librepods-ctl
```

### Commands

| Command | Description |
|---|---|
| `noise:off` | Disable noise control |
| `noise:anc` | Enable Active Noise Cancellation |
| `noise:transparency` | Enable Transparency mode |
| `noise:adaptive` | Enable Adaptive mode |

### Example
```bash
# Enable ANC
librepods-ctl noise:anc

# Enable Transparency mode
librepods-ctl noise:transparency
```


## Hearing Aid

To use hearing aid features, you need to have an audiogram. To enable/disable hearing aid, you can use the toggle in the main app. But, to adjust the settings and set the audiogram, you need to use a different script which is located in this folder as `hearing_aid.py`. You can run it with:

```bash
python3 hearing_aid.py
```

The script will load the current settings from the AirPods and allow you to adjust them. You can set the audiogram by providing the values for 8 frequencies (250Hz, 500Hz, 1kHz, 2kHz, 3kHz, 4kHz, 6kHz, 8kHz) for both left and right ears. There are also options to adjust amplification, balance, tone, ambient noise reduction, own voice amplification, and conversation boost.

AirPods check for the DeviceID characteristic to see if the connected device is an Apple device and only then allow hearing aid features. To set the DeviceID characteristic, you need to add this line to your bluetooth configuration file (usually located at `/etc/bluetooth/main.conf`):

```
DeviceID = bluetooth:004C:0000:0000
```

Then, restart the bluetooth service:

```bash
sudo systemctl restart bluetooth
```

Here, you might need to re-pair your AirPods because they seem to cache this info.

### Troubleshooting

It is possible that the AirPods disconnect after a short period of time and play the disconnect sound. This is likely due to the AirPods expecting some information from an Apple device. Since I have not implemented everything that an Apple device does, the AirPods may disconnect. You don't need to reconnect them manually; the script will handle reconnection automatically for hearing aid features. So, once you are done setting the hearing aid features, change back the `DeviceID` to whatever it was before.

### Why a separate script?

Because I discovered that QBluetooth doesn't support connecting to a socket with its PSM, only a UUID can be used. I could add a dependency on BlueZ, but then having two bluetooth interfaces seems unnecessary. So, I decided to use a separate script for hearing aid features. In the future, QBluetooth will be replaced with BlueZ native calls, and then everything will be in one application.

</details>

---

### AAP Definitions (`docs/AAP Definitions.md`)

# AAP Definitions (As per AirPods Pro 2 (USB-C) Firmware 7A305)

AAP runs on top of L2CAP, with a PSM of 0x1001 or 4097.

# Handshake
This packet is necessary to establish a connection with the AirPods. Or else, the AirPods will not respond to any packets.

```plaintext
00 00 04 00 01 00 02 00 00 00 00 00 00 00 00 00
```

# Setting specific features for AirPods Pro 2

> *may work for airpods 4 anc also, not tested*

Since apple likes to wall off some features behind specific OS versions, and apple silicon devices, some packets are necessary to enable these features.

I captured the following packet only accidentally, because Apple being Apple decided to hide *this* and *the handshake* from packetlogger, but sometimes it shows up.

*Captured using PacketLogger on an Intel Mac running macOS Sequoia 15.0.1*
```plaintext
04 00 04 00 4d 00 ff 00 00 00 00 00 00 00
```

This packet enables conversational awareness when playing audio. (CA works without this packet only when no audio is playing)

It also enables the Adaptive Transparency feature. (We can set Adaptive Transparency, but it doesn't respond with the same packet See [Noise Cancellation](#changing-noise-control))

# Requesting notifications

This packet is necessary to receive notifications from the AirPods like ear detection, noise control mode, conversational awareness, battery status, etc.

*Captured using PacketLogger on an Intel Mac running macOS Sequoia 15.0.1*
```plaintext
04 00 04 00 0F 00 FF FF FE FF
```

This packet also works.

```plaintext
04 00 04 00 0F 00 FF FF FF FF
```

# Notifications

## Battery

AirPods occasionally send battery status packets. The packet format is as follows:

```plaintext
04 00 04 00 04 00 [battery count] ([component] 01 [level] [status] 01) times the battery count
```

| Components | Byte value |
|------------|------------|
| Case       | 08         |
| Left       | 04         |
| Right      | 02         |

| Status       | Byte value |
|------------- |------------|
| Unknown      | 00         |
| Charging     | 01         |
| Discharging  | 02         |
| Disconnected | 04         |


Example packet from AirPods Pro 2

```plaintext
04 00 04 00 04 00 03 02 01 64 02 01 04 01 63 01 01 08 01 11 02 01
```

| Byte      | Interpretation                     |
|-----------|------------------------------------|
| 7th byte  | Battery Count - 3                  |
| 8th byte  | Battery type - Left                |
| 9th byte  | Spacer, value = 0x01               |
| 10th byte | Battery level 100%                 |
| 11th byte | Battery status - Discharging       |
| 12th byte | Battery component end value = 0x01 |
| 13th byte | Battery type - Right               |
| 14th byte | Spacer, value = 0x01               |
| 15th byte | Battery level 99%                  |
| 16th byte | Battery status - Charging          |
| 17th byte | Battery component end value = 0x01 |
| 18th byte | Battery type - Case                |
| 19th byte | Spacer, value = 0x01               |
| 20th byte | Battery level 17%                  |
| 21st byte | Battery status - Discharging       |
| 22nd byte | Battery component end value = 0x01 |

## Noise Control

The AirPods Pro 2 send noise control packets when the noise control mode is changed (either by a stem long press or by the connected device, see [Changing noise control](#changing-noise-control)). The packet format is as follows:

```plaintext
04 00 04 00 09 00 0D [mode] 00 00 00
```

| Noise Control Mode    | Byte value |
|-----------------------|------------|
| Off                   | 01         |
| Noise Cancellation    | 02         |
| Transparency          | 03         |
| Adaptive Transparency | 04         |

## Ear Detection

AirPods send ear detection packets when the ear detection status changes. The packet format is as follows:
```plaintext
04 00 04 00 06 00 [primary pod] [secondary pod]
```

If primary is removed, mic will be changed and the secondary will be the new primary, so the primary will be the one in the ear, and the packet will be sent again.

| Pod Status | Byte value |
|------------|------------|
| In Ear     | 00         |
| Out of Ear | 01         |
| In Case    | 02         |

## Conversational Awareness

AirPods send conversational awareness packets when the person wearing them starts speaking. The packet format is as follows:

```plaintext
04 00 04 00 4B 00 02 00 01 [level]
```

| Level Byte Value    | Meaning                                                 |
|---------------------|---------------------------------------------------------|
| 01/02               | Person Started Speaking; greatly reduce volume          |
| 03                  | Person Stopped Speaking; increase volume back to normal |
| Intermediate values | Intermediate volume levels                              |
| 08/09               | Normal Volume                                           |
### Reading Conversational Awareness State

After requesting notifications, the AirPods send a packet indicating the current state of Conversational Awareness (CA). This packet is only sent once after notifications are requested, not when the CA state is changed.

The packet format is:

```plaintext
04 00 04 00 09 00 28 [status] 00 00 00
```

- `[status]` is a single byte at offset 7 (zero-based), immediately after the header.
    - `0x01` — Conversational Awareness is **enabled**
    - `0x02` — Conversational Awareness is **disabled**
    - Any other value — Unknown/undetermined state

**Example:**
```plaintext
04 00 04 00 09 00 28 01 00 00 00
```
Here, `01` at the 8th byte (offset 7) means CA is enabled.

## Metadata

This packet contains device information like name, model number, etc. The packet format is:

```plaintext
04 00 04 00 1d [strings...]
```

The strings are null-terminated UTF-8 strings in the following order:

1. Bluetooth advertising name (varies in length)
2. Model number 
3. Manufacturer
4. Serial number
5. Firmware version
6. Firmware version 2 (the exact same as before??)
7. Software version   (1.0.0 why would we need it?)
8. App identifier     (com.apple.accessory.updater.app.71 what?)
9. Serial number 1
10. Serial number 2
11. Unknown numeric value
12. Encrypted data
13. Additional encrypted data

Example packet:
```plaintext
040004001d0002d5000400416972506f64732050726f004133303438004170706c6520496e632e0051584e524848595850360036312e313836383034303030323030303030302e323731330036312e313836383034303030323030303030302e3237313300312e302e3000636f6d2e6170706c652e6163636573736f72792e757064617465722e6170702e3731004859394c5432454632364a59004833504c5748444a32364b3000363335373533360089312a6567a5400f84a3ca234947efd40b90d78436ae5946748d70273e66066a2589300035333935303630363400```

The packet contains device identification and version information followed by some encrypted data whose format is not known.
```

# Writing to the AirPods

## Changing Noise Control

We can send a packet to change the noise control mode. The packet format is as follows:

```plaintext
04 00 04 00 09 00 0D [mode] 00 00 00
```

| Noise Control Mode    | Byte value |
|-----------------------|------------|
| Off                   | 01         |
| Noise Cancellation    | 02         |
| Transparency          | 03         |
| Adaptive Transparency | 04         |

The airpods will respond with the same packet after the mode has been changed.

> But if your airpods support Adaptive Transparency, and you haven't sent that [special packet](#setting-specific-features-for-airpods-pro-2) to enable it, the airpods will respond with the same packet but with a different mode (like 0x02).

## Renaming AirPods

We can send a packet to rename the AirPods. The packet format is as follows:

```plaintext
04 00 04 00 1A 00 01 [size] 00 [name]
```

## Toggle case charging sounds

> *This feature is only for cases with a speaker, i.e. the AirPods Pro 2 and the new AirPods 4. Tested only on AirPods Pro 2*

We can send a packet to toggle if sounds should be played when the case is connected to a charger. The packet format is as follows:

```plaintext
12 3A 00 01 00 08 [setting]
```

| Byte Value | Sound |
|------------|-------|
| 00         | On    |
| 01         | Off   |

## Toggle Conversational Awareness

> *This feature is only for AirPods Pro 2 and the new AirPods 4 with ANC. Tested only on AirPods Pro 2*

We can send a packet to toggle Conversational Awareness. If enabled, the AirPods will switch to Transparency mode when the person wearing them starts speaking (and sends packet for notifying the device to reduce volume). The packet format is as follows:

```plaintext
04 00 04 00 09 00 28 [setting] 00 00 00
```

| Byte Value | C.A. |
|------------|------|
| 01         | On   |
| 02         | Off  |

## Adaptive Audio Noise

> *This feature is only for AirPods Pro 2 and the new AirPods 4 with ANC. Tested only on AirPods Pro 2*

The new firmware `7A305` for app2 has a new feature called Adaptive Audio Noise. This allows us to control how much noise is passed through the AirPods when the noise control mode is set to Adaptive. The packet format is as follows:

```plaintext
04 00 04 00 09 00 2E [level] 00 00 00
```

The level can be any value between 0 and 100, 0 to allow maximum noise (i.e. minimum noise filtering), and 100 to filter out more noise.

> This feature is only effective when the noise control mode is set to Adaptive.

*I find it quite funny how I have greater control over the noise control on the AirPods on non-Apple devices than on Apple devices, becuase on Apple Devices, there are just 3 options More Noise (0), Midway through (50), and Less Noise (100), but here I can set any value between 0 and 100.*

## Accessiblity Settings

## Headphone Accomodation
```
04 00 04 00 53 00 84 00 02 02 [Phone] [Media]
[EQ1][EQ2][EQ3][EQ4][EQ5][EQ6][EQ7][EQ8]
duplicated thrice for some reason
```

| Data                | Type          | Value range                 |
|---------------------|---------------|-----------------------------|
| Phone               | Decimal       | 1 (Enabled) or 2 (Disabled) |
| Media               | Decimal       | 1 (Enabled) or 2 (Disabled) |
| EQ                  | Little Endian | 0 to 100                    |

## Customize Transparency mode

```
12 18 00 [enabled]
<left bud>
[EQ1][EQ2][EQ3][EQ4][EQ5][EQ6][EQ7][EQ8]
[Amplification]
[Tone]
[Conversation Boost]
[Ambient Noise Reduction]
<repeat for right bud>
```
/* Detailed source-code truncated for AI context efficiency. */
```plaintext
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 Tracking

This packet stops the head tracking data stream.

```plaintext
04 00 04 00 17 00 00 00 10 00 11 00 08 7E 10 02 42 0B 08 4E 10 02 1A 05 01 00 00 00 00
```
## Received Head Tracking Sensor Data

Once tracking is active, the AirPods stream sensor packets with the following common structure:
  
| Field                    | Offset | Length (bytes) |
|--------------------------|--------|----------------|
| orientation 1            | 43     | 2              |
| orientation 2            | 45     | 2              |
| orientation 3            | 47     | 2              |
| Horizontal Acceleration  | 51     | 2              |
| Vertical Acceleration    | 53     | 2              |

---

### Control Commands (`docs/control_commands.md`)

# Control Commands

AACP uses opcode `9` for control commands. opcodes are 16 bit integers that specify the kind of action being done. The length of a control command is fixed to 7 bytes + 4 bytes header (`04 00 04 00`)

An AACP packet is formated as:

`04 00 04 00 [opcode, little endian] [data]`

So, our control commands becomes

```
04 00 04 00 09 00 [identifier] [data1] [data2] [data3] [data4]
```

Bytes that are not used are set to `0x00`. From what I've observed, the `data3` and `data4` are never used, and hence always zero. And, the `data2` is usually used when the configuration can be different for the two buds: like, to change the long press mode. Or, if there can be two "state" variables for the same feature: like the Hearing Aid feature.

## Identifiers and details 

| Command identifier | Description                                    |
| ------------------ | ---------------------------------------------- |
| 0x01               | Mic Mode                                       |
| 0x05               | Button Send Mode                               |
| 0x06               | Owns connection                                |
| 0x0A               | Ear Detection                                  |
| 0x12               | VoiceTrigger for Siri                          |
| 0x14               | SingleClickMode                                |
| 0x15               | DoubleClickMode                                |
| 0x16               | ClickHoldMode                                  |
| 0x17               | DoubleClickInterval                            |
| 0x18               | ClickHoldInterval                              |
| 0x1A               | ListeningModeConfigs                           |
| 0x1B               | OneBudANCMode                                  |
| 0x1C               | CrownRotationDirection                         |
| 0x0D               | ListeningMode                                  |
| 0x1E               | AutoAnswerMode                                 |
| 0x1F               | Chime Volume                                   |
| 0x20               | Connect Automatically                          |
| 0x23               | VolumeSwipeInterval                            |
| 0x24               | Call Management Config                         |
| 0x25               | VolumeSwipeMode                                |
| 0x26               | Adaptive Volume Config                         |
| 0x27               | Software Mute config                           |
| 0x28               | Conversation Detect config                     |
| 0x29               | SSL                                            |
| 0x2C               | Hearing Aid Enrolled and Hearing Aid Enabled   |
| 0x2E               | AutoANC Strength                               |
| 0x2F               | HPS Gain Swipe                                 |
| 0x30               | HRM enable/disable state                       |
| 0x31               | In Case Tone config                            |
| 0x32               | Siri Multitone config                          |
| 0x33               | Hearing Assist config                          |
| 0x34               | Allow Off Option for Listening Mode config     |
| 0x35               | Sleep Detection config                         |
| 0x36               | Allow Auto Connect                             |
| 0x37               | PPE Toggle config                              |
| 0x38               | Personal Protective Equipment Cap Level config |
| 0x39               | Raw Gestures config                            |
| 0x3A               | Temporary Pairing Config                       |
| 0x3B               | Dynamic End of Charge config                   |
| 0x3C               | System Siri message config                     |
| 0x3D               | Hearing Aid Generic config                     |
| 0x3E               | Uplink EQ Bud config                           |
| 0x3F               | Uplink EQ Source config                        |
| 0x40               | In Case Tone Volume                            |
| 0x41               | Disable Button Input config                    |

## Command Details

### 0x01 - Mic Mode
Format: Single value (1 byte)
Values: `0x00` = Automatic, `0x01` = Right, `0x02` = Left.

### 0x05 - Button Send Mode
Format: Single value (1 byte)
Additional notes: Logged as "Set Button Send Mode: %d". May involve context updates for button handling.

### 0x06 - Owns connection
Format: Single value (1 byte)
Values: `0x01` = own, `0x00` = doesn't own.

### 0x0A - Ear Detection
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled.

### 0x12 - VoiceTrigger for Siri
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled.

### 0x14 - SingleClickMode
Format: Single value (1 byte)

### 0x15 - DoubleClickMode
Format: Single value (1 byte)

### 0x16 - ClickHoldMode
Format: Two values (2 bytes; First byte = right bud, Second byte = left bud).  
Values: `0x01` = Noise control, `0x05` = Siri.  

### 0x17 - DoubleClickInterval
Format: Single value (1 byte)
Values: 0x00 = Default, `0x01` = Slower, `0x02` = Slowest.  

### 0x18 - ClickHoldInterval
Format: Single value (1 byte)
Values: 0x00 = Default, `0x01` = Slower, `0x02` = Slowest.  

### 0x1A - ListeningModeConfigs
Format: Single value (1 byte)
Values: Bitmask, Off mode = `0x01`, ANC=`0x02`, Transparency = 0x04, Adaptive = `0x08`.  

### 0x1B - OneBudANCMode
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x1C - CrownRotationDirection
Format: Single value (1 byte)
Values: `0x01` = reversed, `0x02` = default.  

### 0x0D - ListeningMode
Format: Single value (1 byte)
Values: 1 = Off, 2 = noise cancellation, 3 = transparency, 4 = adaptive.  

### 0x1E - AutoAnswerMode
Format: Single value (1 byte)

### 0x1F - Chime Volume
Format: Single value (1 byte)
Values: 0 to 100.  

### 0x20 - Connect Automatically
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled.

### 0x23 - VolumeSwipeInterval
Format: Single value (1 byte)
Values: 0x00 = Default, `0x01` = Longer, `0x02` = Longest.  

### 0x24 - Call Management Config
Format: Single value (1 byte)

### 0x25 - VolumeSwipeMode
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x26 - Adaptive Volume Config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x27 - Software Mute config
Format: Single value (1 byte)

### 0x28 - Conversation Detect config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x29 - SSL
Format: Single value (1 byte)

### 0x2C - Hearing Aid Enrolled and Hearing Aid Enabled
Format: Two values (2 bytes; First byte - enrolled, Second byte = enabled)
Values: `0x01` = enabled, `0x02` = disabled

### 0x2E - AutoANC Strength
Format: Single value (1 byte)
Values: 0 to 100.  

### 0x2F - HPS Gain Swipe (swipe to adjust amplification)
Format: Single value (1 byte)

### 0x30 - HRM enable/disable state
Format: Single value (1 byte)

### 0x31 - In Case Tone config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x32 - Siri Multitone config
Format: Single value (1 byte)

### 0x33 - Hearing Assist config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x34 - Allow Off Option for Listening Mode config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x35 - Sleep Detection config
Format: Single value (1 byte)
Values: `0x01` = enabled, `0x02` = disabled

### 0x36 - Allow Auto Connect
Format: Single value (1 byte)
Values: `0x01` = allow, `0x02` = disallow

### 0x37 - PPE Toggle config
Format: Single value (1 byte)

### 0x38 - Personal Protective Equipment Cap Level config
Format: Single value (1 byte)

### 0x39 - Raw Gestures config
Format: Single value (1 byte)
Values: Bitmask, single press = `0x01`, double press = `0x02`, triple press = `0x04`, long press = `0x08`.  

### 0x3A - Temporary Pairing Config
Format: Single value (1 byte)
Values: `0x01` = Temporary, `0x02` = Permanent

### 0x3B - Dynamic End of Charge config
Format: Single value (1 byte)

### 0x3C - System Siri message config
Format: Single value (1 byte)

### 0x3D - Hearing Aid Generic config
Format: Single value (1 byte)

### 0x3E - Uplink EQ Bud config
Format: Single value (1 byte)

### 0x3F - Uplink EQ Source config
Format: Single value (1 byte)

### 0x40 - In Case Tone Volume
Format: Single value (1 byte)
Values: 0 to 100.  

### 0x41 - Disable Button Input config
Format: Single value (1 byte)

> [!NOTE]
> - These identifiers have been extracted from the iOS 19.1 Beta (23B5044l)'s bluetooth stack. 
> - I have already added the ranges of values a command takes that I know of. Feel free to experiment by sending the packets for which the range/values are not given here.

---

### Device Info (`docs/device-info.md`)

---
opcode: 0x001D
title: Device Information
description: Information about AirPods, such as model, firmware version, and serial number. This can not be requested from the accessory; it is only sent by the accessory to the host upon connection.
---

## Device information

The device information packet is sent by the accessory to the host upon connection. It contains various details about the AirPods, including model number, software version, and serial number.

Each `null` indicates the start of a new string field.

The data is in this order:
- Name
- Model number
- Manufacturer (always "Apple Inc.")
- Serial number
- Version 1
- Version 2
- Hardware revision (?) (I have `1.0.0`)
- Updater app version (?) (I have `com.apple.accessory.updater.app.71`)
- Serial number (Left Bud)
- Serial number (Right Bud)
- Version (?) (I have `8454371`)
- A few more bytes, I don't know what they are

---

### Opcodes (`docs/opcodes.md`)

# AACP opcodes

AACP (Apple Accessory Communication Protocol) uses various opcodes to define different types of actions and commands. Each opcode is a 16-bit integer that specifies the kind of operation being performed. The opcode is sent in little-endian format as part of the AACP packet structure.


| Opcode (Hex) | Destination | Description                                                        |
| ------------ | ----------- | ------------------------------------------------------------------ |
| 0x0001       | Accessory   | Unknown                                                            |
| 0x0004       | Host        | [Battery report](/docs/battery_report.md)                          |
| 0x0006       | Host        | [Ear detection](/docs/ear-detection_report.md)                     |
| 0x0009       | Both        | [Control commands](/docs/control_commands.md)                      |
| 0x000D       | Accessory   | [Audio source req](/docs/audio-source.md)                          |
| 0x000E       | Host        | [Audio source resp](/docs/audio-source.md)                         |
| 0x000F       | Accessory   | [Notification register](/docs/notification-register.md)            |
| 0x0010       | Accessory   | [Smart routing relay](/docs/smart-routing-relay.md#send)           |
| 0x0011       | Host        | [Smart routing response](/docs/smart-routing-relay.md#receive)     |
| 0x0014       | Accessory   | Send connected device MAC                                          |
| 0x0017       | Both        | Multiple things - undocumented                                     |
| 0x0019       | Host        | [Stem press](/docs/stem-press.md)                                  |
| 0x001B       | Accessory   | [Timestamp](/docs/timestamp.md)                                    |
| 0x001D       | Host        | [Device Information](/docs/device-info.md)                         |
| 0x001E       | Accessory   | [Rename device](/docs/rename.md)                                   |
| 0x0022       | Accessory   | Unknown                                                            |
| 0x0029       | Accessory   | [Host capabilities](/docs/host-capabilities.md#another-opcode) (?) |
| 0x002B       | Host        | Paired devices (?)                                                 |
| 0x002D       | Accessory   | [List of connected dev. req](/docs/connected-devices.md#send)      |
| 0x002E       | Host        | [List of connected devices](/docs/connected-devices.md#receive)    |
| 0x0030       | Accessory   | [BLE keys req](/docs/ble-keys.md)                                  |
| 0x0031       | Host        | [BLE keys response](/docs/ble-keys.md)                             |
| 0x004B       | Host        | [Conversation awareness](/docs/conversational-awareness.md)        |
| 0x004D       | Accessory   | [Host capabilities](/docs/host-capabilities.md)                    |
| 0x004F       | Both        | Information req/res (doesn't work, even with apple's DID)          |
| 0x0053       | Both        | [EQ data](/docs/eq.md)                                             |

---

--- METRICS ---
- Files Extracted: 7
- Estimated Token Budget: ~12952 tokens
- Canonical Reference: https://codewiki.google/github.com/librepods-org/librepods
