问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Android 车载蓝牙音乐开发指南

创作时间:
2025-01-22 02:54:08
作者:
@小白创作中心

Android 车载蓝牙音乐开发指南

公司近期要开发一款智能音箱,这款音箱需要通过蓝牙与手机连接,实现音乐播放控制和信息显示功能。本文将详细介绍如何基于Android系统实现这一功能,主要涉及A2DP和AVRCP两个蓝牙协议的使用,以及Android MediaSession框架的具体实现步骤。

1、A2DP协议

A2DP(Advanced Audio Distribution Profile)是一种蓝牙协议,用于在蓝牙设备之间传输高质量音频流。它允许音频设备(如手机、平板电脑、音频播放器等)通过蓝牙无线连接与音频输出设备(如耳机、扬声器、汽车音响系统等)进行连接,并传输高质量的音频数据。

A2DP协议通过蓝牙连接传输音频数据,支持立体声音频流,并提供了高质量的音频传输体验。这使得用户可以通过蓝牙连接在各种设备之间传输音频,而无需使用传统的有线连接。

在Android开发中,开发人员可以利用A2DP协议来实现蓝牙音频传输功能,例如将手机上的音频流通过蓝牙连接传输到汽车音响系统或蓝牙耳机上。

监听A2DP协议广播:

private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive");
    }
};

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
registerReceiver(broadcastReceiver, intentFilter);

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

private MediaBrowserCompat mMediaBrowser;
private MediaControllerCompat mController;
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 获取播放数据

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 监听音乐播放变化

/**
 * 被动接收蓝牙播放信息、状态改变
 */
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、播放控制

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中, 处理这个消息

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。

时序图:



© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号