ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AVRCP 1.6升级实战:解决蓝牙封面与歌词显示问题

AVRCP 1.6升级实战:解决蓝牙封面与歌词显示问题 1. 先搞清楚AVRCP 1.6到底改了什么前段时间帮朋友调试一套车载蓝牙播放系统遇上个很典型的问题手机连上车机之后歌名、歌手、专辑名都能正常显示唯独封面图那块一直是默认的灰色占位图标。折腾了好一阵子才发现车机蓝牙协议栈里的AVRCP版本还停在1.4。把版本升到1.6、把metadata和图片句柄流程彻底走通之后封面才正常显示出来顺带把歌词也带出来了。这个经历让我觉得有必要把AVRCP 1.6的升级过程好好整理一下。因为实际工作中发现很多人对AVRCP的理解还停留在“能显示歌名”的层面对版本差异、角色模型、图片和歌词的传输机制一知半解出了问题也不知道该往哪个方向排查。这篇文章就围绕“AVRCP 1.6升级”这件事把协议层的变化、手机端配置、设备端实现、常见坑位一次讲清楚。先说明白这篇文章适合三类人一是做蓝牙耳机、音箱、车载系统的嵌入式工程师二是在Android/iOS端做音乐类App、想把封面和歌词推送给蓝牙设备的开发者三是纯粹好奇“为什么我耳机能看到封面别人看不到”的数码玩家。前两类人可以照着实操第三类人看懂了原理以后再遇到同类问题也能快速判断是不是版本惹的祸。1.1 从1.0到1.6版本演进里藏着哪些关键能力AVRCP全称是Audio/Video Remote Control Profile也就是“音频/视频远程控制规范”。它定义的是控制端比如手机和被控制端比如蓝牙耳机、车载主机之间如何传递播放状态、歌曲信息、控制命令。很多人不知道AVRCP其实是个“老家伙”第一版在蓝牙2.0时代就有了。它经历了以下几个重要节点版本发布背景核心能力1.0蓝牙2.0时代基础播放/暂停/上下一曲控制带一点歌曲信息1.3蓝牙2.1EDR时代增加Media Player信息、播放器名称、队列信息1.4蓝牙3.0时代引入Metadata传输增强支持歌曲名/歌手/专辑名等属性同时增加Browsing媒体浏览功能1.6蓝牙4.0时代绝对音量控制、图片句柄Image Handle、封面传输标准化歌词类的扩展字段开始出现这里很多人会问怎么没有1.5实际上AVRCP 1.5是一个过渡性版本主要在1.4基础上修补了一些东西没有大规模铺开。真正带来体验级变化的是1.6。可以说如果你想在蓝牙设备上看到专辑封面和歌词1.6是个分水岭——1.4及以前封面传输没有统一标准1.6之后才有了一套规范可循。1.2 1.6新增的两个核心绝对音量和图片句柄AVRCP 1.6里最容易被感知到的功能其实是“绝对音量”Absolute Volume。在此之前手机和耳机各管各的音量经常出现手机音量调到70%耳机耳机自己又有一套音量两边不同步。1.6把音量控制权统一到了手机端你在手机上拉音量条耳机硬件音量也跟着变反之亦然。这个功能现在看起来很基础但它确实是从1.6才真正规范化。第二个核心是“图片句柄”Image Handle。这个跟专辑封面直接相关。1.6协议里规定当手机端播放一首歌时除了要发送歌名、歌手、专辑名这些文本属性还需要发送一个Image Handle。这个句柄本质上一个“图片ID”指向媒体库里的一张封面图。耳机或车机拿到这个句柄之后再通过AVRCP的Browsing通道去查询图片的具体信息最后把图片拉取下来显示。为什么要把封面设计成“先给句柄、再取图片”的异步流程而不是直接随metadata一起发一个Bitmap因为封面图体积通常不小如果每切一首歌都把整张图塞进蓝牙控制通道带宽压力会很大播放也会出现明显的卡顿感。用句柄延迟加载封面加载可以和音频播放并行体验上好很多。1.3 歌词显示到底是不是AVRCP 1.6的标准功能这个要单独拎出来讲因为太多人误解了。严格来说AVRCP 1.6标准里并没有一个叫“Lyrics”的标准字段。不过很多厂商尤其日系音频品牌在1.6的基础上做了扩展利用Metadata里未被标准占用的扩展字段或者走自定义服务把逐行歌词LRC歌词推送到蓝牙设备上。像索尼的某些蓝牙耳机、一些车载主机显示的都是这种“私有扩展实现”。所以歌词不是AVRCP 1.6的标准能力但是1.6给这类多媒体扩展提供了更完善的基础——至少metadata通道和Browsing通道的机制在1.6之后才足够承载这些额外的数据传输。换句话说想让设备显示歌词首先你得把AVRCP升到1.6但升到1.6不代表一定有歌词还得看音乐App和设备端有没有共同实现那一套扩展字段。这点想清楚了后面排查方向才不会跑偏。2. 封面歌词从手机到设备这一路到底谁负责什么理解了协议特性还要把“链路”这个概念建立起来。很多人调试蓝牙设备时习惯从头到尾只盯着设备端代码结果发现怎么调都拿不到封面。真相往往是这条链路上每一个环节都在起作用任何一个环节没有按AVRCP 1.6的规则做事封面就出不来。2.1 一个典型场景里的三个角色一套完整的蓝牙播放链路里AVRCP协议区分两个角色CTController控制器通常是手机负责发起控制命令、发送metadata、管理播放状态。TGTarget目标设备通常是耳机、音箱、车机负责接收命令、显示信息、响应查询。但要注意有些场景下角色会互换。比如手机作为“媒体播放器”通过蓝牙连接车载系统时车机是CT手机是TG。手机的音乐App把播放信息传给手机蓝牙协议栈协议栈再通过AVRCP发给车机。这个场景里手机虽然是TG但仍然要主动上报“当前播放的歌曲是什么”。很多工程师在调试时只认“我是设备端”没有区分当前的CT/TG角色导致处理逻辑写反了。比如耳机端既要做TG接收控制命令又要在支持Browsing时作为CT去拉取图片两种角色涉及的服务、回调、事件都不一样写混了就会出现“歌名能显示但封面没反应”的诡异情况。2.2 数据流链路从MediaSession到显示屏以最常见的“手机音乐App - 蓝牙协议栈 - 蓝牙设备显示”链路为例封面的传输大致经历这么几步音乐App把歌曲信息填进系统的MediaSession包括标题、歌手、专辑名、时长以及封面图的Uri或Bitmap。系统蓝牙服务读取MediaSession的信息把文本属性封装成AVRCP 1.6的metadata格式发送给设备端。同时系统会为封面图生成一个Image Handle把句柄随metadata一起发给设备。设备端收到Image Handle后如果自带显示屏且支持封面显示就会通过AVRCP Browsing通道发起图片获取请求。系统收到请求后返回图片数据一般是一块JPEG或PNG数据设备端解码并显示。这个链路里如果音乐App没有设置封面图那后面的Image Handle根本不会生成设备端自然拿不到图。如果设备端没有实现Browsing通道查询流程即使手机发了Image Handle设备也不知道怎么去“取图”。所以很多问题其实不是某一端“坏了”而是两端没对上暗号。2.3 哪些设备能显示封面哪些根本不行这里给一个非常实际的判断方法。先看设备有没有屏幕没有屏幕的纯蓝牙耳机通常只支持基础AVRCP封面显示无从谈起。再看设备支持的AVRCP版本很多老式车载蓝牙模块还停在1.0/1.4时代指望它显示封面不太现实升级到1.6才有基础条件。还有个很容易被忽略的点手机系统蓝牙协议栈默认的AVRCP版本。Android不同版本默认值不一样有些定制ROM在开发者选项里能调有些没有这个选项就完全锁死。可以这样快速验证当前连接支持到哪个版本拿Android手机连上设备用adb抓取蓝牙日志搜索“avrcp”关键字会看到类似“avrcp_version 0x0106”的信息0x0106就代表AVRCP 1.6。如果看到0x0104那就是1.4。iOS这边开发者看不到这个值普通用户也没法主动调但iOS系统本身对AVRCP 1.6支持得比较彻底绝大多数时候不用操心。3. 手机端的实操配置确认、开启、调通一条龙设备端的事后面再说先把最简单的手机侧处理好。因为手机是所有内容的源头源头没有把metadata发出去后面的设备再强大也没用。3.1 确认当前连接的AVRCP版本Android用户特别是涉及方案调试的第一步建议先查当前手机蓝牙协议栈实际协商出来的AVRCP版本。能开开发者选项的进去翻“蓝牙”相关的配置。部分机型尤其高通平台会有“蓝牙AVRCP版本”这一项可以直接在1.4/1.5/1.6之间切换。没有这个选项的用adb连上手机打开蓝牙连接目标设备后执行logcat过滤关键词avrcp找版本协商记录。也可以用BluetoothAdapter的相关API写个几行的小工具去读协议栈支持的profile但不一定能直接拿到协商版本日志最可靠。如果是iOS生态没有手动配置空间但iOS 13之后对AVRCP 1.6支持很完善封面传输也有做私有增强。实际体验中iOS连支持封面显示的车机时基本能做到开箱即用。3.2 Android端如何通过MediaSession发送封面做音乐App的开发者注意Android端要支持AVRCP封面核心不是蓝牙层而是MediaSession这一层。系统蓝牙服务是“被动”从MediaSession取数据的。下面是一个最简可用的MediaSession配置示例mediaSession MediaSessionCompat(baseContext, MusicService) mediaSession.setCallback(object : MediaSessionCompat.Callback() { ... }) val metadata MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, 晴天) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, 周杰伦) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, 叶惠美) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 269000) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, coverBitmap) .build() mediaSession.setMetadata(metadata)关键点在第13行.putBitmap(METADATA_KEY_ALBUM_ART, coverBitmap)。很多开发者只在通知栏里设置了封面却忘了往MediaSession的metadata里塞封面图。通知栏有图不代表蓝牙能拿到图因为蓝牙协议栈只认MediaSession里的数据。有个细节要提醒coverBitmap最好不要直接塞一个几MB的原图建议先压缩到长边不超过1024像素、内存占用控制在几百KB以内的Bitmap。封面图过大会导致蓝牙传输耗时变长设备端解码也慢实际表现就是切歌后封面要等好几秒才出现。3.3 iOS端的注意事项和额外小技巧iOS这边如果你的App是系统自带音乐App或者Apple Music一切路径都是系统替你实现的不需要额外开发。但如果是第三方音乐App想做到“蓝牙能显示封面”其实做的事和Android类似核心也是把播放信息正确喂给系统。AVPlayer或MPNowPlayingInfoCenter的设置方式大家都熟这里只强调一点MPMediaItemPropertyArtwork这一项和Android的METADATA_KEY_ALBUM_ART一样是系统把图转给蓝牙协议栈的唯一入口漏掉它其他字段填得再完整封面也出不来。实际操作中我还发现一个有用的小技巧连不上封面的时候把音乐App切到后台再切回前台或者手动点一下下一曲封面往往能“补”出来。原因是网络播放器的metadata在连接建立时不完整系统蓝牙服务在某些情况下不会主动重新拉取一次播放状态改变会触发重新通知信息就补全了。4. 设备端实现耳机、音箱、车机怎么把封面接住如果说手机端是“发件人”设备端就是“收件人”。收件人要做的远比发件人复杂——因为要发回请求、解码图片、管理缓存还要处理各种奇奇怪怪的兼容问题。这一部分主要讲嵌入式设备端的落地思路。4.1 芯片平台和协议栈选型做蓝牙音频设备的工程师都知道AVRCP不闭环在某个芯片原厂SDK里不是简单“打开一个开关”就完事。目前主流几大平台对AVRCP 1.6的支持差异不小高通QCC系列如QCC3040、QCC5144等自带ADK对AVRCP 1.6支持比较完整教材示例也很多Browsing、Absolute Volume基本默认就有。CSR867x系列老牌经典方案AVRCP 1.6需要确认补丁版本部分SDK默认只开到1.4要手动切换配置宏。瑞昱RTL8763系列对AVRCP 1.6支持也不错但文档相对少调试时最好抓HCI log看实际协商结果。中低端国产蓝牙音频SoC杰理、中科蓝汛等有的标称“支持AVRCP 1.6”实际上只做了命令通道Browsing通道没实现完整封面显示可能受限。选型时务必看到实际验证结果不要只看datasheet上的字眼。在协议栈选型上开源方案常见两个蓝芽协议栈BlueZ和Android Bluedroid。BlueZ里AVRCP的实现集中在profiles/audio/avrcp.c这个文件里Bluedroid则在stack/avrc/avrc_int.h和btif_avrc相关文件中。升级AVRCP版本核心是确认协议栈默认版本宏比如AVRC_VERSION_1_6是否被正确定义以及Browsing相关通道是否被编译进固件。4.2 设备端实现封面的完整流程当设备端收到手机发来的metadata后里面应该包含一个Image Handle。处理流程大致是这样1. 检查metadata里是否存在ImageView属性 2. 若存在解析出Image Handle值 3. 返回AVRCP 1.6规定的GetImageProperties命令查询图片格式 4. 收到响应后发起GetImage请求 5. 接收图片数据解码JPEG/PNG 6. 缓存到本地关联到当前歌曲ID 7. 显示封面到UI注意第二步很多小白开发者会犯一个概念性错误以为手机直接通过AVRCP把封面图Binary发过来。实际上不是。AVRCP 1.6的metadata里只会带一个“图片引用”也就是Image Handle。设备端拿到引用后要主动发起图片获取流程才能拿到真正的图片数据。这个“拿到句柄再取图”的流程如果没写成异步UI线程会被阻塞表现出来就是切歌卡顿甚至ANR。4.3 Browsing通道的几个关键命令AVRCP 1.6中除了常规的控制命令通道还有一个Browsing通道专门处理“浏览媒体信息”和“获取图片”等重量级操作。设备端参与封面拉取时重点要处理这几个命令命令作用异常表现GetFolderItems获取当前媒体列表数据也可能包含当前播放的元素获取不到曲目信息GetItemAttributes获取某个媒体元素的详细属性可能包含ImageHandle拿到句柄但取不到图GetImageProperties查询图片的属性格式、尺寸等查询超时或返回不支持GetImage真正拉取图片二进制数据传输中断、只收到部分数据ChangePath切换媒体浏览目录浏览层级错乱这几个命令都是走AVRCP Browsing通道如果你在设备端工程里搜不到这些命令的对应处理函数那基本可以判断该平台的AVRCP版本支持还没做到1.6封面功能要补的工程量很大。想快速验证设备端到底支不支持这些命令比较直接的办法是在手机端抓HCI日志看设备连上后有没有发起过GetImageProperties或GetImage。如果设备从来没有发起过取图请求那问题大概率出在设备端而不是手机端。4.4 歌词显示在设备端怎么接歌词这块要说实话真正意义上的“逐行滚动歌词”通常不是通用标准能做到的更多是厂商私有方案。以我个人接触到的几个实际项目为例比较常见的做法有两种一是通过metadata里的扩展字段传输同步歌词。手机端把正在播放的歌词文本按特定格式如LRC格式塞进一个自定义metadata key里设备端解析这个key配合当前的播放时间戳逐行显示。这种做法的好处是不需要额外连接实现简单缺点是非标准不同品牌之间无法互通。二是单独开一个RFCOMM通道手机和耳机之间走私有协议同步歌词。实时性和同步精度更高很多中高端TWS耳机用的是这种方式。当然无论哪种方式都需要手机App一侧和耳机固件一侧共同配合包协议、加密方式、逐行时间戳的粒度都要提前约定清楚。5. 真实排查经历不显示封面时我一般按什么顺序查最后这部分用一次真实案例把上面讲的内容串起来。这个案例很有代表性现象是一台Android手机连一台支持蓝牙的车机歌名正常显示封面一直刷不出来用户反复断开重连也没用。当时排查链路是这样的第一步确认手机端有没有把封面发出去。具体做法是在开发者选项里打开“蓝牙HCI日志”然后用音乐App播放等日志生成后用hcidump或Wireshark打开日志包过滤AVRCP相关的packet检查metadata里有没有携带ImageHandle属性。这一步的结果是有。说明手机端工作正常问题更可能出在设备端。第二步抓取车机端的协议栈日志看车机有没有主动请求图片。这里就会看到车机在拿到metadata之后完全没有发起GetImage相关的Browsing命令。说明车机的AVRCP协议栈版本虽然名义上报了1.6但Browsing通道里的图片获取功能没做完整或者根本没有实现。第三步回到车机工程代码里查。果然SDK的配置宏里AVRCP版本定义是1.6但仔细一看BT模块的配置文件里AVRC_BROWSE_SUPPORTED这项被设置成了FALSE。也就是说整个Browsing通道被关掉了。自然也没有GetImage命令处理逻辑。把这项配置改过来重新编一版固件封面就出来了。这个案例里最恼人的点在于协议栈版本号明明是1.6Browsing通道却被单独关掉了单看版本号完全发现不了问题。这给我们的教训是不要只看版本号要抓实际命令流程。设备说“支持AVRCP 1.6”和真正能显示封面之间可能还差着一整套Browsing命令的实现。关于周边问题很多人也会遇到“删除电脑蓝牙设备怎么删除不了”或者“发送到蓝牙设备查找设备对话框里找不到设备”这类情况。前者一般是蓝牙服务进程残留驱动缓存重新启动蓝牙服务或者用官方驱动清理工具基本能解决后者通常和使用环境里蓝牙射频被干扰、设备进入不可发现模式有关。这些虽然不是AVRCP协议本身的问题但都属于蓝牙设备联调时常见的周边问题遇到时可以先查服务层和射频环境基本覆盖八九成。还有一点值得单独说部分手机在连接某些老设备时会自动协商到一个“保守”的AVRCP版本而不是设备最高支持的版本。这是为了兼容某些兼容性差的设备而做的兜底机制。如果你的测试机默认连出来是1.4但设备明明支持1.6可以试试在开发者选项里强制切换AVRCP版本。连版本都没有的只能借助第三方蓝牙HCI日志工具辅助分析。最后再分享一个我屡试不爽的小技巧如果封面显示偶尔失效但重新切歌又能恢复多半是metadata和ImageHandle的关联没有刷新。在音乐App的代码里每次setMetadata的时候确保METADATA_KEY_ALBUM_ART是新生成的Bitmap引用而不是复用上一个播放歌曲的缓存对象。很多播放器在切到下一首时因为没有新图顺手复用了上一首的封面导致蓝牙协议栈认为ImageHandle没有变化而不去拉新图。这本质上不是协议问题是应用层缓存设计问题但表现形态会让人误以为是蓝牙兼容性有问题。AVRCP 1.6这套东西说难不难说简单也不简单。把版本特性、角色模型、取图流程、Browsing通道这四块捋顺了封面显示只是水到渠成的事。至于歌词标准里没给路但有了1.6的底子私有扩展做起来也顺手很多。你的设备如果再遇到封面空白别急着怀疑硬件先按这篇文章的链路从手机端往设备端一步步查大概率问题没那么玄。
返回列表