公司近期要做一款智能音箱, 这款音箱上面有一块屏幕,屏幕上的板子跑的Android系统。 音箱嘛, 肯定可以通过手机蓝牙连接, 播放手机APP的音乐, 同时可以通过音箱屏幕反向控制手机APP的播放, 实现暂停上一首下一首等。 基本上类似于 基于Android 的车载蓝牙音乐播放器。
对此我们要实现两个功能:
1、音箱屏幕能够显示手机APP当前播放的音乐信息。(歌曲名称, 专辑, 歌手等信息) 2、音箱屏幕能够反向控制手机APP的音乐播放。(暂停, 播放, 上一首, 下一首等)
对于这两个功能, 主要涉及A2DP 和 AVRCP 协议。下面先介绍一下这两个协议。
1、A2DP协议
A2DP(Advanced Audio Distribution Profile)是一种蓝牙协议,用于在蓝牙设备之间传输高质量音频流。它允许音频设备(如手机、平板电脑、音频播放器等)通过蓝牙无线连接与音频输出设备(如耳机、扬声器、汽车音响系统等)进行连接,并传输高质量的音频数据。
A2DP协议通过蓝牙连接传输音频数据,支持立体声音频流,并提供了高质量的音频传输体验。这使得用户可以通过蓝牙连接在各种设备之间传输音频,而无需使用传统的有线连接。
在Android开发中,开发人员可以利用A2DP协议来实现蓝牙音频传输功能,例如将手机上的音频流通过蓝牙连接传输到汽车音响系统或蓝牙耳机上。
2、AVRCP协议
AVRCP(Audio/Video Remote Control Profile)是一种蓝牙协议,旨在允许控制设备(如手机、平板电脑)通过蓝牙连接远程控制音频播放设备(如蓝牙耳机、汽车音响系统)的播放、暂停、跳转等功能。
AVRCP协议定义了一组命令和事件,使得远程控制音频设备成为可能。这些命令和事件包括播放、暂停、停止、上一曲、下一曲、音量控制等。通过AVRCP协议,控制设备可以向音频播放设备发送这些命令,从而实现对音频播放的远程控制。
此外,AVRCP还支持显示音频设备的元数据信息,如歌曲名称、艺术家信息、专辑信息等。这使得控制设备可以获取并显示连接的音频设备上正在播放的音频内容的相关信息。
在Android开发中,开发人员可以利用AVRCP协议来实现蓝牙音频设备的远程控制功能。通过Android的蓝牙API,开发人员可以发送AVRCP命令给连接的蓝牙音频设备,并接收来自音频设备的状态更新和元数据信息,从而实现对音频播放的远程控制和交互。
3、 如何实现?Android MediaSession 框架
从上面的架构图可以看出AVRCP的架构类似于蓝牙的其他协议,但也有不同。不同之处在于应用层还通过安卓系统中的媒体浏览器服务MediaBrowserService 与蓝牙服务进行通信,为何要多此一举呢?
查看安卓官方说明,安卓系统通过媒体浏览器服务已经为大家提供了一套完整的音乐控制解决方案,并进行了封装。所以音乐类应用通过媒体浏览器服务可以轻松实现音乐控制等功能。
蓝牙音乐应用根据当前系统的安卓版本通过构建相应的 ComponentName 来初始化媒体浏览器服务的客户端也即是 MediaBrowser 来连接媒体浏览器服务的服务端 MediaBrowserService ,连接成功后应用获取到 MediaController 来控制音乐。
蓝牙音乐应用最终通过 MediaController.getTransportControls() 提供的音乐控制接口下发相应的指令,指令经过媒体浏览器服务转送到蓝牙服务中,通过蓝牙技术传输到远端设备执行响应的动作,最终达到控制蓝牙音乐的目的。
4、Android MediaSession 框架实现
1、 创建 MediaBrowserCompat
arduino
private MediaBrowserCompat mMediaBrowser;
private MediaControllerCompat mController;
csharp
private void connectRemoteService() {
// 1.待连接的服务
ComponentName componentName = new ComponentName("com.android.bluetooth", "com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService");
// 2.创建MediaBrowser
mMediaBrowser = new MediaBrowserCompat(mContext, componentName, mConnectionCallbacks, null);
// 3.建立连接
mMediaBrowser.connect();
}
2、通过MediaBrowserCompat.ConnectionCallback 获取播放数据
java
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
new MediaBrowserCompat.ConnectionCallback() {
@Override
public void onConnected() {
Log.d(TAG, "MediaBrowser.onConnected");
if (mMediaBrowser.isConnected()) {
try {
mController = new MediaControllerCompat(MainActivity.this, mMediaBrowser.getSessionToken());
mController.registerCallback(mMediaControllerCallback);
// 获取播放数据
if (mController.getMetadata() != null) {
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onConnectionSuspended() {
// 连接中断回调
Log.d(TAG, "onConnectionSuspended");
}
@Override
public void onConnectionFailed() {
Log.d(TAG, "onConnectionFailed");
}
};
初始化 mController, 通过mController.getMetadata() 获取正在播放的数据。
3、通过 MediaControllerCompat.Callback 监听音乐播放变化
typescript
/**
* 被动接收蓝牙播放信息、状态改变
*/
MediaControllerCompat.Callback mMediaControllerCallback =
new MediaControllerCompat.Callback() {
@Override
public void onSessionDestroyed() {
// Session销毁
Log.d(TAG, "onSessionDestroyed");
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// 循环模式发生变化
Log.d(TAG, "onRepeatModeChanged");
}
@Override
public void onShuffleModeChanged(int shuffleMode) {
// 随机模式发生变化
Log.d(TAG, "onShuffleModeChanged");
}
@Override
public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
// 当前蓝牙播放列表更新回调
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
// 数据变化
Log.e(TAG, "onMetadataChanged ");
}
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
// 播放状态变化
Log.d(TAG, "onPlaybackStateChanged PlaybackState:" + state.getState());
}
};
当播放的音乐信息改变时, 会回调 onMetadataChanged 方法, 放播放状态改变时, 会回调 onPlaybackStateChanged方法, 可以处理相应的UI
4、播放控制
scss
private void playControl() {
MediaControllerCompat.TransportControls transportControls = mController.getTransportControls();
// 暂停
transportControls.pause();
// 播放
transportControls.play();
// 上一首
transportControls.skipToNext();
// 下一首
transportControls.skipToPrevious();
}
通过 MediaControllerCompat.TransportControls 提供的方法, 进行播放控制。
5、源码探索
由注释可以看出, 当远程段的音乐信息改变时, Android 系统 JNI层会调用 AvrcpControllerService 的 onTrackChanged 方法, 将远端音乐信息改变的消息传递给 Framework层。 在 onTrackChanged 方法中 , 调用AvrcpControllerStateMachine 的 sendMessage方法。
在AvrcpControllerStateMachine中, 处理这个消息
arduino
325 case MESSAGE_PROCESS_TRACK_CHANGED:
326 mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
327 BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
328 return true;
在在AvrcpControllerStateMachine处理 MESSAGE_PROCESS_TRACK_CHANGED 中 主要做 了两件事情。
1、通知mAddressedPlayer播放器, 播放信息改变。 播放器播放更新后的音乐信息 2、调用 BluetoothMediaBrowserService 的 trackChanged方法, 猜一下应该是要回调到 MediaSession 里面
让我们接着往下追
调用了 MediaSession 的 setMetadata 方法。
看起来是个Binder IPC调用, 我们看看这个mBinder 是什么
调用了 MediaSessionManager 的 createSession 方法
其中 mService是MediaSessionService#SessionManagerImpl的远程代理
调用了 createSessionInternal 方法
从图中可以看出, 实际上是创建了 createSessionInternal 方法 实际上创建了 MediaSessionRecord对象。
回到之前的逻辑, MediaSession iBinder 对象的服务端就是 MediaSessionRecord对象。
MediaSession 的 setMetadata 方法, 实际上调用了 MediaSessionRecord对象 setMetadata 方法。
使用 handler 转发了消息
最后调用了 mCallback.onMetadataChanged(metadata), 也就是我们自己注册的callback。
参考: