蓝牙音频协议——安卓开发
协议
AVRCP(Audio Vidoe Remote Control Protocol,音频视频远程控制协议),区分为CT(Control)和TG(Target)两端,TG就是受控端。

A2DP(Advenced Audio Distribution Profile,即蓝牙音频传输模型协定),和音频模型一样区分Sink和Source两端

AVRCP 用于控制,如播放/音量等按键。安卓属性 bluetooth.profile.avrcp.target.enabled
A2DP 用于音频流传输,除此之外还有LDCP等等更好的
底层
基于蓝牙HCI串口协议
抓包btsnoop后使用协议分析软件
如Wireshark/ellisys better_analysis打开
setprop persist.bluetooth.btsnooppath /data/misc/bluetooth/logs/btsnoop_hci.cfa
setprop persist.bluetooth.btsnoopenable true
setprop persist.bluetooth.btsnooplogmode full
# 重启
# 抓 btsnoop 之前,先关蓝牙,然后再打开
复现问题后需要把 btsnoop 文件及时报错,否则重新开关蓝牙后之前的 btsnoop 会被覆盖
同时,导出此过程 logcat
adb pull /data/misc/bluetooth/logs/ ./
adb logcat -d > bt_log.log
```c
## RK蓝牙文档说明
### Libbt
负责蓝牙初始化,如加载蓝牙固件等操作。不同厂家都有定制化。
如 hardware/broadcom/libbt和 hardware/realtek/rtkbt/code/lifbt-vendor。
libbt一般只需要配置蓝牙端口和固件路径即可。
配置文件:
- hardware/broadcom/libbt/conf/rockchip/rksdk/bt_vendor.conf
- hardware/realtek/rtkbt/code/vendor/etc/bluetooth/rtkbt.conf
### bluedroid
位于 system/bt 下,产物为 libbluetooth.so
日志级别:
- 板卡 /system/etc/bt_stack.conf
- 属性 persist.bluetooth.btsnoopenable 和 persist.bluetooth.btsnoopsize
- 日志路径:/data/misc/bluetooth/logs/btsnoop_hci.log
例子
```c
[persist.bluetooth.btsnooppath]: [/data/misc/bluetooth/logs/btsnoop_hci.log]
[persist.bluetooth.btsnoopsize]: [0xffff]
[persist.bluetooth.disableabsvol]: [true] 绝对音量,禁用后CT可以调节TG音量
pcba bt测试
测试代码默认端口为 /dev/ttyS0 ,可以通过 /system/etc/bt_stack.conf 配置
AVRCP报文

音量控制按键走的是VENDOR DEPENDENT操作码[0x1958]报文,CT发送,TG确认。
播放暂停走的是Pass Through
AVRCP CT和TG都支持 CONTROL/STATUS/NOTIFY三种。

例子:


安卓源码
AN16为例,蓝牙模块 /packages/modules/Bluetooth
键值转换 android/app/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java

例子:某平台音量键正常,但播放暂停下一首没有响应

发送KEYCODE_MEDIA_PAUSE
input keyevent 127
可以看到歌曲正常暂停了,这里可以确认上层没有发送该键值。
过滤TG日志:
logcat|grep -i AvrcpTargetService &
logcat|grep -i OSMediaPlayer &


查看源码,发现发送按键是在sendMediaKeyEvent
过滤日志没找到,查看源码发现日志等级不够,但有dump接口
dumpsys bluetooth_manager|grep sendMediaKeyEvent
底层一切正常,那么就是软件本身的问题
实际上,安卓APP需要适配一个叫 MediaSession 的东西,才能有媒体控制的功能。

sendMediaKeyEvent 调用分析
架构框架图
调用流程图
关键代码位置
1. Native Stack
- 文件:
system/profile/avrcp/device.cc - 函数:
Device::MessageReceived()(line ~810) - 功能: 接收并解析AVRCP PassThrough命令
2. Native Interface Implementation
- 文件:
android/app/jni/com_android_bluetooth_avrcp_target.cpp - 类:
AvrcpMediaInterfaceImpl - 函数:
SendKeyEvent()(line ~131) - 功能: 调用JNI层的sendMediaKeyEvent
3. JNI Layer
- 文件:
android/app/jni/com_android_bluetooth_avrcp_target.cpp - 函数:
sendMediaKeyEvent()(line ~361) - 功能: 通过JNI CallVoidMethod回调Java层
4. Java Native Interface
- 文件:
android/app/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java - 函数:
sendMediaKeyEvent()(line 119) - 功能: 接收native回调,转发给Service
5. AVRCP Service
- 文件:
android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.java - 函数:
sendMediaKeyEvent()(line 511) - 功能: 转换按键码并分发给AudioManager
6. Key Code Converter
- 文件:
android/app/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java - 函数:
toKeyCode()(line 24) - 功能: 将AVRCP按键ID转换为Android KeyEvent码
按键映射示例
| AVRCP ID | 常量名 | Android KeyCode | 说明 |
|---|---|---|---|
| 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 | 音量减少 |
数据流
- 输入: AVRCP PassThrough命令 (操作ID + 按键状态)
- C++处理: 解析命令并通过接口回调
- JNI桥接: Native到Java的跨越
- Java转换: AVRCP ID → Android KeyCode
- 系统分发: AudioManager → MediaSession
- 输出: 媒体播放器响应(播放/暂停/切歌等)


浙公网安备 33010602011771号