Android 蓝牙开发——Avrcp协议(十二)

news/2024/11/1 21:43:39/

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() {@Overridepublic 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

暂停pauseonPause
停止stoponStop
下一首skipToNextonSkipToNext
上一首skipToPreviousonSkipToPrevious
指定位置播放seekTo
快进fastForwardonFastForward
回倒rewindonRewind
指定位置播放skipToQueueItemonSkipToQueueItem

指定id播放

playFromMediaIdonPlayFromMediaId
搜索播放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:浏览通道连接状态

 5、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 的代码中并没有对后面两个参数进行处理。


http://www.ppmy.cn/news/20179.html

相关文章

初学者如何学好Java数组,不妨点进来看看,赶在新年前肝完的万字博客

新年好~~~新年开篇万字博客 —Java数组的学习,有点干货,建议收藏观看!!! 本篇介绍了数组的概念,数组创建和初始化.数组的使用(元素访问,和数组遍历方法),初识引用数据类型,简单介绍JVM内存分布,认识null,堆区空间的释放 二维数组相关知识的介绍~ 学习Java中的数组一.数组的基本…

一起自学SLAM算法:10.3 机器学习与SLAM

连载文章,长期更新,欢迎关注: 前面已经分析过的8种SLAM算法案例(Gmapping、Cartographer、LOAM、ORB-SLAM2、LSD-SLAM、SVO、RTABMAP和VINS)都可以称为传统方法,因为这些算法都是在人为精心设计的特定规则下…

LeetCode[685]冗余连接II

难度:困难题目:在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。输入一个有向图&#xf…

原力分入门技能树-模拟

博客(blog) 以下哪种情况不可以提升原力分: 发高质量博客发低质量博客博客被点赞博客被评论 质量分(qc) 在以下哪个网址中可以查询博文质量分: https://ask.csdn.net/https://bbs.csdn.net/https://blog.csdn.net/https://www.csdn.net/qc 问答(as…

2022回顾

2022年回顾 前言 新年和亲朋好友的相聚差不多接近尾声,假期也所剩无几,开始静下心来写作,回顾一下我的2022年,看下自己去年 做得好的和不足,展望下2023,开始新一年的生活。(因为是公历2023年…

MFC|Toolbox内控件简单介绍

参考: MFC控件工具箱 (https://blog.csdn.net/Hubz131/article/details/77684910) 对应工具的超链接是本人搜到认为较易理解的单个控件介绍。 Pointer:就是普通的鼠标,默认状态Button:按钮,用…

【通信原理(含matlab程序)】实验三 数字基带信号及其频谱特性

💥💥💞💞欢迎来到本博客❤️❤️💥💥 本人持续分享更多关于电子通信专业内容以及嵌入式和单片机的知识,如果大家喜欢,别忘点个赞加个关注哦,让我们一起共同进步~ &#x…

2023编程语言趋势

2023编程语言趋势 作为CTO,我需要持续关注编程语言的发展。按照惯例,每年年初我都会对未来一年关键编程语言的趋势做一定的预判。今年由于众所周知的原因,预测地有些晚,我选择在开年的第一天给出我的预测,也算是祝大家…