Android 车载蓝牙音乐 (基于 Android 10)

公司近期要做一款智能音箱, 这款音箱上面有一块屏幕,屏幕上的板子跑的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。

参考:

手把手教你打通车载蓝牙与手机app的音频信息传输&车载反向控制手机app

蓝牙歌曲发送的原理(源码调用流程)

蓝牙音乐之AVRCP在安卓系统中的实现

MediaSession_MusicPlayer

android-media-controller

A2dp协议

ARVCP协议

相关推荐
王码码20351 小时前
Flutter for OpenHarmony: Flutter 三方库 cryptography 在鸿蒙上实现金融级现代加解密(高性能安全库)
android·安全·flutter·华为·金融·harmonyos
编程之路从0到12 小时前
ReactNative新架构之iOS端TurboModule源码剖析
react native·ios·源码阅读
亚历克斯神2 小时前
Flutter for OpenHarmony:Flutter 三方库 yaml_edit 精准修改 YAML 文件内容(保留注释与格式的编辑神器)
android·flutter·华为·harmonyos
左手厨刀右手茼蒿2 小时前
Flutter for OpenHarmony: Flutter 三方库 image_size_getter 零加载极速获取图片尺寸(鸿蒙 UI 布局优化必备)
android·服务器·flutter·ui·华为·harmonyos
亚历克斯神2 小时前
Flutter for OpenHarmony:zxing2 纯 Dart 条码扫描与生成库(不仅是扫码,更是编解码引擎) 深度解析与鸿蒙适配指南
android·flutter·华为·harmonyos
钛态2 小时前
Flutter for OpenHarmony:dio_cookie_manager 让 Dio 发挥会话管理能力,像浏览器一样自动处理 Cookie 深度解析与鸿蒙适配指南
android·linux·运维·flutter·ui·华为·harmonyos
键盘鼓手苏苏2 小时前
Flutter for OpenHarmony: Flutter 三方库 ntp 精准同步鸿蒙设备系统时间(分布式协同授时利器)
android·分布式·算法·flutter·华为·中间件·harmonyos
louisgeek5 小时前
Android ViewBinding
android
城东米粉儿7 小时前
Kotlin 协程的异常处理 笔记
android
锥栗7 小时前
【其他】基于Trae的大模型智能应用开发
android·java·数据库