深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

share-image
中
ESC

领克车机改造记:自己写个音乐 App 装进车里

我对领克这套车机的语音和播放器都不满意,最直接的原因是:听我自己的歌,还要我买它的会员。

所以我自己写了个车机版音乐 App,装进车里:曲库在我自己的服务器上,方向盘上的语音键说一句”播放稻香”,它就去我的曲库里找,然后放出来。

在车机上搜周杰伦

后来这事就不只是”放歌”了:地图换成了高德、导航和歌名都能上仪表盘、桌面上还多了一个音乐组件。这篇写的就是这几件事。

为什么不用原厂的

  • 原厂那套播放器(亿咖通做的「随心听」)只认它自己的在线曲库。我那 1449 首在自建存储上的歌,它根本看不见。
  • 语音助手是百度 DuerOS:说”播放稻香”,它会去云端搜,没登录先回你一句”请您先登录”;登录了,它放的还是它的曲库。
  • 车机是 Android 4.4.3,而且是 user 版本、没有 root。能做的事情天生受限。

结论就是:指望原厂改,不如自己写一个。

车机的底细

先摸硬件和系统。领克 CS11,飞思卡尔 i.MX6,Android 4.4.3(API 19)。我把 MusicFree 那套曲库搬到车上,用原生 Java 写了个车机版:一个列表 Activity + 一个播放 Service,曲库在构建时从我的 MusicFree 插件里抽成 JSON 打进 APK。

4.4 有三个坑,全是踩过才记住的:

  1. 没有 MediaSession(那是 5.0 才有的东西)。方向盘上的媒体键走 ACTION_MEDIA_BUTTON 广播 + RemoteControlClient,谁注册成”当前媒体客户端”,按键就发给谁。
  2. MediaPlayer.setDataSource(路径) 是让 mediaserver 进程去开这个文件(API 21 以后才改成 app 自己开好 fd 传过去)。app 私有目录默认 0700、文件 0600,mediaserver 连目录都进不去,直接报 setDataSourceFD failed: status=0x80000000。把缓存目录设成 0755、缓存文件 0644 就通了。
  3. 边下边播喂的是还没下完的 .part,4.4 的 AwesomePlayer 对太小的文件 sniff 不出格式,报的还是上面那个错。实测 300K 左右必挂、1.8M 正常,所以 4.4 上我把起播门槛抬到 1M(API 21+ 保持 512K 秒开)。

最难的一块:让语音听我的

我没打算重做语音识别——唤醒词、麦克风、降噪都在系统那套里,重做一遍不划算。我想要的只是”它听清之后,听我的”。

抓了一遍包,发现 DuerOS 把”播放稻香”变成了一条普通广播:

action = com.baidu.car.radio.QUERY
extras = query=稻香, audio_list={"type":"music","list":[],"error":6001}

原厂随心听收这条、要登录、播不出来;我也注册同一个 action,拿到 query 去搜我自己的曲库——歌就放上了。

代价是:这是无序广播(有序=false),abortBroadcast() 挡不掉原厂那一份。所以最后靠的是抢音频焦点(它一开播我就抢回来)和把我的播放页顶到前台,把原厂那页压下去。

顺手把车上 77 个原厂 APK 全拉下来归档了。语音服务的 AIDL、车况接口、埋点上报都翻了一遍,也发现它其实留了”媒体应用自报能力”的正规接口——那是下一步的事。

顺手做的几件事

  • 上电自动续播:车机启动会发 BOOT_COMPLETED / FIRST_BOOT_COMPLETED,我加了个 receiver 把播放服务拉起来,接着上次那首、上次的位置继续放(4G 没起来就先等网络,不然会被当成”放不了”直接跳歌)。
  • 点播一律从头放:之前”续播位置”会漏到新点的那首歌上,听着很像鬼故事。
  • 队列永远是整个曲库:搜索只是”找到这首歌”的入口,否则搜出来只有两三首,切歌只能在里面打转。
  • 网络电台:525 个台,国家台、省市广播、网络电台都能听。
  • 播放页:黑胶转起来、歌词跟上。
  • 键盘党的优化(今天刚加):拼音首字母搜歌,打 ghsy 出「光辉岁月」,回车直接放第一首,不用再去点列表。

曲库页:4138 首,顶部一排是电台 / 夸克 / 收藏 / U 盘

夸克网盘目录页:按目录翻,不用把几百 G 拖到本地

网络电台

播放页:黑胶在转,歌词在上面

换掉原厂地图,再把高德送上仪表盘

原车那套导航是百度车机版,能用的确能用,但我更想用高德。装是能装的:高德车机版 V9.1.0.600087(arm32)在这台 i.MX6 上跑得挺稳;V9.5 那个是 arm64,装上去根本起不来。

装完之后位置还得一个个换:首页三卡片里那张导航卡片、还有左边竖栏那一格,原来都认着原厂那套。

这里有个我自己挖的坑。为了试试”能不能把百度地图禁掉”,我跑了一次 pm block com.baidu.naviauto。应用本身后面 pm unblock 就回来了,但左侧栏那一格再也回不来了——因为左侧栏(Launcher3 的 hotseat)不是动态排的,是 launcher.db 里 favorites 表的固定记录;包一旦”不可用”,Launcher3 会顺手把那条记录删掉,unblock 也不会重灌默认布局。

想程序化加回去,要 ecarx.launcher3.permission.WRITE_SETTINGS(signature|privileged),第三方 App 拿不到。所以只能回到最原始的办法:长按图标拖进去。我把它写成了脚本——用 sendevent 合成”按下 → 停 600ms → 移动 → 抬起”(input swipe 不行,中间不停顿触发不了长按拖拽),一条命令把高德拖回左侧栏。

“默认导航应用”这个偏好也还挂在百度身上:语音说”导航去公司”,走的是 DuerOS 的默认导航,而它存在 /data/data/com.baidu.che.codriver/shared_prefs/prefs_amap.xml,没 root 改不了。好在 DuerOS 自己留了个开关:它的开放 SDK 里有一条 fun_navi_set_default_app 命令,值填 Amap 就是高德。我没去塞那个 3.3MB 的 SDK dex(4.4 是 Dalvik,没有 multi-dex),而是直接 bind 它的 ICoDriverService,自己发那条 Binder 事务。现在语音导航也归高德管了。

真正花功夫的是下面这段:让高德也能把导航送到仪表盘。

仪表盘那边原来只有原厂随心听和百度地图能显示。但接口本身没那么神秘:/system/framework/ecarx.adapterapi.jar 就在 boot classpath 里,亿咖通留了 com.ecarx.xui.adaptapi.diminteraction.DimInteraction 这个门面,原厂应用都是调它往仪表推内容。服务端 DiServer.onBind() 只看 Intent 里的一个字符串 extra(app_type=com.ecarx.navigation),不校验包名、不校验权限——所以第三方 App 也能用。

麻烦的是高德根本不知道这套 API。不过抓包发现它一直在发标准广播 AUTONAVI_STANDARD_BROADCAST_SEND,KEY_TYPE=10001 那一帧里转向图标、下一段路名、剩余距离、剩余时间全都有。于是我写了个”高德仪表桥”,夹在中间当翻译:

高德车机版 --广播(AUTONAVI_STANDARD_BROADCAST_SEND, KEY_TYPE=10001)--> 高德仪表桥
|
DimInteraction.getNaviInteraction().updateXxx()
v
com.ecarx.dim_interaction(DiServer → 仪表 MCU)

两个坑,都是踩了才知道:

  • 不要调 notifyTurnByTurnStarted()。SDK 会把它记成 turnState=0,而服务端 turnByTurn() 看到 0 会直接抛 RuntimeException: Navigation state is not correct.;仪表是靠 turnState=1 的更新帧进导航界面的。所以只发更新帧,退出导航时发 state=3。
  • updateDriveDirection(int) 这个 SDK 版本里是空实现,反编译出来就是 return-void,调了也没反应。

另外高德退出导航后 10001 会自己停发(实测几十秒内衰减到零),我就用”20 秒没收到 10001”当结束信号,自动把仪表清干净——不然仪表会一直挂在上一次导航上。

应用列表里那两个自己写的:高德地图、高德仪表桥

歌名也上仪表盘

导航能上,音乐当然也能。仪表那套门面里另一半是 getMediaInteraction(),原厂随心听就是这么把歌名、歌手、封面、进度推上去的。

车机版 MusicFree 从 1.0.39 起走了同一条路(CarCluster.java)。因为那些类在系统里、编译期拿不到 jar,就用反射取 DimInteraction.create(ctx).getMediaInteraction(),再用 Proxy 现造一个 IPlaybackInfo 实现,然后推 updatePlaybackInfo + updateCurrentProgress + updateMediaSourceTypeList。模拟器上没有这套 API,就静默跳过,只记一次日志。

两个约束值得记一笔:

  • 服务端把歌名 / 歌手 / 专辑按 70 字节(UTF-8) 截断,中文大概 23 个字就砍掉了。
  • 封面只有在 URI 里含 http 且正在播放时,才会走”换封面”那条分支——所以我在后台线程现签一个 URL 再补推一次。

还有个特别隐蔽的坑:服务是异步绑定的,第一次绑定没成功就被我当成”车机没有这套 API”直接跳过了,歌名自然一个都不出。改成 1 秒重试才对上。

桌面组件

车机桌面首页是三张卡片:高德地图、音乐、天气。中间那张音乐卡片,就是车机版 MusicFree 的桌面组件。

  • 第一版做的是 2x3 卡片 + 2x1 细条,尺寸是照着高德地图那个组件量的。
  • 后来改成 2x2:左边色块封面,右边「接下来」三行,点某一行直接跳那首歌,最后把高度改成填满格子。
  • 坑:进度条不动。 RemoteViews 在这套系统上最短半小时才给你刷一次,指望系统每秒推进度是做梦。解法很土——播放期间组件自己按秒走帧,把进度重画一遍。
  • 中间还试过一个更野的方案:用 TYPE_SYSTEM_ALERT 在所有界面最下面浮一条系统常驻播放条,这样无论停在哪个界面都能看到在放什么。实车调了两版底边高度,最后还是撤了——它要多一个悬浮窗权限,还跟原厂的分屏打架,收益不如”桌面上放个组件”来得干净。

车机首页三卡片:高德地图、MusicFree 音乐组件、天气

边界在哪

  • 车机没 root,pm disable 这类系统操作都做不了,所以对原厂应用我只能是”接管”,做不到”替换”。
  • pm block 虽然能用(而且能把应用彻底钉死),但它会留下后遗症——Launcher3 会把被屏蔽的应用从桌面上抹掉,unblock 也不会恢复。我拿左侧栏那一格换来了这个教训。
  • 远程开门、远程启动那条路我查到了,但没碰:这东西安全攸关,厂家也做了加密和签名,不值得也不该拿自己的车去试。
  • 现在稳定能用的:语音点歌、语音导航走高德、导航和歌名上仪表盘、桌面音乐组件、上车自动续播、中文/拼音搜歌、方向盘切歌、离线缓存、夸克网盘曲库。

最后

这件事让我改了个看法:车机里很多东西不是做不到,是没人愿意为”你那点具体需求”去做。而当你有 AI 帮手、又有自己的一套服务(曲库、签名服务都在自己手里),”我不满意”和”我自己写一个”之间的距离,可能只有一个周末。

文章作者:阿文
文章链接: https://www.awen.me/post/9dbc5603.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客!

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️