Android 蓝牙音频协议详解
核心协议介绍
在蓝牙音频通信中,有两个关键协议:
- AVRCP(音频视频远程控制协议):用于控制操作,如播放、暂停、音量调节等。分为CT(控制器)与TG(目标设备)两个角色。
- A2DP(高级音频分发协议):用于高质量音频流的传输,通常配合Sink(接收端)和Source(发送端)使用。


底层实现机制
蓝牙音频协议运行于HCI(主机控制接口)之上,可通过抓取 btsnoop 数据包进行分析。
setprop persist.bluetooth.btsnooppath /data/misc/bluetooth/logs/btsnoop_hci.cfa
setprop persist.bluetooth.btsnoopenable true
setprop persist.bluetooth.btsnooplogmode full
# 重启蓝牙前关闭再开启
# 复现问题后及时导出日志,避免被覆盖
adb pull /data/misc/bluetooth/logs/ ./
adb logcat -d > bt_log.log
RK 平台蓝牙组件说明
- Libbt:负责初始化工作,包括固件加载。常见路径如下:
- Broadcom:
hardware/broadcom/libbt/conf/rockchip/rksdk/bt_vendor.conf - Realtek:
hardware/realtek/rtkbt/code/vendor/etc/bluetooth/rtkbt.conf
- Broadcom:
- Bluedroid:位于
system/bt目录下,编译生成libbluetooth.so库。
相关配置文件及参数:
- 日志级别设置:
/system/etc/bt_stack.conf - 日志保存路径:
/data/misc/bluetooth/logs/btsnoop_hci.log - 控制绝对音量属性:
persist.bluetooth.disableabsvol
AVRCP 控制报文格式

- 音量调整通过 VENDOR DEPENDENT 操作码(如 0x1958)完成,由 CT 发起请求,TG 回应确认。
- 播放控制类指令则采用 Pass Through 方式传递。
- CT 和 TG 同时支持 CONTROL、STATUS 和 NOTIFY 功能。

调试实例分析
假设某平台音量键可正常使用,但播放/暂停/下一曲无响应:
首先模拟发送暂停事件:
input keyevent 127
若应用未收到该事件,则需进一步排查 TG 端行为:
logcat | grep -i AvrcpTargetService
logcat | grep -i OSMediaPlayer

如果日志中找不到对应内容,可通过 dumpsys 查看详细调用栈:
dumpsys bluetooth_manager | grep sendMediaKeyEvent

Android 媒体会话机制
要使应用程序能接收并响应蓝牙遥控指令,必须正确注册 MediaSession:

sendMediaKeyEvent 执行流程
整体架构图
Sends AVRCP Command"] end subgraph "Bluetooth Native Stack" AVCTP["AVCTP Layer
(Protocol Handler)"] Device["Device.cc
MessageReceived()"] Interface["AvrcpMediaInterfaceImpl
SendKeyEvent()"] end subgraph "JNI Layer" JNI["com_android_bluetooth_avrcp_target.cpp
sendMediaKeyEvent()"] end subgraph "Java Framework" NativeInterface["AvrcpNativeInterface
sendMediaKeyEvent()"] AvrcpService["AvrcpTargetService
sendMediaKeyEvent()"] Passthrough["AvrcpPassthrough
toKeyCode()"] end subgraph "Android System" AudioManager["AudioManager
dispatchMediaKeyEvent()"] MediaSession["MediaSessionManager
Distributes to Active Player"] end Remote -->|"AVRCP PassThrough"| AVCTP AVCTP -->|"Parse Message"| Device Device -->|"Call Interface"| Interface Interface -->|"JNI Callback"| JNI JNI -->|"CallVoidMethod"| NativeInterface NativeInterface -->|"Forward"| AvrcpService AvrcpService -->|"Convert Key Code"| Passthrough AvrcpService -->|"KeyEvent"| AudioManager AudioManager --> MediaSession
具体执行步骤
e.g., PLAY/PAUSE/NEXT/PREV"] Receive["Device::MessageReceived()
Parses incoming message"] CheckOpcode{"Is Opcode PASS_THROUGH?"} CheckActive{"Is device active?"} GetKeyInfo["Extract Key Info:
• OperationId
• KeyState (PUSHED/RELEASED)"] CallInterface["media_interface_->SendKeyEvent()
AvrcpMediaInterfaceImpl"] JNICallback["sendMediaKeyEvent()
C++ JNI function"] JavaCallback["CallVoidMethod
JNIEnv callback to Java"] NativeIntf["AvrcpNativeInterface.sendMediaKeyEvent()"] CheckService{"Is AvrcpTargetService null?"} ServiceMethod["AvrcpTargetService.sendMediaKeyEvent()"] ConvertKey["AvrcpPassthrough.toKeyCode()
Map AVRCP ID to Android KeyCode"] CreateEvent["Create KeyEvent object
Action: DOWN/UP
KeyCode: Android standard"] DispatchEvent["AudioManager.dispatchMediaKeyEvent()"] MediaTarget["Deliver to active MediaSession controller"] End["MediaPlayer responds to event"] ErrorLog1["Log.w: AvrcpTargetService is null"] ErrorLog2["Return without processing"] Start --> Receive Receive --> CheckOpcode CheckOpcode -->|"Yes"| CheckActive CheckOpcode -->|"No"| ErrorLog2 CheckActive -->|"Yes"| GetKeyInfo CheckActive -->|"No (special handling)"| GetKeyInfo GetKeyInfo --> CallInterface CallInterface --> JNICallback JNICallback --> JavaCallback JavaCallback --> NativeIntf NativeIntf --> CheckService CheckService -->|"Null"| ErrorLog1 CheckService -->|"Valid"| ServiceMethod ErrorLog1 --> ErrorLog2 ServiceMethod --> ConvertKey ConvertKey --> CreateEvent CreateEvent --> DispatchEvent DispatchEvent --> MediaTarget MediaTarget --> End style Start fill:#e1f5fe style End fill:#c8e6c9 style ErrorLog1 fill:#ffcdd2 style ErrorLog2 fill:#ffcdd2 style ConvertKey fill:#fff9c4 style DispatchEvent fill:#f3e5f5
关键源码定位
- Native 层:
- 文件:
system/profile/avrcp/device.cc - 函数:
Device::MessageReceived()(约第810行) - 作用:解析 PassThrough 类型的 AVRCP 消息
- 文件:
- 接口实现:
- 文件:
android/app/jni/com_android_bluetooth_avrcp_target.cpp - 类:
AvrcpMediaInterfaceImpl - 函数:
SendKeyEvent()(约第131行) - 作用:调用 JNI 函数触发 Java 方法
- 文件:
- JNI 桥接:
- 文件:
android/app/jni/com_android_bluetooth_avrcp_target.cpp - 函数:
sendMediaKeyEvent()(约第361行) - 作用:从 C++ 到 Java 的跨语言调用
- 文件:
- Java 接口层:
- 文件:
android/app/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java - 函数:
sendMediaKeyEvent()(第119行) - 作用:接收 native 调用,并转发至服务层
- 文件:
- 服务逻辑:
- 文件:
android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java - 函数:
sendMediaKeyEvent()(第511行) - 作用:将 AVRCP 键值转换为 Android 标准键码,并交由系统处理
- 文件:
- 键码转换工具:
- 文件:
android/app/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java - 函数:
toKeyCode()(第24行) - 作用:建立 AVRCP ID 与 Android KeyEvent 映射关系
- 文件:
常用键值对照表
| AVRCP ID | 常量名称 | Android 键码 | 功能描述 |
|---|---|---|---|
| 0x44 | PASSTHROUGH_ID_PLAY | KEYCODE_MEDIA_PLAY | 播放 |
| 0x46 | PASSTHROUGH_ID_PAUSE | KEYCODE_MEDIA_PAUSE | 暂停 |
| 0x4B | PASSTHROUGH_ID_FORWARD | KEYCODE_MEDIA_NEXT | 下一曲 |
| 0x4C | PASSTHROUGH_ID_BACKWARD | KEYCODE_MEDIA_PREVIOUS | 上一曲 |
| 0x41 | PASSTHROUGH_ID_VOL_UP | KEYCODE_VOLUME_UP | 增大音量 |
| 0x42 | PASSTHROUGH_ID_VOL_DOWN | KEYCODE_VOLUME_DOWN | 减小音量 |
完整数据流转路径
- 输入:来自远程设备的 PassThrough 命令(含操作 ID 和按键状态)
- C++ 处理阶段:解析命令并调用接口回调
- JNI 过渡:连接 Native 与 Java 层的关键桥梁
- Java 转换:将 AVRCP ID 映射为 Android 标准键码
- 系统分发:AudioManager 将事件传递给当前活跃的 MediaSession
- 最终输出:媒体播放器根据事件做出相应动作(如播放、暂停或切换曲目)