转眼间,在XX音乐(国内著名音乐APP公司)工作了1年多了,作为Android多媒体开发的主力,必须奉上一点知识了。

今天,先说一下android播放音乐时如何在蓝牙设备上显示歌曲名、歌手、专辑等信息的。

在那个风和日丽、鸟语花香的日子,突然客服Miss Hu发来一个消息,问我说,有用户反馈说在车载蓝牙上播放歌曲看不到歌曲名、歌手、专辑等信息。

我当时虽然不是一脸懵逼,但对这个问题而言确实是只知其一不知其二。

其一,代码中并没有任何直接与蓝牙相关的任何操作;

其二,真不清楚如何控制蓝牙显示的。于是乎,开始深入这个问题......

一、首先,讲一下Android上面蓝牙的部分规范。

截止到现在,世界上已经发布了约40个蓝牙应用规范。先介绍一下最常用的2个。分别是:

1.Advanced Audio Distribution Profile 简称为A2DP(高质量音频分发规范)定义了如何将立体声质量的音频通过流媒体的方式从媒体源传输到接收器上,A2DP有两种应用场景分别是播放和录音。

2.Audio Video Remote Control Profile  简称为AVRCP,定义了蓝牙设备和audio/video控制功能通信的特点和过程。该Profile定义了AV/C数字命令控制集。命令和信息通过AVCTP协议进行传输。

也就是说,连接蓝牙耳机的时候一般使用A2DP协议,而控制和显示通过AVCTP协议实现。

上图来自Google I/O 2013 - Best Practices for Bluetooth Development

那么谷歌是怎么推荐通过Avrcp在蓝牙设备上显示歌曲信息的呢?请看下图

顺便附上视频链接,分秒都给你seek到了,看不了youtube的自己想办法

https://www.youtube.com/watch?v=EC5-cEbr520&feature=youtu.be&t=25m18s

二、那我们去深入一下RemoteControlClient和Avrcp (此时已是身不由己)

RemoteControlClient enables exposing information meant to be consumed by remote controls capable of displaying metadata, artwork and media transport control buttons.

RemoteControlClient暴露信息给具有遥控功能的显示媒体、艺术品和按钮控制设备。(请忽略本人的翻译不准确性

根据谷歌的说法,先往AudioManager里面注册一个RemoteControlClient实例,然后获取MetaDataEditor,往里面填充信息,然后执行MetaDataEditor.apply(),就是这么easy;

MetaDataEditor是什么? 这个不要问了,随便瞟两眼就知道了。

那么apply里面做了什么呢?

先看一下Android 4.3的源码,这里为什么先说这个版本,因为5.0系统与这个不一样,后面再详细解释。

apply里面根据参数不同,执行了不同的代码,我们只看sendMetadata_syncCacheLock好了。

先从mRcDisplays里面取出DisplayInfoForClient,发送IRemoteControlDisplay.setMetadata接口。

实现IRemoteControlDisplay.setMetadata接口共有下面几个:

我想大家已经看到了,Avrcp实现了这个,但是还需要确认一下

上面说了,这一切都是从mRcDisplays中来的,mRcDisplays又是什么?

它是一个ArrayList<DisplayInfoForClient>数组。

那么这么里面的DisplayInfoForClient又是哪里来的?

还是在RemoteControlClient这个类里面有一个方法onPlugDisplay里面有mRcDisplays.add(),从此处一一添加进去。

接着往下,onPlugDisplay是在一个MSG_PLUG_DISPLAY消息里面处理的。这个消息是从plugRemoteControlDisplay()这个方法里面执行的。

关键点来了,是谁搞了plugRemoteControlDisplay()这个?

讲到这里,开始跳入framework层代码,不卖关子了,这个是在AudioService里面执行了。

这个plugRemoteControlDisplaysIntoClient_syncRcStack方法是在

AudioService里面注册registerRemoteControlClient的时候调用了。

哈哈,看到这里,是不是想起了google官方介绍如何使用RemoteControlClient的,就是注册到AudioService。具体怎么玩,这里不讲了,因为不是这里的重点。

刚刚上面已经提到IRemoteControlDisplay.setMetadata去更新数据,那么这个IRemoteControlDisplay到底是哪里来的?

于是,上图已经给了答案了,是AudioService中的mRcDisPlays中的。

这个mRcDisPlays里面的内容是通过registerRemoteControlDisplay方法add进去的。

而registerRemoteControlDisplay是在AudioManager中调用的。查找引用,发现

也就是说Avrcp里面注册了这个registerRemoteControlDisplay。

这里就不说其他三个,重点还是蓝牙上面。

registerRemoteControlDisplay是在start里面执行的,start是在make里面执行的,make是在com.android.bluetooth.a2dp.A2dpServic.start里面的,而这个是在ProfileService启动的时候执行的,再往上就不深究了,这里已经有答案了。

这里的结论是IRemoteControlDisplay.setMetadata确实是发给Avrcp里面继承这个接口的元素了。

接下来看com.android.bluetooth.avrcp.Avrcp这个东西。

Avrcp中IRemoteControlDisplayWeak类继承扩展了这个接口,实现了setMetadata这个方法。

setMetadata执行了updateMetadata方法,将歌曲信息更新到内部的mMetadata里面。

至于如何发送,接收端如何显示,这里也不作详细解释了。

也就是说如果Android手机连接蓝牙播放,最好把歌曲信息、歌手、专辑等信息通过RemoteControlClient发送给蓝牙就行了,蓝牙设备就能对应的显示出来歌曲内容和播放状态。

还有一件事,不要忘了,上面讲的是Android4.3的系统。对于5.0以上系统和5.0以下系统是不一样的。

5.0系统RemoteControlClient中的MetaDataEditor.apply()是将MetaData给MediaSession传递过去的,在MediaSession中通过setMetadata(metadata)方法将metadata设置进去。

先调用AudioManager中的registerRemoteControlClient方法注册RemoteControlClient为rcClient。

而AudioManager中的registerRemoteControlClient有三个调用地方:

然后给rcClient注册MediaSessionLegacyHelper单例为helper。

rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mContext));

helper是MediaSessionLegacyHelper.getHelper(mContext)获取到的。

registerWithSession中helper添加了监听helper.addRccListener,然后获取MediaSession。

MediaSession是从ArrayMap<PendingIntent, SessionHolder> mSessions中的SessionHolder中取出。

mSessions是在getHolder()方法中put进去的。

也就是在MediaSessionLegacyHelper.addRccListener(),MediaSessionLegacyHelper.removeRccListener(),

MediaSessionLegacyHelper.addMediaButtonListener(),MediaSessionLegacyHelper.removeMediaButtonListener()

四个方法中实现的。

这也就是说为什么google官网要求RemoteCntrolClinet要跟mediabutton的注册要一起了。

提到这里就伤心不已,后来因为优化了mediabutton的注册策略,引起了蓝牙显示问题,唉,都是演技....

哎呀,又刹不住车了,扯远了,回归正题。

现在说说MediaSession.setMetadata(metadata);

mBinder是什么?

mBinder是通过MediaSessionManager.createSession(mCbStub, tag, userId)得到的。

也就是MediaSessionService.createSession()得到的。

进而得知返回的是类型为MediaSessionRecord的实例。

也就是说MediaSession.setMetadata实际是执行了MediaSessionRecord.setMetadata();

MediaSessionRecord.setMetadata()里面发送MSG_UPDATE_METADATA这个消息由MessageHandler处理,调用pushMetadataUpdate()方法,交给cb.onMetadataChanged处理,cb就是ISessionControllerCallback。

再来看com.android.bluetooth.avrcp.Avrcp文件,

在Avrcp.start()时,将一个构建了mRemoteController类注册到AudioService中,

mRemoteController中的RemoteControllerWeak用来监听MediaSession的变化。

当AudioManager.registerRemoteController()时,调用rctlr.startListeningToSessions()。

然后构建出一个SessionsListenerRecord添加到ArrayList类型的mSessionsListeners中。

重点来了,通过MediaSessionManager.getActiveSessions()方法将其设置到MediaController里面。

MediaController已经实现了ISessionControllerCallback接口,当接收到onMetadataChanged()时,发送MSG_UPDATA_METADATA消息执行mCallback.onMetadataChanged();

接着调用MediaControllerCallback.onMetadataChanged()执行onNewMediaMetadata(metadata)。

onNewMediaMetadata内部调用Avrcp中的RemoteControllerWeak.onClientMetadataUpdate()从而将歌曲信息更新到Avrcp中。

这样就将RemoteControlClient和Avrcp连接起来了,使用的是MediaSession。

而Android 4.3使用的则是RemoteControlDisplay,这就是二者的区别,却在app层接口基本是一致的。

好了,到这里基本算是搞定了RemoteControlClient和Avrcp的关系了,也算完成了蓝牙播放显示歌曲信息的功能了。

唉,这会感觉快吐血了。其实这一段反反复复斟酌了好久文字,担心读者搞不清楚,结果差点把自己也绕进去,还好我是这篇文章的原创,这口血已经咽回去了。

三、如果感觉上面太深(各位大神请见谅我的自夸,),同为媒体组的兄弟们还是不要笑话我了,

直接Show Code吧(亮剑)

这里因为业务需要我已经把MediaMetadata信息装进了HashMap,大家还是按照官方要求就行了。

同志们,到这里,你觉得完成了这个需求了吗?

别怕打击,这个时候其实只完成了55%(数据不确定性,完全凭个人感觉捏造),还有很多手机不能显示。为什么呢?

 

因为RemoteControlClient是从Android 4.0才出现的,那之前的系统呢?

所以,蓝牙肯定还有一种取信息的方式,至少一种。

这种方法是广播com.android.music.metachanged。

直接Show Code吧

Intent mediaIntent =newIntent("com.android.music.metachanged");

mediaIntent.putExtra("artist","歌手名称");

mediaIntent.putExtra("track","歌曲名称");

mediaIntent.putExtra("album","专辑名称");

mediaIntent.putExtra("duration", (Long) duration));

mediaIntent.putExtra("playing", (boolean) playing); //播放状态

getContext().sendBroadcast(mediaIntent); //豆沙绿的背景看起来是不是眼睛舒服多了.......

做完这一步,98%的蓝牙设备都能正常显示了,但是请记住,发送完这个广播之后,如果不小心执行了metadata.clear(), metadata.apply(),你的信息可能就会被清除了。

但是别忘了,还有2%的解决不了,为什么呢?

部分三星手机搞不掂(在官网论坛看到一个说法,跟自身的适配有关);

部分车载蓝牙显示异常,最起码我的车是这样的

(前面增加数字相关的字符串,其实我想说,车载蓝牙可能是一个很混乱的行业,不过腾讯、苹果等土豪已经涉足合作了,也许未来能统一起来)

比如,客户反馈一台宝马三系用我们产品蓝牙显示异常,于是我们真的去租了一辆BMW用来调试

不要质疑我们的行为,这不是炒作,我很负责的告诉你,我们媒体组真的很敬业。

 

到这里,特此感谢媒体组各位的支持和帮助,感谢某人的警示良言,让我坚持2天不松懈。

好了,到此为止了,真心累了,写文章真的很耗脑力体力,周末整个没休息,不过还是要多写写。

期待下一篇好文!

Android蓝牙播放如何显示歌曲信息?相关推荐

  1. 安卓源码避坑指南10—蓝牙音乐播放状态和歌曲信息不更新

    蓝牙音乐播放状态和歌曲信息不更新 安卓版本:android-9 (P版本) 问题现象:歌曲信息和蓝牙音乐的播放状态不更新,蓝牙音乐界面感觉卡死(其实是界面信息不更新,音频数据正常) 歌曲信息和播放状态 ...

  2. android音乐播放器_歌曲列表

    歌曲列表是来显示SD卡或手机内存中的歌曲,因为android会自动扫描媒体对象,直接使用MediaStore就可以显示歌曲名称.艺术家.缩略图等.再次使用ListView来显示这些信息,xml布局很简 ...

  3. android 取消蓝牙配对框,android - 蓝牙配对 - 如何显示简单的取消/配对对话框? - 堆栈内存溢出...

    我在GitHub为这个问题准备了一个简单的测试项目 . 我正在尝试创建一个Android应用程序,它将从计算机屏幕扫描QR代码,然后使用数据(MAC地址和PIN或哈希)与蓝牙设备轻松配对(绑定). 类 ...

  4. iOS锁屏显示歌曲信息

    导入头文件#import <MediaPlayer/MediaPlayer.h> 远程控制事件接收与处理 - (void)viewWillAppear:(BOOL)animated { [ ...

  5. Android蓝牙音乐获取歌曲信息

    由于我在蓝牙开发方面没有多少经验,如果只是获取一下蓝牙设备名称和连接状态那么前面的那篇文章就已经足够了,接下来的内容是转自一个在蓝牙音乐方面颇有经验的开发者的博客,他的这篇文章对我帮助很大. 今天,先 ...

  6. android 蓝牙歌名,从Android上的蓝牙CarKit上显示标题(不是歌曲或艺术家,但是有效的SIP会话)...

    所以我目前正在开发一个SIP拨号应用程序,我想知道如何更改蓝牙CarKit上显示的信息.我使用以下方法将音频路由到carkit: AudioManager localAudioManager = (A ...

  7. 写一个APP控制第三方播放器播放,以及获取正在播放的歌曲信息

    最近遇到这么一个需求,就是在自己的应用中控制第三方播放器播放,以及获取正在播放的歌曲信息,包括名字,歌手,专辑,显示出来.一开始觉得很简单,但实际上遇到了不少的麻烦,最终实现了两种方案,读者可根据自己 ...

  8. android蓝牙音乐之AVRCP介绍和使用

    引言 最近做的车载蓝牙音乐开发,遇到很多问题,记录一下.也是到处东拼西凑的,勉强看看吧. AVRCP:Audio/Video Remote Control Profile,音视频远端控制协议,所以该协 ...

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

    SDK路径:frameworks/base/core/java/android/bluetooth/ 服务路径:packages/apps/Bluetooth/src/com/android/blue ...

最新文章

  1. 条件注释判断浏览器!--[if !IE]!--[if IE]!--[if lt IE 6]!--[if gte IE 6]
  2. 从一道面试题分析Thread.interrupt方法
  3. 斯皮尔曼相关系数范围_数据的相关系数
  4. awk分析nginx日志里面的接口响应时间
  5. java开发工具软件排行榜
  6. Java集合——HashMap、HashTable以及ConCurrentHashMap异同比较
  7. green ethernet
  8. TensorFlow 2.0 - Checkpoint 保存变量、TensorBoard 训练可视化
  9. 打开数据库_打开这份指南,数据库运维也能优雅、简单!
  10. 要写related_name的两种情况
  11. jdbc批量更新_用集算器更新数据库的技巧
  12. 总结一下在ASP.NET中开发网站的一般步骤
  13. 管理感悟:复制代码是错误行为
  14. 本泽马梅开二度瓦拉内染红 10人皇马4:2客胜西班牙人
  15. oracle可以只装客户端吗,我想在linux下只装oracle客户端行吗?怎么装?
  16. python删除文本框内容_js清除文本框内容
  17. 2021全国大学生电子设计竞赛C题
  18. XUPT第三届新生算法赛
  19. 得到app文稿导出_得到app
  20. 中国人工智能学会通讯——当巧妇遇到“大米”——机器翻译启示录

热门文章

  1. 第29讲 常见算法(查找算法、递归算法、排序算法)
  2. Java小农养成记第六天
  3. 【Android UI】贝塞尔曲线 ① ( 一阶贝塞尔曲线 | 二阶贝塞尔曲线 )
  4. 超详细的MySQL8.0.20安装教程及其安装问题处理
  5. 相机模型和双目立体匹配完成一个基于KITTI立体相机采集图片的立体图像匹配程序,生成视差图像和3D点云图像
  6. matlab弹道仿真,战役战术导弹弹道仿真在simulink下如何实现
  7. 使用Scrapy框架,爬取b站番剧信息。
  8. 实现一个联系客服对话框的前端部分
  9. android checkbox自定义(文字位置、格式等)
  10. 多远线性回归代码-波士顿房价问题