为 Apple Music Windows 客户端适配 AMLL 协议
一点小小的逆向尝试。
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 (显示渲染)
整个项目分为两个部分:
- AMHookDll:被
AMHook.exe注入到AppleMusic.exe进程中的 DLL。它的任务是从内部获取播放状态,通过命名管道以 NDJSON 格式向外输出。 - 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 索引。
实际交叉使用了三种手段:
- dnSpy:反编译
AMP.*.winmd文件,查看接口定义和方法顺序。 - IDA Pro:分析
AMP.Services.dll的虚表布局,交叉验证索引。 - 全表扫描:在隔离环境中对每个索引调用,观察返回值和崩溃行为。
最终确定的几个关键索引如下:
| 接口 | 方法 | 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 客户端的部分也顺手实现了,步骤和取得歌词等信息别无二致,此处不再赘述。
使用效果

终端里是 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 支持
项目代码不会开源,二进制仅供学习使用亦不会公开。
以上。