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

news2024/9/24 3:25:05

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

暂停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:浏览通道连接状态

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【通信原理(含matlab程序)】实验一 双边带模拟调制和解调

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

AcWing - 寒假每日一题2023(DAY 11——DAY 15)

文章目录一、AcWing 4656. 技能升级(困难)1. 实现思路2. 实现代码二、AcWing 4454. 未初始化警告(简单)1. 实现思路2. 实现代码三、AcWing 4509. 归一化处理(简单)1. 实现思路2. 实现代码四、AcWing 4699. …

OpenCV实战(8)——直方图详解

OpenCV实战(8)——直方图详解0. 前言1. 直方图概念2. 直方图计算2.1 灰度图像直方图计算2.2 彩色图像直方图计算3. 应用查找表修改图像3.1 查找表3.2 拉伸直方图提高图像对比度3.3 在彩色图像上应用查找表4. 图像直方图均衡化5. 完整代码小结系列链接0. …

操作流程违规作业监测系统 yolov7

操作流程违规作业监测系统通过pythonyolov7网络深度学习技术,对高危场景下作业人员未按照操作流程进行正常操作行为进行实时分析识别检测,发现现场人员违规作业操作行为,不需人为干预,立即自动抓拍存档预警。YOLOv7 在 5 FPS 到 1…

在 VSCode 中像写 TypeScript 一样写 JavaScript

大家好,我是前端西瓜哥。 我们在 VSCode 编辑器中编写 js 代码,是会提供类型提示的。 VSCode 会推断一个变量是什么类型,并在你输入内容的时候,提供对应的 API 属性或方法补全。 如下图,在 js 文件中,ar…

【Java】到底什么是包?|最通俗易懂讲解|保姆级

博主简介:努力学习的预备程序媛一枚~博主主页: 是瑶瑶子啦所属专栏: Java岛冒险记【从小白到大佬之路】 目录Part1:类比理解:Part2:与包(package)正式见面:2.1:包的本质--文件夹2.2&…

学习C++基本数值类型

写在前面 正在学习C/C/Javascript,面向初学者撰写专栏 博主原创C/C笔记(干货),如有错误之处请各位读者指正 请读者评论回复、参与投票,反馈给作者,我会获得持续更新各类干货的动力。 致粉丝:可以…

力扣刷题记录——709. 转换成小写字母、771. 宝石与石头、704. 二分查找

本专栏主要记录力扣的刷题记录,备战蓝桥杯,供复盘和优化算法使用,也希望给大家带来帮助,博主是算法小白,希望各位大佬不要见笑,今天要分享的是——《力扣刷题记录——709. 转换成小写字母、771. 宝石与石头…

C++11并发指南二(stdthread详解)

C11并发指南二(stdthread详解) 文章目录C11并发指南二(stdthread详解)std::thread 构造move 赋值操作其他成员函数上一篇博客《 C11 并发指南一(C11 多线程初探)》中只是提到了 std::thread 的基本用法,并给出了一个最…

{(leetcode 题号:169. 多数元素)+(189. 轮转数组)}时间复杂度与空间复杂度分析:

目录 一. 基本概念 1.时间复杂度 2.空间复杂度 二.实例分析 实例(1):旋转数组 方法1:暴力旋转法(时间复杂度加空间复杂度分析) 方法2 :三步整体逆序法 (时间复杂度加空间复杂度分析) 实例(2):斐波那契递归的时间复杂度和空间复杂度分析 实例(3):169. 多数元素…

模拟实现C库函数(1)

"啊~所有经历给它赋予魔力。"很久没更新过C专栏的文章了,借复习(review)的机会,本节的内容针对我们一些常见、常用的C库函数的模拟实现。“当你行走了一段时间后,回头往往那不管是起初咿咿呀呀胡乱踩陷的小坑时,还是之后…

C++11并发指南三(stdmutex详解)

C11并发指南三&#xff08;std:mutex详解&#xff09; 文章目录C11并发指南三&#xff08;std:mutex详解&#xff09;<mutex> 头文件介绍Mutex 系列类(四种)Lock 类&#xff08;两种&#xff09;其他类型函数std::mutex 介绍std::mutex 的成员函数std::recursive_mutex 介…

miracl

文章目录Windows平台编译网址 https://miracl.com/https://github.com/miracl/MIRACL Windows平台编译 源码目录下新建文件夹ms32或ms64&#xff0c;把/lib/ms32doit.bat或ms64doit.bat分别拷进去。 把源码include和source目录所有文件拷贝进要编译的ms32或ms64&#xff0c…

32. 实战:PyQuery实现抓取TX图文新闻

目录 前言 &#xff08;链接在评论区&#xff09;&#xff08;链接在评论区&#xff09;&#xff08;链接在评论区&#xff09; 目的 &#xff08;链接在评论区&#xff09;&#xff08;链接在评论区&#xff09;&#xff08;链接在评论区&…

ATAC-seq分析:Motifs分析(11)

1. 切割位点 ATACseq 应该在较小的保护区&#xff08;如转录因子结合位点&#xff09;周围生成较短的片段&#xff08;我们的无核小体区域&#xff09;。 因此&#xff0c;我们可以在不同组织/细胞类型/样本中寻找围绕感兴趣基序的切割位点堆积。 为了从我们的 BAM 文件中生成切…

FecMall多语言商城宝塔安装搭建教程

FecMall多语言商城宝塔安装搭建教程 1.1、删除禁用函数 PHP管理→禁用函数&#xff0c;删除putenv、pcntl_signal函数 如果不删除会报错&#xff1a;[ErrorException] pcntl_signal() has been disabled for security reasons 1.2下载fecmall 进入如下目录中cd /www/wwwroot 下…

行为型模式-中介模式

1.概述 一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关系会呈现为复杂的网状结构&#xff0c;这是一种过度耦合的架构&#xff0c;即不利于类的复用&#xff0c;也不稳定。例如在下左图中&#xff0c;有六个…

LeetCode 2325. 解密消息

给你字符串 key 和 message &#xff0c;分别表示一个加密密钥和一段加密消息。解密 message 的步骤如下&#xff1a; 使用 key 中 26 个英文小写字母第一次出现的顺序作为替换表中的字母 顺序 。 将替换表与普通英文字母表对齐&#xff0c;形成对照表。 按照对照表 替换 mess…

〖产品思维训练白宝书 - 核心竞争力篇⑤〗- 产品经理核心竞争力解读之如何培养创造力

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

【从零开始】力扣刷题(1)

文章目录前言数组&#xff1a;数组的遍历485.最大连续的一个数495.提莫攻击414.第三大的数628.三个数的最大乘积数组&#xff1a;统计数组中的元素645.错误的集合697.数组的度484.找到所有数组中消失的数组442.数组中重复的数据41.缺失的第一个正数274.H指数前言 我根据这里的…