尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LibrePods 逆向笔记:AirPods Pro 2 的 AAP 协议包定义与实战解析(基于固件 7A305)

LibrePods 逆向笔记:AirPods Pro 2 的 AAP 协议包定义与实战解析(基于固件 7A305) LibrePods 逆向笔记AirPods Pro 2 的 AAP 协议包定义与实战解析基于固件 7A305【免费下载链接】librepodsAirPods liberated from Apples ecosystem.项目地址: https://gitcode.com/gh_mirrors/al/librepods本篇技术指南以 LibrePods 项目维护者逆向整理的 AAP Definitions 文档 为核心骨架系统讲解 AirPods Pro 2USB-C固件 7A305上 Apple Accessory ProtocolAAP的报文格式从建立连接所需的握手包到订阅通知、解析电池/入耳检测/对话感知等事件再到写入侧的命令切换降噪、重命名、自定义通透模式、配置长按手势、头部追踪等。读完本文你将掌握每一类 AAP 报文的字节级结构与含义并能对照 LibrePods 在 Android 与 Linux 两端的具体实现AACPManager.kt、Packets.kt、airpods_packets.h验证并复用这些报文。AAP 协议概述L2CAP 之上的自定义通道AAPApple Accessory Protocol苹果配件协议运行在蓝牙 L2CAP 层之上使用的 PSMProtocol/Service Multiplexer为0x1001十进制 4097。它负责承载 AirPods 与主机之间几乎所有非音频数据交换电池状态、入耳检测、噪声控制模式、对话感知Conversational Awareness简称 CA、设备元数据、长按手势、头部追踪等。LibrePods 在 Android 端正是通过L2CAP套接字与 AirPods 建立这条通道见 AACPManager.kt 的类注释“handling the L2CAP socket management”。AAP 包几乎都以四字节固定头04 00 04 00开头随后是两个字节的小端序 opcode。对控制类命令opcode0x09负载为固定 7 字节[identifier] [data1] [data2] [data3] [data4]未使用的字节填0x00。据此可以得到通用的 AACP 包结构04 00 04 00 [opcode, little endian] [data]控制命令则进一步展开为04 00 04 00 09 00 [identifier] [data1] [data2] [data3] [data4]从已有观察看data3、data4从未被使用、恒为 0data2通常用于“左右耳机可配置不同”或“同一功能存在两个状态变量”的场景例如长按模式配置0x16 ClickHoldMode的第一个字节是右耳、第二个字节是左耳以及助听功能的 enroll/enabled 双状态0x2C。本仓库 opcodes.md 汇总了完整 opcode 表其中与本文直接相关的关键 opcode 包括Opcode (Hex)方向描述0x0004Host电池报告Battery report0x0006Host入耳检测Ear detection0x0009Both控制命令Control commands0x000FAccessory通知订阅Notification register0x0017Both头部追踪等多用途部分未公开0x001AAccessory重命名设备Rename device0x001DHost设备信息Device Information0x004BHost对话感知Conversation awareness0x004DAccessory主机能力Host capabilities / 功能特性开关0x0053BothEQ 数据Headphone Accommodation建立连接握手包HandshakeAirPods 在收到握手包之前不会响应任何后续报文因此握手是建立会话的第一步、也是必要的一步。握手包内容如下00 00 04 00 01 00 02 00 00 00 00 00 00 00 00 00在 Android 端LibrePods 将其封装为createHandshakePacket()AACPManager.kt字节序列与文档完全一致Linux 端则定义在 airpods_packets.h 的Connection::HANDSHAKE QByteArray::fromHex(00000400010002000000000000000000)。可以看到握手包并非以04 00 04 00开头而是以00 00 04 00开头属于连接阶段的特例报文握手完成后随后的数据交换才使用标准04 00 04 00帧头。解锁 Apple 独占功能特性开关包0x4DApple 会将部分功能锁定在特定 OS 版本与 Apple Silicon 设备之后因此需要向 AirPods 发送一个特性开关包来解锁。该包是作者在用 PacketLoggermacOS Sequoia 15.0.1Intel Mac抓包时偶然捕获的——Apple 刻意在 PacketLogger 中隐藏了该包与握手包但偶尔仍会漏出04 00 04 00 4d 00 ff 00 00 00 00 00 00 00这个包的作用包括启用播放音频时的对话感知Conversational Awareness不发送该包时CA 仅在无音频播放状态下生效启用自适应通透模式Adaptive Transparency发送后即可设置该模式但 AirPods 不会回传相同的报文来确认参见下文“更改噪声控制”。注意原文档作者标注该包在 AirPods 4 ANC 上“可能同样有效但未经测试”。LibrePods 在 Android 端的封装位于createSetFeatureFlagsPacket()AACPManager.ktopcode 为0x4D、负载为D7 00 00 00 00 00 00 00Linux 端对应SET_SPECIFIC_FEATURES 040004004d00d700000000000000airpods_packets.h。可以看到实际发送的负载值与文档中抓包得到的FF 00 ...略有差异D7vsFF从源码注释看它被命名为 “Set Feature Flags”属于功能特性位标志具体每一位的含义并未完全公开。订阅通知Notification Register0x0FAirPods 不会主动推送所有状态主机需要先发送通知订阅包之后才能收到入耳检测、噪声控制模式、对话感知、电池状态等事件。订阅包如下同样抓自 macOS Sequoia 15.0.104 00 04 00 0F 00 FF FF FE FF以下替代包同样有效04 00 04 00 0F 00 FF FF FF FFAndroid 端封装为createRequestNotificationPacket()AACPManager.kt使用FF FF FF FF全订阅。源码中还留有一条作者笔记当入耳检测被禁用时第三个字节会变成0xFD// note to self #1: third byte is 0xfd when ear detection is disabled且该包“可以在任意时刻发送不限于 AACP 连接建立之初”// note to self #2。从 AirPods 收到的通知电池状态BatteryAirPods 会不定时发送电池状态包通用格式如下04 00 04 00 04 00 [battery count] ([component] 01 [level] [status] 01) × battery count组件字节值与状态字节值分别见下表组件字节值充电盒Case08左耳Left04右耳Right02状态字节值Unknown未知00Charging充电中01Discharging放电中02Disconnected未连接04以 AirPods Pro 2 的真实报文为例04 00 04 00 04 00 03 02 01 64 02 01 04 01 63 01 01 08 01 11 02 01逐字节解读如下字节从 1 起含义7Battery Count 3三组电池8电池类型 Left左耳9分隔字节值 0x0110电量 100%11状态 Discharging放电中12该组电池结束标记 0x0113电池类型 Right右耳14分隔字节值 0x0115电量 99%16状态 Charging充电中17该组电池结束标记 0x0118电池类型 Case充电盒19分隔字节值 0x0120电量 17%21状态 Discharging放电中22该组电池结束标记 0x01Android 端 Packets.kt 中的BatteryComponentLEFT4、RIGHT2、CASE8与BatteryStatusCHARGING1、NOT_CHARGING2、DISCONNECTED4、OPTIMIZED_CHARGING5与上表严格对应BatteryNotification::setBattery()则按“左耳 7/9/10、右耳 12/14/15、充电盒 17/19/20”字节偏移解析三组电量。顺带一提状态值0x05优化充电在文档表格中未列出属于仓库源码中补充发现的取值。噪声控制Noise Control无论噪声控制模式是通过耳机柄长按还是由已连接设备见下文“更改噪声控制”触发变化AirPods Pro 2 都会发送噪声控制通知包04 00 04 00 09 00 0D [mode] 00 00 00噪声控制模式字节值Off关闭01Noise Cancellation降噪02Transparency通透03Adaptive Transparency自适应通透04在 Packets.kt 的AirPodsNotifications.ANC类中isANCData()要求包长为 11 字节并以04 00 04 00 09 00 0D开头对应Enums.NOISE_CANCELLATION_PREFIX随后从第 8 个字节下标 7读取模式值name属性把 1/2/3/4 分别映射为OFF/ON/TRANSPARENCY/ADAPTIVE。入耳检测Ear Detection入耳状态变化时AirPods 会发送如下格式的包04 00 04 00 06 00 [primary pod] [secondary pod]耳机状态字节值佩戴中In Ear00取出Out of Ear01放入充电盒In Case02值得注意的一个细节如果主耳机primary被取出麦克风会自动切换次耳机secondary将成为新的主耳机因此包中的“primary”始终是戴在耳朵里的那一只并且此时会再次发送该包。LibrePods 的 Android 端在 Packets.kt 的AirPodsNotifications.EarDetection中用data[6]与data[7]分别记录两只耳机的状态Linux 端则在 airpods_packets.h 中定义了Parse::EAR_DETECTION 040004000600前缀用于识别。对话感知Conversational Awareness当佩戴者开始说话时AirPods 会发送对话感知通知04 00 04 00 4B 00 02 00 01 [level]Level 字节值含义01/02佩戴者开始说话大幅降低音量03佩戴者停止说话音量恢复至正常中间值中间音量等级08/09正常音量Android 端 Packets.kt 中的ConversationalAwarenessNotification以04 00 04 00 4B 00 02 00作为前缀Enums.CONVERSATION_AWARENESS_RECEIVE_PREFIX要求包长 10 字节并从下标 9 读取状态Linux 端对应 airpods_packets.h 中的DATA_HEADER 040004004B00020001。读取对话感知当前状态在发送通知订阅包之后AirPods 会只发送一次表示 CA 当前状态的包仅在请求通知后发送一次CA 状态变化时不会再次发送。格式如下04 00 04 00 09 00 28 [status] 00 00 00[status]为头部之后第 8 个字节零基偏移 70x01— 对话感知已启用0x02— 对话感知已禁用其他值 — 未知/未确定状态例如04 00 04 00 09 00 28 01 00 00 00第 8 个字节偏移 7的01表示 CA 已启用。该包在控制命令体系中的 identifier 为0x28 Conversation Detect config参见 control_commands.md。设备元数据Metadata / Device Information设备信息包包含名称、型号等元数据格式为04 00 04 00 1d [strings...]字符串是按顺序排列的、以\0结尾的 UTF-8 字符串蓝牙广播名称长度可变型号Model number制造商Manufacturer序列号Serial number固件版本Firmware version固件版本 2和前面完全一样??软件版本1.0.0为什么需要它?App 标识符com.apple.accessory.updater.app.71什么鬼?序列号 1序列号 2未知数值加密数据额外的加密数据示例包040004001d0002d5000400416972506f64732050726f004133303438004170706c6520496e632e0051584e524848595850360036312e313836383034303030323030303030302e323731330036312e313836383034303030323030303030302e3237313300312e302e3000636f6d2e6170706c652e6163636573736f72792e757064617465722e6170702e3731004859394c5432454632364a59004833504c5748444a32364b3000363335373533360089312a6567a5400f84a3ca234947efd40b90d78436ae5946748d70273e66066a2589300035333935303630363400该包包含设备标识与版本信息随后跟着一段格式未知的加密数据。opcode0x001D对应的完整字段说明见 device-info.md设备信息包只能由配件在连接建立时主动发给主机主机无法主动请求。LibrePods 在 AACPManager.kt 中定义了AirPodsInformation数据类name、modelNumber、manufacturer、serialNumber、version1、version2、hardwareRevision、updaterIdentifier、leftSerialNumber、rightSerialNumber、version3与上表的字符串顺序一一对应。向 AirPods 写入命令更改噪声控制模式发送如下包即可切换噪声控制模式写命令与上文“噪声控制通知”使用相同的 0x0D 控制命令标识04 00 04 00 09 00 0D [mode] 00 00 00噪声控制模式字节值Off关闭01Noise Cancellation降噪02Transparency通透03Adaptive Transparency自适应通透04模式切换成功后AirPods 会回传相同报文作为确认。一个易踩的坑如果你的 AirPods 支持自适应通透但尚未发送上述“特性开关包”解锁该功能那么 AirPods 会用另一个模式例如0x02回包而不是你所请求的自适应模式。Linux 端实现非常直观airpods_packets.h 的NoiseControl命名空间static const QByteArray OFF ControlCommand::createCommand(0x0D, 0x01); static const QByteArray NOISE_CANCELLATION ControlCommand::createCommand(0x0D, 0x02); static const QByteArray TRANSPARENCY ControlCommand::createCommand(0x0D, 0x03); static const QByteArray ADAPTIVE ControlCommand::createCommand(0x0D, 0x04);重命名 AirPods重命名包格式如下04 00 04 00 1A 00 01 [size] 00 [name]其中[size]是名称的字节长度[name]是 UTF-8 编码的名称。Linux 端 airpods_packets.h 的Rename::getPacket()展示了完整构造过程先写入040004001A0001头部再追加名称长度字节、一个\0分隔字节最后追加名称字节。Android 端的createRenamePacket()AACPManager.kt逻辑相同1A 00 | 01 | size | 00 | name。README 中提到 Android 端重命名后需要重新配对才能看到新名称。切换充电盒充电提示音注意该功能仅适用于带扬声器的充电盒即 AirPods Pro 2 与新 AirPods 4仅在 AirPods Pro 2 上测试过。发送以下包控制充电盒接入电源时是否播放提示音12 3A 00 01 00 08 [setting]字节值含义00提示音开On01提示音关Off注意此包不以04 00 04 00开头是少数特殊帧头的命令之一。切换对话感知注意该功能仅适用于 AirPods Pro 2 与新 AirPods 4带 ANC仅在 AirPods Pro 2 上测试过。启用后当佩戴者开始说话时 AirPods 会自动切换到通透模式并发送通知包提示设备降低音量04 00 04 00 09 00 28 [setting] 00 00 00字节值C.A.01开On02关OffLinux 端 airpods_packets.h 的ConversationalAwareness命名空间使用BasicControlCommand0x28ENABLED/DISABLED即对应0x01/0x02。自适应音频降噪强度Adaptive Audio Noise固件 7A305 为 AirPods Pro 2 带来了“自适应音频噪声”新特性当噪声控制模式为**自适应Adaptive**时可通过该命令控制透过的噪声量04 00 04 00 09 00 2E [level] 00 00 00[level]取值范围为01000允许最大噪声即最少噪声过滤100过滤掉更多噪声该命令仅在噪声控制模式为 Adaptive 时生效。作者在文档中戏谑道在非 Apple 设备上反而能获得比 Apple 设备更精细的控制——Apple 设备上只有“More Noise (0)、Midway through (50)、Less Noise (100)”三档而这里可以设置 0100 的任意值。Linux 端实现为AdaptiveNoise::getPacket(level)即0400040009002E level 000000airpods_packets.h。无障碍设置Accessibility Settings耳机调节Headphone Accommodation04 00 04 00 53 00 84 00 02 02 [Phone] [Media] [EQ1][EQ2][EQ3][EQ4][EQ5][EQ6][EQ7][EQ8] 不知为何重复三遍数据类型取值范围PhoneDecimal1启用或 2禁用MediaDecimal1启用或 2禁用EQLittle Endian0 到 100opcode0x0053也对应 opcodes.md 中的 EQ data。Android 端 AACPManager.kt 在解析 HEADPHONE_ACCOMMODATION 包时要求包长恰好为 140 字节且下标 6 处为标识符0x84通过eqOnMedia (packet[10] 0x01)、eqOnPhone (packet[11] 0x01)读取两个开关并以小端序浮点解析 EQ 数据目前取第一组 8 个 EQ 值。自定义通透模式Customize Transparency Mode12 18 00 [enabled] 左耳 [EQ1][EQ2][EQ3][EQ4][EQ5][EQ6][EQ7][EQ8] [Amplification] [Tone] [Conversation Boost] [Ambient Noise Reduction] 右耳重复上述结构所有数值均为小端序 IEEE 754 浮点数数据类型范围Enabled启用IEEE754 Float0/1EQIEEE754 Float0-100Amplification放大IEEE754 Float0-2Tone音调IEEE754 Float0-2Conversation Boost对话增强IEEE754 Float0/1Ambient Noise Reduction环境噪声降低IEEE754 Float0-1[!IMPORTANT] 发送完此包后还必须接着发送一次 耳机调节Headphone Accommodation 包。配置耳机柄长按Configure Stem Long Press长按配置是作者记录的、针对当前状态发送的一组包AirPods 每次连接到一个“未与 iCloud 同步的新Apple设备”时长按配置很可能会被覆盖。因此对非 Apple 设备同样如此——必须保存配置并在每次连接时重新写入这是唯一能保持配置的方式同时由于需要先知道上一个状态再设置新状态这也是唯一能控制该配置的方式。下面按“切换的目标模式”分组列出基于不同前一个状态的完整包序列。包尾字节 0B/0D/0E/0F 与 03/05/06/07/09/0A/0C 等实际上编码了“当前启用了哪些模式”的位掩码Off0x01, ANC0x02, Transparency0x04, Adaptive0x08见 control_commands.md 中0x1A ListeningModeConfigs的取值说明。切换 Adaptive自适应04 00 04 00 09 00 1A 0B 00 00 00— 从 OffANC 打开 Adaptive04 00 04 00 09 00 1A 0D 00 00 00— 从 OffTransparency 打开 Adaptive04 00 04 00 09 00 1A 0E 00 00 00— 从 TransparencyANC 打开 Adaptive04 00 04 00 09 00 1A 0F 00 00 00— 从 OffTransparencyANC 打开 Adaptive04 00 04 00 09 00 1A 03 00 00 00— 从 OffANCAdaptive关闭 Adaptive04 00 04 00 09 00 1A 05 00 00 00— 从 OffTransparencyAdaptive关闭 Adaptive04 00 04 00 09 00 1A 06 00 00 00— 从 TransparencyANCAdaptive关闭 Adaptive04 00 04 00 09 00 1A 07 00 00 00— 从 OffTransparencyANCAdaptive关闭 Adaptive切换 Transparency通透04 00 04 00 09 00 1A 07 00 00 00— 从 OffANC 打开 Transparency04 00 04 00 09 00 1A 0D 00 00 00— 从 OffAdaptive 打开 Transparency04 00 04 00 09 00 1A 0E 00 00 00— 从 AdaptiveANC 打开 Transparency04 00 04 00 09 00 1A 0F 00 00 00— 从 OffAdaptiveANC 打开 Transparency04 00 04 00 09 00 1A 03 00 00 00— 从 OffANCTransparency关闭 Transparency04 00 04 00 09 00 1A 09 00 00 00— 从 OffAdaptiveTransparency关闭 Transparency04 00 04 00 09 00 1A 0A 00 00 00— 从 AdaptiveANCTransparency关闭 Transparency04 00 04 00 09 00 1A 0B 00 00 00— 从 OffAdaptiveANCTransparency关闭 Transparency切换 ANC降噪04 00 04 00 09 00 1A 07 00 00 00— 从 OffTransparency 打开 ANC04 00 04 00 09 00 1A 0B 00 00 00— 从 OffAdaptive 打开 ANC04 00 04 00 09 00 1A 0E 00 00 00— 从 AdaptiveTransparency 打开 ANC04 00 04 00 09 00 1A 0F 00 00 00— 从 OffAdaptiveTransparency 打开 ANC04 00 04 00 09 00 1A 05 00 00 00— 从 OffTransparencyANC关闭 ANC04 00 04 00 09 00 1A 09 00 00 00— 从 OffAdaptiveANC关闭 ANC04 00 04 00 09 00 1A 0C 00 00 00— 从 AdaptiveTransparencyANC关闭 ANC04 00 04 00 09 00 1A 0D 00 00 00— 从 OffAdaptiveTransparencyANC关闭 ANC切换 Off关闭模式04 00 04 00 09 00 1A 07 00 00 00— 从 TransparencyANC 打开 Off04 00 04 00 09 00 1A 0B 00 00 00— 从 AdaptiveANC 打开 Off04 00 04 00 09 00 1A 0D 00 00 00— 从 TransparencyAdaptive 打开 Off04 00 04 00 09 00 1A 0F 00 00 00— 从 TransparencyAdaptiveANC 打开 Off04 00 04 00 09 00 1A 06 00 00 00— 从 TransparencyANCOff关闭 Off04 00 04 00 09 00 1A 0A 00 00 00— 从 AdaptiveANCOff关闭 Off04 00 04 00 09 00 1A 0C 00 00 00— 从 TransparencyAdaptiveOff关闭 Off04 00 04 00 09 00 1A 0E 00 00 00— 从 TransparencyAdaptiveANCOff关闭 Off作者吐槽道这些状态完全可以用位掩码穷举总共也只有 4² 种组合再扣掉单模式/双模式的情形Apple 却没有硬编码成统一的协议逻辑导致第三方实现必须逐状态枚举。头部追踪Head Tracking开始追踪发送该包启动头部追踪后AirPods 会开始持续上报头部方向、加速度等传感器数据可用于实时绘图与分析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停止追踪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 00Android 端分别由createStartHeadTrackingPacket()与createStopHeadTrackingPacket()构造AACPManager.kt且各提供一套 Alternate 变体opcode 为0x17与 opcodes.md 中 “0x0017 Multiple things - undocumented” 的描述吻合。头部追踪传感器数据包追踪开启后AirPods 会持续发送传感器数据包公共字段结构如下字段偏移Offset长度字节orientation 1方向 1432orientation 2方向 2452orientation 3方向 3472Horizontal Acceleration水平加速度512Vertical Acceleration垂直加速度532Android 端的isHeadTrackingData()Packets.kt要求包长 60、以04 00 04 00 17 00 00 00 10 00开头、且下标 10 为0x44或0x45、下标 11 为0x00与上述结构一致。与 LibrePods 源码的对应关系一览本文所有报文均在 LibrePods 仓库中有直接实现便于对照验证报文Android 实现Linux 实现握手包AACPManager.createHandshakePacket()AACPManager.ktConnection::HANDSHAKEairpods_packets.h特性开关0x4DcreateSetFeatureFlagsPacket()AACPManager.ktConnection::SET_SPECIFIC_FEATURES通知订阅0x0FcreateRequestNotificationPacket()AACPManager.ktConnection::REQUEST_NOTIFICATIONS噪声控制0x0DPackets.kt的Enums.NOISE_CANCELLATION_PREFIX/ANCNoiseControl::OFF/NOISE_CANCELLATION/TRANSPARENCY/ADAPTIVE电池0x04Packets.kt的BatteryComponent/BatteryStatus/BatteryNotificationParse::BATTERY_STATUS入耳检测0x06Packets.kt的EarDetectionParse::EAR_DETECTION对话感知0x4B / 0x28Packets.kt的ConversationalAwarenessNotificationConversationalAwareness/DATA_HEADER设备元数据0x1DAACPManager.AirPodsInformation 解析逻辑Parse::METADATA重命名0x1AcreateRenamePacket()AACPManager.ktRename::getPacket()自适应噪声强度0x2EControlCommandIdentifiers.AUTO_ANC_STRENGTHAdaptiveNoise::getPacket(level)头部追踪0x17createStartHeadTrackingPacket()/createStopHeadTrackingPacket()—Android 端所有控制命令的发送统一收敛到AACPManager.sendControlCommand(identifier, value)系列重载AACPManager.kt布尔值会被自动映射为0x01/0x02最终经createControlCommandPacket()打包为04 00 04 00 09 00 [identifier] [data...]发送收包时则通过receivePacket()按 opcode 分发并维护controlCommandStatusList状态表——这套实现完整印证了本文的包格式定义。更多控制命令标识符麦克风模式、长按间隔、助听配置、PPE、睡眠检测等 0x010x41 共 40 个可进一步查阅 control_commands.md其取值来源于 iOS 19.1 Beta23B5044l的蓝牙协议栈提取。总结本文完整梳理了 AirPods Pro 2固件 7A305的 AAP 协议报文先发握手包建立会话再发特性开关包解锁对话感知与自适应通透随后订阅通知即可接收电池、入耳检测、噪声控制、对话感知与元数据等事件写入侧则覆盖了降噪切换、重命名、充电盒提示音、自适应噪声强度、耳机调节、自定义通透、长按配置以及头部追踪等命令。结合 LibrePods 在 AndroidAACPManager.kt、Packets.kt与 Linuxairpods_packets.h的源码实现你可以直接将本文的报文用于自己的跨平台 AirPods 控制实现或继续深入 opcodes.md、control_commands.md、device-info.md 探索更完整的协议空间。【免费下载链接】librepodsAirPods liberated from Apples ecosystem.项目地址: https://gitcode.com/gh_mirrors/al/librepods创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表