SDK路径:frameworks/base/core/java/android/bluetooth/
服务路径:packages/apps/Bluetooth/src/com/android/bluetooth/
在使用协议类的时候无法找到该类,由于安卓源码中关于蓝牙协议的 Client 部分或相关接口都被 @hide 给隐藏掉了,这样 android.jar 满足不了安卓源码 framework 层开发人员的需求,可以使用反射机制或者引用 framework.jar 代替 android.jar。
位置:out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar
1、协议版本介绍
AVRCP协议版本变化,版本之间都是向下兼容的关系:
v1.0:基本的远程控制命令,如播放、暂停、切歌
v1.3:新增获取音乐当前的播放状态以及播放音乐的歌曲信息(歌曲总时长、当前播放位置、歌曲名、专辑名、歌手)
v1.4:新增浏览功能,支持绝对音量调节
v1.5:相关协议已通过的更改以纠正各种错误
v1.6:新增两个特性:
1)项目的数量:用于控制器的接口,请求和接收文件夹中的项数,而无需下载列表
2)封面艺术:支持通过基于OBEX协议上的BIP(Basic Imaging Profile)协议将图像传输到媒体项目
所以如果两端设备的AVRCP协议都支持1.6及以上,则可实现通过蓝牙传输图片的功能。由于蓝牙传输数据量的限制,该功能也只是适用于音乐专辑封面照等小数据量的传输,而不适合大批量图片的传输。
2、Avrcp 结构
从上面的架构图可以看出AVRCP的架构类似于蓝牙的其他协议,但也有不同。不同之处在于应用层还通过安卓系统中的媒体浏览器服务 MediaBrowserService 与蓝牙服务进行通信,为何要多此一举呢?
查看安卓官方说明,安卓系统通过媒体浏览器服务已经为大家提供了一套完整的音乐控制解决方案,并进行了封装。所以音乐类应用通过媒体浏览器服务可以轻松实现音乐控制等功能。对蓝牙音乐也不另外,从而安卓蓝牙对外提供的接口文件 BluetoothAvrcpController 中,从安卓N版本(API:24)及之后的版本是没有音乐控制的接口,而之前的版本通过 BluetoothAvrcpController.sendPassThroughCmd() 接口直接将控制指令下发到蓝牙服务层。
蓝牙音乐应用根据当前系统的安卓版本通过构建相应的 ComponentName 来初始化媒体浏览器服务的客户端,也即是 MediaBrowser 来连接媒体浏览器服务的服务端 MediaBrowserService,连接成功后应用获取到 MediaController 来控制音乐。
因为ComponentName指明了bind哪个服务,从而可以正确找到蓝牙服务中对应于媒体浏览器的服务。根据蓝牙服务的清单文件AndroidManifest.xml指定,应用构建相应的ComponentName,构建此变量需要提供包名package和类名class。
mComponentName = new ComponentName(package,class);
客户端:MediaBrowser + MediaController
MediaBrowser 媒体浏览器,用来连接媒体服务 MediaBrowserService 和订阅数据,在注册的回调接口中我们就可以获取到Service的连接状态、获取音乐数据。一般在客户端中创建。
MediaController 媒体控制器,在客户端中工作,通过控制器向媒体服务器发送指令,然后通过MediaControllerCompat.Callback设置回调函数来接受服务端的状态。
服务端: MediaBrowserService + MediaSeesion
MediaBrowserService:浏览器服务,实现具体的媒体逻辑
MediaSeesion:这里就是客户端指令送达的地方。也会在媒体信息或状态改变后,通知客户端。
3、接口介绍
SDK接口
AvrcpControllerService.java:蓝牙apk中的 Avrcp 协议的代理类,应用通过此代理类访问Avrcp 协议的方法。
onConnectionStateChanged:Avrcp的连接由协议栈触发,一般是A2DP连接后,协议栈通知Avrcp连接状态
onTrackChanged:歌曲信息回调,包含歌曲title、artist、playingTime、cover等基础信息,对应实体类AvrcpItem.java
onPlayPositionChanged:歌曲播放进度回调
onPlayStatusChanged:歌曲播放状态回调,收到此回调后,FW会根据上次的Position和系统时间差,重新计算歌曲最新的播放进度。
重点关注的接口:
/**
* 向远程发送控制命令
*
* @param device 要连接的远程设备
* @param keyCode 是控制信号的键值
* @param keyState 是键值的状态,0表示按下,1表示释放
* @return 发送成功返回true,失败则返回false
*/
public boolean sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { }
这些控制信号存放在 BluetoothAvrcp 类里,客户端通过上面的方法发送到服务端。
服务端接口
BluetoothMediaBrowserService.java:蓝牙音乐Service具体实现文件。蓝牙音乐应用最终通过 MediaController.getTransportControls() 提供的音乐控制接口下发相应的指令,指令经过媒体浏览器服务转送到蓝牙服务中,通过蓝牙技术传输到远端设备执行响应的动作,最终达到控制蓝牙音乐的目的。
trackChanged(AvrcpItem track) :和App通信的主要接口,通过mSession.setMetadata(track.toMediaMetadata()),将歌曲信息给到App。
notifyChanged(PlaybackStateCompat playbackState) : 和App通信的主要接口,通过mSession.setPlaybackState(playbackState),将歌曲播放状态和进度给到App。
// 继承MediaBrowserServiceCompat
public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat
// 提供trackchanged方法,通过setMetadata,将媒体信息通知App
static synchronized void trackChanged(AvrcpItem track) {
if (sBluetoothMediaBrowserService != null) {
if (track != null) {
sBluetoothMediaBrowserService.mSession.setMetadata(track.toMediaMetadata());
} else {
sBluetoothMediaBrowserService.mSession.setMetadata(null);
}
} else {
Log.w(TAG, "trackChanged Unavailable");
}
}
AvrcpControllerStateMachine.java:蓝牙音乐处理App指令文件。
broadcastConnectionStateChanged(int currentState) :对外通知Avrcp的连接状态
MediaSessionCompat.Callback mSessionCallbacks:App 通过 MediaSession 框架下发的指令都会调到这里包括 onPause() 、onPlay() 、onSkipToNext()、onSkipToPrevious() 等,在由AvrcpControllerService调到协议栈。
// 通过MediaSession.CallBack来处理App的音乐指令
MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
requestAudioFocus();
sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
}
……
}
BluetoothMediaBrowserService 中我们可以直接调用的方法:
接口名 | 描述 |
---|---|
play | 发送AVRCP播放命令 |
pause | 发送AVRCP暂停命令 |
getPlaybackState | 获取播放状态 |
getTransportControls | 获取用于控制回放的对象 |
setActive | 设置为活动 |
getSession | 获取更新状态的媒体会话 |
可以看到用于媒体控制的方法只有播放和暂停,无法直接满足我们的需求,这就需要我们通过 getTransportControls 接口拿到 MediaControllerCompat.TransportControls 去控制蓝牙音乐。下面看一下 TransportControls 的接口:
功能 | 接口 TransportControls | 回调函数 MediaSessionCompat.Callback |
---|---|---|
播放 | play | onPlay |
暂停 | pause | onPause |
停止 | stop | onStop |
下一首 | skipToNext | onSkipToNext |
上一首 | skipToPrevious | onSkipToPrevious |
指定位置播放 | seekTo | |
快进 | fastForward | onFastForward |
回倒 | rewind | onRewind |
指定位置播放 | skipToQueueItem | onSkipToQueueItem |
指定id播放 | playFromMediaId | onPlayFromMediaId |
搜索播放 | playFromSearch | |
指定uri播放 | playFromUri | |
发送自定义动作 | sendCustomAction | |
打分 | setRating |
代码位置:
/packages/apps/Bluetooth/src/com/android/bluetooth/audio_util/mockable/MediaController.java
/packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
4、Avrcp 协议连接流程
通过上面的接口可以看出,没有提供连接的接口,那么AVRCP是如何连接的呢?因为AVRCP和A2DP协议通常都是一起使用的,A2DP连接成功后,sink 端的协议栈会主动发起AVRCP的连接。我们只需要关注AVRCP协议连接状态的广播即可。
ACTION_CONNECTION_STATE_CHANGED:监听AVRCP的连接状态改变
ACTION_BROWSE_CONNECTION_STATE_CHANGED:浏览通道连接状态
4、Avrcp 命令控制流程
其实之前 SDK 部分已经介绍了自身播放、暂停的调用和媒体播放器功能的调用,但是在上面的 SDK 中还发现一个 sendGroupNavigationCmd 接口也是用于发送控制命令的。
BluetoothAvrcpController.sendGroupNavigationCmd()
public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
final IBluetoothAvrcpController service = getService();
if (service != null && isEnabled()) {
try {
service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource);
return;
} catch (RemoteException e) {
return;
}
}
if (service == null) Log.w(TAG, "Proxy not attached to service");
}
这里向下传递时增加了一个 mAttributionSource 参数,该参数为创建 BluetoothAdapter 时通过 BluetoothManager 设置的蓝牙基础属性。
AvrcpControllerService.sendGroupNavigationCmd()
@Override
public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState, AttributionSource source) {
Attributable.setAttributionSource(device, source);
AvrcpControllerService service = getService(source);
if (service == null) {
return;
}
Log.w(TAG, "sendGroupNavigationCmd not implemented");
}
流程走到这里可以看到并没有对后面的参数进行处理,也没有了下面的流程。所以怀疑 sendGroupNavigationCmd 是之前版本的遗留方法,因为在 Android 9.0 中使用过调用 sendGroupNavigationCmd 下发控制命令的:
btManager.sendGroupNavigationCmd(BluetoothAvrcp.PASSTHROUGH_ID_PAUSE);
Android 9.0 中的 AvrcpControllerService.sendGroupNavigationCmd():
public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode,
int keyState) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Message msg = mAvrcpCtSm.obtainMessage(
AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
keyCode, keyState, device);
mAvrcpCtSm.sendMessage(msg);
}
这里通过状态机将参数信息发送出去,而前面 Android 12 的代码中并没有对后面两个参数进行处理。