MelodyCodecTweaker 是一个面向 OPPO / OnePlus「无线耳机」App 的 LSPosed 模块。它不会替换系统文件,也不会修改「无线耳机」App 安装包,而是在运行时注入音质控制项,让部分原本藏在系统蓝牙栈里的编解码器、播放质量、采样率和 LE Audio 状态可以直接在耳机控制面板里操作。
模块主要服务于 ColorOS / OPlus 系设备上的 com.oplus.melody,同时配合 com.android.bluetooth 和 com.oplus.wirelesssettings 作用域完成更稳定的状态读取和写入。
- 在「无线耳机」主面板
DetailMainActivity注入蓝牙音质区域。 - 在 OneSpace 快捷面板
OneSpaceDetailActivity注入同一套控制项。 - 显示当前协议:SBC、AAC、LDAC、LHDC、LC3 等。
- 支持播放质量切换,例如 LHDC 的连接优先、均衡、音质优先,以及 LDAC 的 330 / 660 / 990 kbps。
- 支持采样率切换,根据当前耳机和协议动态显示 44.1 / 48 / 96 / 192 kHz 等可选项。
- 播放质量和采样率会做联动修正,尽量避免写入蓝牙栈不接受的组合。
- 支持按耳机记忆选择,重新连接后自动应用上次设置。
- 支持 LE Audio 开关:开启后进入 LC3,并隐藏经典 A2DP 下的播放质量和采样率选项;关闭后恢复经典蓝牙音频状态。
- 耳机未连接、当前协议不可控、耳机不支持 Hi-Res 或 LE Audio 时,会隐藏或置灰对应选项,尽量贴近官方控件表现。
- 提供模块内置诊断页,可以查看作用域加载、页面 Hook、蓝牙桥接、无线设置桥接等状态。
- 提供一键收集反馈包,便于排查不同手机、系统版本、耳机型号和「无线耳机」版本带来的差异。
- Android 12 及以上。
- 支持 libxposed API 101 的框架,例如新版 LSPosed。
- OPPO / OnePlus / ColorOS 系统上的「无线耳机」App:
com.oplus.melody。 - 建议启用全部三个 LSPosed 作用域:
com.oplus.melodycom.android.bluetoothcom.oplus.wirelesssettings
com.oplus.melody 负责页面注入和用户交互,com.android.bluetooth 负责更稳定地读写 A2DP 编解码器状态,com.oplus.wirelesssettings 负责调用系统侧 LE Audio 能力。少开作用域可能仍能部分工作,但实时切换、状态回读和 LE Audio 会更容易失效。
- 安装模块 APK。
- 在 LSPosed 中启用模块。
- 勾选上面三个作用域。
- 强制停止「无线耳机」、蓝牙相关进程和无线设置,或者直接重启手机。
- 打开「无线耳机」App,进入耳机主面板或 OneSpace 面板查看注入项。
如果只想临时停用模块,可以打开桌面图标「欧加耳机音质助手」,关闭模块总开关。关闭后需要重启「无线耳机」进程,宿主页才会完全恢复原状。
模块桌面入口会显示:
- 模块总开关。
- 模块版本、手机型号、Android 版本。
- 「无线耳机」作用域是否加载。
- 页面 Hook 是否安装成功。
- 蓝牙作用域是否加载。
- A2DP Bridge 是否收到事件。
- 无线设置作用域是否加载。
- LE Audio 无线设置桥是否收到事件。
- 最近 Hook / 写入 / 状态同步日志。
如果出现「页面没有注入」「切换失败」「LE Audio 状态不刷新」这类问题,优先截一张诊断页图,基本可以先判断是作用域没生效、页面 Hook 丢了、蓝牙桥没收到,还是无线设置桥没工作。
诊断页里的「一键收集反馈包」会生成:
OPlusHeadsetAudioHelper-feedback-YYYYMMDD-HHMMSS.zip
优先保存到:
/storage/emulated/0/
如果系统不允许直接写入根目录,会降级保存到:
/storage/emulated/0/Download/
反馈包包含设备信息、模块版本、相关包版本、诊断状态、最近模块事件、scope.list、module.prop 和一份 logcat。它不会主动打包用户文件,但 logcat 里可能包含系统日志,请反馈前自行确认是否介意。
LE Audio 开关只会在模块判断当前设备支持时显示。判断来源包括「无线耳机」自身状态、系统蓝牙 UUID、蓝牙侧桥接回传和无线设置侧状态,避免出现「手机支持 LE Audio,但当前耳机不支持」时误显示。
开启流程:
- 用户在「无线耳机」主面板或 OneSpace 面板点击 LE Audio。
- 模块在当前 Melody Activity 内弹出确认对话框。
- 用户确认后,模块再向系统侧作用域发送请求。
com.oplus.wirelesssettings或蓝牙侧桥接调用系统LeAudioProfile.setEnabled(device, true)。- 蓝牙栈完成切换后回传状态,页面显示
蓝牙音质: LC3。 - 经典 A2DP 的播放质量和采样率行隐藏。
关闭 LE Audio 时,耳机通常会短暂断开并重新连回经典蓝牙音频。模块会延迟刷新 A2DP 状态,期间页面可能短暂显示等待状态,这是蓝牙栈重新协商造成的。
模块优先读取系统蓝牙栈中的实时能力,而不是硬编码所有耳机档位:
- 当前协议来自
BluetoothA2dp.getCodecStatus()。 - 播放质量来自
codecSpecific1能力。 - 采样率来自
sampleRatebitmask。 - 写入优先使用
setCodecConfigPreference()并等待系统广播确认。
写入路径会按能力降级:
- Melody 进程内直接反射 A2DP 隐藏 API。
- 通过
com.android.bluetooth中注册的 AIDL bridge 写入。 - 对 LDAC / 采样率尝试写入开发者选项
Settings.Global。 - 最后尝试 root shell fallback。
LHDC 的实时切换更依赖厂商蓝牙栈。模块会先做一次 LHDC priming,再写入目标播放质量 / 采样率组合。如果蓝牙栈拒绝当前组合,模块会尽量自动选择兼容采样率,例如从「均衡 / 48 kHz」切换到「音质优先」时先提升到可用采样率。
「无线耳机」App 经常经过 R8 混淆,直接绑定单个类名非常容易在更新后失效。当前模块做了这些兜底:
- 优先 Hook Manifest 中相对稳定的 Activity,例如
DetailMainActivity和OneSpaceDetailActivity。 - 同时 Hook Melody / COUI / AndroidX 的 PreferenceFragment 形态。
- 运行时扫描 FragmentManager,查找带有目标 PreferenceScreen 标记的页面。
- 通过 Preference key、页面结构和可见分类兜底寻找注入点。
- 从 Intent、Fragment / Activity 字段以及当前 active A2DP 设备解析当前耳机,兼容从系统设置跳转进 DetailMain 的路径。
- 对没有 Hi-Res、没有 LE Audio、没有对应协议能力的设备做隐藏或禁用处理。
- 系统侧蓝牙和无线设置也做了多点 Hook,降低系统更新后单点失效概率。
这些策略能覆盖同一大版本内较多小版本更新,但模块仍然依赖厂商私有页面结构和隐藏 API,不是公开 SDK。若「无线耳机」或系统更新后彻底改掉页面结构、资源 key、包名或蓝牙实现,仍可能部分失效甚至完全失效。
给普通用户的建议:
- 非必要不要频繁更新「无线耳机」App。
- 尽量固定在已验证可用的版本。
- 更新前保留旧版 APK,方便回退。
- 更新后如果模块失效,请提供新 APK、反馈包、手机型号、系统版本、耳机型号和截图。
- 模块只能控制当前耳机和系统已经协商出的 A2DP / LE Audio 能力,不能强行让耳机支持不存在的协议。
- 不支持 LE Audio 的手机或耳机不会显示 LE Audio 开关。
- 不支持 Hi-Res 或没有对应页面项的耳机会走备用注入点;如果页面结构完全不同,仍可能无法显示。
- 系统冻结
com.oplus.wirelesssettings、蓝牙栈重启或耳机重连期间,状态回读可能延迟几秒。 - 部分厂商蓝牙栈会拒绝特定播放质量 / 采样率组合,模块会尝试联动修正,但不能保证所有组合都能实时生效。
调试时可以抓取:
adb logcat -s MelodyCodecLsp:V常见关键字:
evt=scope.host.start/evt=scope.host.context.ready:无线耳机作用域是否加载。evt=preference.fragment.hooked:PreferenceFragment Hook 是否安装。evt=detailmain.activity.hooked:主面板 Activity Hook 是否安装。evt=onespace.activity.hooked:OneSpace Activity Hook 是否安装。evt=mac.resolved:当前耳机地址是否解析成功。detailmain_fallback.injected/hires_anchored.injected/onespace.injected:页面注入是否成功。evt=scope.system.context.ready:蓝牙作用域是否加载。evt=codec.updated.hooks:蓝牙侧编解码器更新 Hook 是否安装。evt=scope.wirelesssettings.context.ready:无线设置作用域是否加载。le.melody.state.recv:LE Audio 状态是否回传到 Melody。write.path:播放质量 / 采样率实际走了哪条写入路径。切换未生效:蓝牙栈拒绝本次写入或回读未确认。
项目使用 Android Gradle Plugin,目标 Java 17。release 构建当前关闭 R8,方便保留清晰的 Hook 排查路径。
本地构建:
gradle wrapper --gradle-version 8.10.2
./gradlew :app:assembleRelease输出位置:
app/build/outputs/apk/release/
GitHub Actions 分为两个入口:
Build APK:推送main/master、PR 或手动触发时执行,用于日常开发构建,产物名带dev和提交号。Release APK:仅手动触发。它会按 patch / minor / major 或指定版本号自动抬升versionName和versionCode,构建签名 APK,提交版本号变更,创建符合 Xposed Modules Repo 规则的versionCode-versionNametag(例如4-1.2.0),并在 GitHub Release 中写入手填说明和自动生成的提交记录。发布工作流还会把源码、tag 和 APK Release 自动同步到Xposed-Modules-Repo/xyz.melodylsp.codec,需要在源仓库配置LSP_REPO_TOKENsecret。
app/src/main/
├── AndroidManifest.xml
├── resources/META-INF/xposed/
│ ├── java_init.list
│ ├── module.prop
│ └── scope.list
├── aidl/xyz/melodylsp/codec/bridge/
└── java/xyz/melodylsp/codec/
├── MelodyCodecLspEntry.java
├── bt/ # A2DP 隐藏 API 反射
├── bridge/ # AIDL Parcelable 类型
├── diag/ # 诊断事件与反馈包
├── host/ # Melody 页面注入与 UI 控制
├── leaudio/ # LE Audio 状态、IPC 与无线设置桥接
├── storage/ # 按耳机保存记忆项
├── system/ # com.android.bluetooth 侧 bridge
├── ui/ # 模块内置诊断页和总开关
└── util/ # 日志
本项目使用 Apache-2.0 License。