Yet another otaku

某ロリコン的自白


  • 首頁
  • 歸檔
  • 分類
  • 標籤
  • 連結
  • 關於
  •    

© 2026 SgDylan

Theme Typography by Makito

Proudly published with Hexo

为 Apple Music Windows 客户端适配 AMLL 协议

發佈於 2026-05-04 評論 笔记  AMLL Apple Music Windows 

一点小小的逆向尝试。


Apple Music 的 Windows 客户端基于 UWP/WinUI 构建,界面现代、体验流畅,但它有一个致命问题——没有任何公开的播放状态 API。
这意味着你无法直接获取「现在播放什么歌」、「播放到哪了」、「歌词是什么」、「专辑封面长啥样」这些信息。

市面上已有的歌词显示工具(如 AMLL, Apple Music Lyrics)在 macOS 上能良好工作,因为平台提供了媒体会话接口。
例如在 Windows 上通常是使用 SMTC 接口,但该接口并不提供歌词信息和精确的播放时间,因此体验并不够好。

可用的实现路径很有限:

  • UI 自动化:UI 自动化读取 UI 树上的视障辅助文本。性能差、需要打开歌词窗口、无法获取精确时间轴。
  • OCR:OCR 识别屏幕文字,准确性差、需要打开歌词窗口、歌词时间轴有延迟。
  • 网络抓包:Apple Music 的播放核心在本地,服务端只负责初始资源加载,抓包拿不到实时进度。
  • 进程注入:直接进入 Apple Music 进程内部,读取其私有属性、调用其内部方法。

从第一性原理出发,显然选择进程注入的方法来实现了。

整体架构

Apple Music.exe + AMHookDll (负责调用内部方法读取内部属性) -> Named Pipe (NDJSON) ->
AMHook.exe (转换适配 AMLL 协议) -> WebSocket (BinaryV1) -> AMLL Player (显示渲染)

整个项目分为两个部分:

  1. AMHookDll:被 AMHook.exe 注入到 AppleMusic.exe 进程中的 DLL。它的任务是从内部获取播放状态,通过命名管道以 NDJSON 格式向外输出。
  2. AMHook.exe:独立的 CLI 工具。负责接收管道消息,解析后通过 WebSocket BinaryV1 协议对接到 AMLL 服务端。

主要难点

激活私有 WinRT 组件

Apple Music 的播放核心是一组私有 WinRT 组件,命名空间为 AMP.Services.*。
这些组件并没有注册到系统的激活存储中,因此常规的 RoGetActivationFactory 调用会直接返回 REGDB_E_CLASSNOTREG (0x80040154)。

注入后可以直接调用 AMP.Services.dll 的导出函数 DllGetActivationFactory 获取到组件。
例如可以通过以下方法拿到 IPlayerStatics 工厂,调用 get_Current() 获取单例播放器实例。

HMODULE hMod = GetModuleHandleW(L"AMP.Services.dll");
auto pfn = (DllGetActivationFactory_t)GetProcAddress(hMod, "DllGetActivationFactory");
HStringPtr hClassName;
hClassName.Attach(L"AMP.Services.Player");
hr = pfn(hClassName.Get(), &factoryRaw);

VTable 逆向工程

私有组件没有公开的头文件,所有接口方法都需要通过反编译 + 动态探测来确定 vtable 索引。

实际交叉使用了三种手段:

  1. dnSpy:反编译 AMP.*.winmd 文件,查看接口定义和方法顺序。
  2. IDA Pro:分析 AMP.Services.dll 的虚表布局,交叉验证索引。
  3. 全表扫描:在隔离环境中对每个索引调用,观察返回值和崩溃行为。

最终确定的几个关键索引如下:

接口 方法 VTable 索引 说明
IPlayer get_NowPlayingItem 6 获取当前播放项
INowPlayingItem get_Name 19 歌曲名称
INowPlayingItem get_Subtitle 6 "Artist — Album"
INowPlayingItem get_Duration 31 歌曲时长
IPlayerState get_IsPlaying 25 播放状态
IPlayerState get_Volume 21 音量
ILyricsInfo GetTTML 14 TTML XML 歌词

取得歌词与封面图片

歌词:TTML 时间轴解析

Apple Music 内部的歌词不是简单的 LRC 格式,而是 **TTML (Timed Text Markup Language)**。
调用内部方法 LyricsInfo::GetTTML 返回完整的 TTML XML,包含每行、甚至每个单词的时间轴。

为此简单实现了一个 TTML XML 解析器,按当前进度 CurrentPosition(索引 30)匹配到当前行,并更新 AMLL 侧。

封面图片:从私有 URI 到 HTTP URL

INowPlayingItem::get_Artwork 返回的并不是直接可用的 URL,而是一个形如 amp-artwork-id://1.dbid/{GUID}_1.1 的私有协议 URI。

Apple Music 在本地维护了一个 SQLite 数据库(artwork.sqlite),存储了 artwork_id 到公开 HTTP URL 的映射。
用正则提取 GUID 查询数据库,最终拿到可用的封面链接。
需要注意的是数据库里有时候存储的只有 URL 的后半段,可以根据自己需要拼上前半段来使用。

// 示例
amp-artwork-id://1.dbid/E7CEFC4E-..._1.1
// → SQL: SELECT location FROM artwork_source WHERE artwork_id = 'E7CEFC4E-...'
// → https://is1-ssl.mzstatic.com/image/thumb/Music221/.../800x800bb.jpg
// -> Music221/.../800x800bb.jpg

由于这个 API 实际上是免鉴权使用的,照着网页版 Apple Music 逆向工程实现抄即可。
拼接 URL 的方法大致如下,URL 中间一部分不变,改变前缀后缀取得原图 / 压缩图。

# 原图
https://a5.mzstatic.com/us/r1000/0/Music221/v4/b3/1b/50/{GUID}/{AdamID}_cover.png
# 压缩版,可以自己随便改分辨率
https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/b3/1b/50/{GUID}/{AdamID}_cover.png/800x800bb.jpg

AMLL WebSocket 协议桥接

这部分照着文档和 AMLL 自己的源码抄就行了。
唯一需要注意的地方是 AMLL 的播放进度完全依赖 WS 汇报,歌词时间戳精度依赖汇报的播放进度精度。
同时使用定时汇报进度 + 歌词事件触发汇报进度就可以高精度了。

AMLL 反向操作 Apple Music 客户端的部分也顺手实现了,步骤和取得歌词等信息别无二致,此处不再赘述。

使用效果

tayori

终端里是 AMHook.exe,被终端挡住的窗口是 Apple Music for Windows,右侧为 AMLL Player。

后记

目前 AMHook 已能稳定输出歌曲名称、歌手/专辑、播放进度、音量、封面 URL 和歌词数据,并完整对接 AMLL 协议。
music_id / album_id / artist_id 等字段因 Apple Music 内部没有稳定来源,暂留空处理。

大概还可以继续实现一下:

  • 集成 AMLL 歌词数据库,回避 Apple Music 缺少歌词的情况
  • 适配 TTML 逐字歌词(主要是没找到带逐字歌词的歌曲)
  • 加入 LLM 翻译 / 罗马音 Ruby 支持

项目代码不会开源,二进制仅供学习使用亦不会公开。

以上。

分享到 

 下一篇: 2025 稍作休息 

© 2026 SgDylan

Theme Typography by Makito

Proudly published with Hexo