

























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音量
测试代码默认端口为 /dev/ttyS0 ,可以通过 /system/etc/bt_stack.conf 配置

音量控制按键走的是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

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

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


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

flowchart TB subgraph "远程蓝牙设备" Remote["蓝牙耳机/车机<br/>发送AVRCP按键"] end subgraph "Bluetooth Native Stack" AVCTP["AVCTP协议层<br/>(AV/C Transport Protocol)"] Device["Device.cc<br/>MessageReceived()"] Interface["AvrcpMediaInterfaceImpl<br/>SendKeyEvent()"] end subgraph "JNI Layer" JNI["com_android_bluetooth_avrcp_target.cpp<br/>sendMediaKeyEvent()"] end subgraph "Java Framework" NativeInterface["AvrcpNativeInterface<br/>sendMediaKeyEvent()"] AvrcpService["AvrcpTargetService<br/>sendMediaKeyEvent()"] Passthrough["AvrcpPassthrough<br/>toKeyCode()"] end subgraph "Android System" AudioManager["AudioManager<br/>dispatchMediaKeyEvent()"] MediaSession["MediaSessionManager<br/>分发到活跃媒体播放器"] end Remote -->|"AVRCP<br/>PassThrough命令"| AVCTP AVCTP -->|"解析命令"| Device Device -->|"调用接口"| Interface Interface -->|"JNI回调"| JNI JNI -->|"CallVoidMethod"| NativeInterface NativeInterface -->|"转发"| AvrcpService AvrcpService -->|"转换按键码"| Passthrough AvrcpService -->|"KeyEvent"| AudioManager AudioManager --> MediaSession
flowchart TD Start["远程设备发送AVRCP PassThrough命令<br/>例如: PLAY/PAUSE/NEXT/PREV"] Receive["Device::MessageReceived()<br/>接收PassThrough消息包"] CheckOpcode{"检查Opcode<br/>是否为<br/>PASS_THROUGH?"} CheckActive{"设备是否<br/>为活跃设备?"} GetKeyInfo["获取按键信息<br/>• OperationId (按键类型)<br/>• KeyState (PUSHED/RELEASED)"] CallInterface["media_interface_->SendKeyEvent()<br/>AvrcpMediaInterfaceImpl"] JNICallback["sendMediaKeyEvent()<br/>Native函数(C++)"] JavaCallback["CallVoidMethod()<br/>调用Java方法"] NativeIntf["AvrcpNativeInterface<br/>.sendMediaKeyEvent()"] CheckService{"AvrcpTargetService<br/>是否为null?"} ServiceMethod["AvrcpTargetService<br/>.sendMediaKeyEvent()"] ConvertKey["AvrcpPassthrough.toKeyCode()<br/>AVRCP ID -> Android KeyCode<br/>例如: 0x44 -> KEYCODE_MEDIA_PLAY"] CreateEvent["创建KeyEvent对象<br/>action: ACTION_DOWN/ACTION_UP<br/>keyCode: Android标准按键码"] DispatchEvent["AudioManager<br/>.dispatchMediaKeyEvent()"] MediaTarget["分发到活跃的<br/>MediaSession控制器"] End["媒体播放器响应按键"] ErrorLog1["Log.w: AvrcpTargetService is null"] ErrorLog2["返回,不处理"] Start --> Receive Receive --> CheckOpcode CheckOpcode -->|"是"| CheckActive CheckOpcode -->|"否"| ErrorLog2 CheckActive -->|"是"| GetKeyInfo CheckActive -->|"否 (特殊处理)"| GetKeyInfo GetKeyInfo --> CallInterface CallInterface --> JNICallback JNICallback --> JavaCallback JavaCallback --> NativeIntf NativeIntf --> CheckService CheckService -->|"否"| ServiceMethod CheckService -->|"是"| ErrorLog1 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
system/profile/avrcp/device.ccDevice::MessageReceived() (line ~810)android/app/jni/com_android_bluetooth_avrcp_target.cppAvrcpMediaInterfaceImplSendKeyEvent() (line ~131)android/app/jni/com_android_bluetooth_avrcp_target.cppsendMediaKeyEvent() (line ~361)android/app/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.javasendMediaKeyEvent() (line 119)android/app/src/com/android/bluetooth/avrcp/AvrcpTargetService.javasendMediaKeyEvent() (line 511)android/app/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.javatoKeyCode() (line 24)| 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 | 音量减少 |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。