<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>开发随谈 on AntiOblivionis</title><link>https://tgwgroup.ltd/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E8%B0%88/</link><description>Recent content in 开发随谈 on AntiOblivionis</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><copyright>AntiOblivionis</copyright><lastBuildDate>Wed, 11 Nov 2026 15:00:00 +0800</lastBuildDate><atom:link href="https://tgwgroup.ltd/categories/%E5%BC%80%E5%8F%91%E9%9A%8F%E8%B0%88/index.xml" rel="self" type="application/rss+xml"/><item><title>AMExclusive，如何给予 Windows 端 Apple Music 独占输出的能力</title><link>https://tgwgroup.ltd/2026/11/11/about-amexclusive/</link><pubDate>Wed, 11 Nov 2026 15:00:00 +0800</pubDate><guid>https://tgwgroup.ltd/2026/11/11/about-amexclusive/</guid><description>&lt;img src="https://tgwgroup.ltd/uploads/cms/ame_logo_76fa6bd654.png" alt="Featured image of post AMExclusive，如何给予 Windows 端 Apple Music 独占输出的能力" /&gt;&lt;h2 id="背景现状"&gt;&lt;a href="#%e8%83%8c%e6%99%af%e7%8e%b0%e7%8a%b6" class="header-anchor"&gt;&lt;/a&gt;背景，现状&#13;&#10;&lt;/h2&gt;&lt;p&gt;Apple Music，苹果公司互联网业务的一颗明珠。其成功固然离不开与健全的苹果硬件生态的融合，但同时，苹果直接直接与唱片公司对接拿到母带，并以此优势达到流媒体音频质量第一梯队，因此在音乐发烧友群体，Apple Music 的声誉也非常高。&lt;/p&gt;&#10;&lt;p&gt;然而，Apple Music 的音频质量优势在音源阶段后戛然而止。在&lt;a class="link" href="https://audiophilestyle.com/ca/bits-and-bytes/apple-musics-lossless-and-hi-res-mess-r1022/page/9/" target="_blank" rel="noopener"&#13;&#10; &gt;The Computer Audiophile — Apple Music&amp;rsquo;s Lossless and Hi-Res Mess&lt;/a&gt;，&lt;a class="link" href="https://audiophilestyle.com/ca/bits-and-bytes/apple-musics-lossless-and-hi-res-mess-r1022/page/7/" target="_blank" rel="noopener"&#13;&#10; &gt;The Computer Audiophile — Apple Music&amp;rsquo;s Lossless and Hi-Res Mess Page 7&lt;/a&gt;，&lt;a class="link" href="https://www.whathifi.com/streaming-entertainment/music-streaming/bit-perfect-i-tested-airplay-google-cast-and-dlna-to-see-which-streaming-protocol-preserves-audio-quality-best" target="_blank" rel="noopener"&#13;&#10; &gt;Stephen Dawson — Bit-perfect? I tested AirPlay, Google Cast and DLNA&lt;/a&gt;的研究中，即使是被大多数奉为圭臬的 iPhone，iPad，Mac，也无法做到全时 bit-perfect 输出。在 Windows 端，这一情况则更加糟糕。接下来，我们就要尝试改善这一情况。&lt;/p&gt;&#10;&lt;h2 id="拆解开盒"&gt;&lt;a href="#%e6%8b%86%e8%a7%a3%e5%bc%80%e7%9b%92" class="header-anchor"&gt;&lt;/a&gt;拆解，开盒&#13;&#10;&lt;/h2&gt;&lt;p&gt;第一步：在不改变播放结果的前提下，观察不同位置的格式和数据。&lt;/p&gt;&#10;&lt;p&gt;（本轮分析主要基于 Windows 版 Apple Music 文件版本 1.6.4.90，包及框架版本 1.1540.23042.0， x86-64）&lt;/p&gt;&#10;&lt;p&gt;先确定进程边界。Apple Music 的界面进程与承担媒体工作的&lt;code&gt;AMPLibraryAgent.exe&lt;/code&gt;是分开的，因此我们需要观察 AMP 中与音频有关的模块、导出函数、对象创建和生命周期。以下是重点记录信息：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AudioConverter&lt;/code&gt;的输入和输出格式：编码类型、采样率、声道数、每帧字节数、整数或浮点。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;AudioUnit&lt;/code&gt;的输入输出格式，初始化、启动、渲染调用。&lt;/li&gt;&#10;&lt;li&gt;WASAPI 的&lt;code&gt;IAudioClient::Initialize&lt;/code&gt;参数、共享或独占，以及&lt;code&gt;IAudioRenderClient&lt;/code&gt;提交的缓冲。&lt;/li&gt;&#10;&lt;li&gt;对象标识、调用顺序、线程、时间戳、实际产生的帧数和曲目切换之生命周期。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;格式信息通常由&lt;code&gt;AudioStreamBasicDescription&lt;/code&gt;描述，以下简称ASBD。它可以告诉我们一个对象输入、输出的数据是什么样子，但不会自动告诉我们这是不是当前正在听的歌曲。播放器可能同时预取出下一首、保留旧对象、创建多个转换器，因此还要把转换器和正在工作的音频图建立关联。&lt;/p&gt;&#10;&lt;p&gt;实际观察可以从进程查看器和模块导出表开始：先确认哪条进程加载了&lt;code&gt;CoreAudioToolbox.dll&lt;/code&gt;等音频相关模块，再看可观察的转换器和 AudioUnit 接口。接着我们在&lt;code&gt;AudioConverterNew&lt;/code&gt; 、&lt;code&gt;AudioConverterNewSpecific&lt;/code&gt;记录每个转换器的 I/O ASBD，在&lt;code&gt;AudioUnitSetProperty&lt;/code&gt;、&lt;code&gt;AudioOutputUnitStart&lt;/code&gt;和&lt;code&gt;AudioUnitRender&lt;/code&gt;记录图的格式、启动渲染时序。WASAPI 的&lt;code&gt;IAudioClient&lt;/code&gt;和&lt;code&gt;IAudioRenderClient&lt;/code&gt;是 COM 接口，应结合设备激活、&lt;code&gt;GetService&lt;/code&gt;返回的接口对象观察方法调用。&lt;/p&gt;&#10;&lt;p&gt;找到候选之后，再通过播放、暂停、切歌和进度跳转观察对象如何变化，记录转换器地址及代次、所属线程、时间戳、输入输出格式、每次请求和实际产生的帧数，并与当前正在启动或渲染的 AudioUnit 对照。&lt;/p&gt;&#10;&lt;p&gt;初步观测后我们可以给 Apple Music 的音频链画一个简单的示意图：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;受 Apple DRM 保护的 Compressed Audio Stream&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Apple AudioConverter 解码&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P0：解码器返回 PCM&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Apple AudioUnit 及相邻处理&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P1：AudioUnit渲染输出&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WASAPI 客户端缓冲&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P2：提交给Windows&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;P0、P1、P2 是这次研究中自定义的观测点，并不是 Apple 官方对内部结构的命名。它们的意义是把「我认为声音经过了这里」变成可以分别记录、比较和证伪的假设。&lt;/p&gt;&#10;&lt;p&gt;在流媒体 ALAC 路径里，P0 候选点是&lt;code&gt;AudioConverterFillComplexBuffer&lt;/code&gt;调用原函数之后得到的解码输出。P1 观察&lt;code&gt;AudioUnitRender&lt;/code&gt;的返回数据，P2 则观察 WASAPI 的&lt;code&gt;GetBuffer&lt;/code&gt;/&lt;code&gt;ReleaseBuffer&lt;/code&gt;提交边界。&lt;/p&gt;&#10;&lt;p&gt;为什么选择这个观察点？Apple 对 &lt;code&gt;AudioConverterComplexInputDataProc&lt;/code&gt;的说明指出，转换器通过回调获得输入数据，并在&lt;code&gt;AudioConverterFillComplexBuffer&lt;/code&gt;中输出转换结果。&lt;a class="link" href="https://developer.apple.com/documentation/audiotoolbox/audioconvertercomplexinputdataproc" target="_blank" rel="noopener"&#13;&#10; &gt;Apple：AudioConverterComplexInputDataProc&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="为什么-p0-是关键"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-p0-%e6%98%af%e5%85%b3%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;为什么 P0 是关键？&#13;&#10;&lt;/h2&gt;&lt;p&gt;先看一次实际观察。在一首 96 kHz / 24-bit 立体声 ALAC 曲目的测试中，记录到的原生链路是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P0：96 kHz ALAC/QLAC → 96 kHz LPCM，float32 表示&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;活动 AudioUnit 输入：96 kHz LPCM&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;活动 AudioUnit 输出：384 kHz LPCM&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;P2：384 kHz、立体声、IEEE float、WASAPI 共享模式&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;换句话说，这次测试里，96 kHz 到 384 kHz 的变化发生在 AudioUnit 输入与输出之间，或紧邻它的 Apple 内部处理阶段。P2 当时已经是 384 kHz 流，不能再把它当成原始 96 kHz 样本序列的逐帧副本。&lt;/p&gt;&#10;&lt;p&gt;更重要的问题是：P0 虽然看起来更靠前，它真的对应原始音乐样本吗？我用了三层证据。&lt;/p&gt;&#10;&lt;h3 id="第一层确认它是解码输出而不是另一个处理器"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e5%b1%82%e7%a1%ae%e8%ae%a4%e5%ae%83%e6%98%af%e8%a7%a3%e7%a0%81%e8%be%93%e5%87%ba%e8%80%8c%e4%b8%8d%e6%98%af%e5%8f%a6%e4%b8%80%e4%b8%aa%e5%a4%84%e7%90%86%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;第一层：确认它是解码输出，而不是另一个处理器&lt;/strong&gt;&#13;&#10;&lt;/h3&gt;&lt;p&gt;对于这条流媒体路径，观察到的转换器输入是 ALAC 或受保护的 QLAC，输出是同采样率的 LPCM。相对地，一个 LPCM → LPCM 转换器更可能是在做后续格式整理或采样率转换，不能仅凭它也输出 PCM 就称为解码边界。&lt;/p&gt;&#10;&lt;p&gt;每次捕获都需要结合转换器身份、输入输出 ASBD、实际产出帧数和活动音频图判断。这里的关键不是函数名看起来合适，而是对象的输入、输出和生命周期能够与当前播放关联。&lt;/p&gt;&#10;&lt;h3 id="第二层检查样本是否仍落在-24-bit-整数格点上"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e5%b1%82%e6%a3%80%e6%9f%a5%e6%a0%b7%e6%9c%ac%e6%98%af%e5%90%a6%e4%bb%8d%e8%90%bd%e5%9c%a8-24-bit-%e6%95%b4%e6%95%b0%e6%a0%bc%e7%82%b9%e4%b8%8a" class="header-anchor"&gt;&lt;/a&gt;第二层：检查样本是否仍落在 24-bit 整数格点上&#13;&#10;&lt;/h3&gt;&lt;p&gt;这次 P0 使用 float32 表示，但音源有效精度为 24-bit。对于没有经过增益或其他处理的 24-bit 整数 PCM，归一化后的浮点值应当落在相应的 24-bit 整数格点上，并且可以准确映射回原来的整数值。&lt;/p&gt;&#10;&lt;p&gt;在一次 P0 聚合采样中，1,152,000 个样本里没有发现偏离该 24-bit 格点的值。这说明该窗口里的数据具备精确映射回 24-bit 整数的条件。&lt;/p&gt;&#10;&lt;p&gt;而在另一次冷启动观察中，按 AudioUnit &lt;strong&gt;输出格式&lt;/strong&gt;解释的 P1 长窗口有 4,608,000 个浮点样本，其中 3,662,971 个不在原来的 24-bit 格点上。结合该次观察中的 96 kHz 输入与 384 kHz 输出，这支持「P1 已经过后续处理」的判断。&lt;/p&gt;&#10;&lt;h3 id="第三层与独立解码的同曲目参考逐样本比较"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e5%b1%82%e4%b8%8e%e7%8b%ac%e7%ab%8b%e8%a7%a3%e7%a0%81%e7%9a%84%e5%90%8c%e6%9b%b2%e7%9b%ae%e5%8f%82%e8%80%83%e9%80%90%e6%a0%b7%e6%9c%ac%e6%af%94%e8%be%83" class="header-anchor"&gt;&lt;/a&gt;第三层：与独立解码的同曲目参考逐样本比较&#13;&#10;&lt;/h3&gt;&lt;p&gt;为了回答「是否真的相同」，我把 P0 与同一曲目、同一规格的独立 ALAC 参考解码结果对齐。&lt;/p&gt;&#10;&lt;p&gt;在一次新的采集中，连续 38,400 个立体声 P0 帧与参考 PCM 在确定的帧偏移处逐样本相等；另外 17 个各 19,200 帧的完整窗口也与参考匹配，比较结果没有样本误差。&lt;/p&gt;&#10;&lt;p&gt;这个结果比「有声音」或「格式一致」更强：对于这些已经捕获和对齐的窗口，P0 与独立参考解码得到了相同的样本值。&lt;/p&gt;&#10;&lt;p&gt;准确的说法是：P0 是本次探针能够稳定观察到、并通过参考样本验证的解码后 PCM 边界；对从这里接管输出这个目标而言，它是目前最有价值的入口。&lt;/p&gt;&#10;&lt;h2 id="p0-的-float32-是否影响精度"&gt;&lt;a href="#p0-%e7%9a%84-float32-%e6%98%af%e5%90%a6%e5%bd%b1%e5%93%8d%e7%b2%be%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;P0 的 float32 是否影响精度？&#13;&#10;&lt;/h2&gt;&lt;p&gt;第一次看到 P0 是 float32 时，很容易认为「浮点转整数一定会损失位深」。问题的关键是浮点数据里的值是什么，以及它从什么精度的音源而来。&lt;/p&gt;&#10;&lt;p&gt;以测试过的 24-bit 整数音源为例，把有符号整数样本记为 s，若 Apple 输出的浮点值正好是 s / 2²³，float32 可以精确表示这些归一化的 24-bit 样本。映射回使用 32-bit 容器的整数 PCM 时，可以把 24 位有效数据放在高位，低 8 位补零，而不改变音乐样本值。&lt;/p&gt;&#10;&lt;p&gt;这里改变的是表示方式：P0 的 float32 和输出端的 24-valid-in-32 整数，原始字节布局不同，但可以代表同一个 24-bit 样本。实现上必须检查有限值、数值范围、有效位对齐和往返映射；不能对任意浮点数据随便四舍五入后就宣布 bit-perfect。项目中的&lt;code&gt;ExactSampleMapper.h&lt;/code&gt;就是在这个边界做严格映射。&lt;/p&gt;&#10;&lt;h2 id="拥有-p0-后如何接管并实现独占输出"&gt;&lt;a href="#%e6%8b%a5%e6%9c%89-p0-%e5%90%8e%e5%a6%82%e4%bd%95%e6%8e%a5%e7%ae%a1%e5%b9%b6%e5%ae%9e%e7%8e%b0%e7%8b%ac%e5%8d%a0%e8%be%93%e5%87%ba" class="header-anchor"&gt;&lt;/a&gt;拥有 P0 后如何接管并实现独占输出？&#13;&#10;&lt;/h2&gt;&lt;p&gt;找到合适的取样点，只完成了一半。真正的播放器还必须处理线程、缓冲、播放状态以及设备所有权。&lt;/p&gt;&#10;&lt;p&gt;AMExclusive 的架构把界面控制和音频工作拆开：界面侧负责设置与状态；Broker 负责进程协调和通信；音频 Hook DLL 进入&lt;code&gt;AMPLibraryAgent.exe&lt;/code&gt;，在目标进程内观察和处理音频。当前实现由 Broker 将模块加载到目标进程，再通过 &lt;a class="link" href="https://github.com/TsudaKageyu/minhook" target="_blank" rel="noopener"&#13;&#10; &gt;MinHook&lt;/a&gt; 安装函数拦截。具体实现可以查看&lt;code&gt;Broker.cpp&lt;/code&gt;和&lt;code&gt;HookDll.cpp&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;对于前面提到的流媒体 ALAC 路径，核心步骤可以概括为：&lt;/p&gt;&#10;&lt;p&gt;观察转换器的创建与格式，识别压缩音频到 LPCM 的候选对象。&lt;/p&gt;&#10;&lt;p&gt;结合活动 AudioUnit、播放生命周期和当前代次，确认候选对象属于正在播放的音频图。&lt;/p&gt;&#10;&lt;p&gt;调用 Apple 原来的&lt;code&gt;AudioConverterFillComplexBuffer&lt;/code&gt;，在它返回后检查实际产生的 PCM，并复制进入预分配的有界队列。&lt;/p&gt;&#10;&lt;p&gt;在自己的音频线程里，从队列取出 PCM，做所需的精确样本表示映射，再交给 WASAPI 独占输出。&lt;/p&gt;&#10;&lt;p&gt;协调 Apple 原生输出端和新输出端的所有权，保证 Apple 的解码与播放调度能继续运行，同时避免两条路径同时向真实设备发声。&lt;/p&gt;&#10;&lt;p&gt;有一个容易忽略的细节：不能只看函数返回状态来判断是否有数据。Apple 的输入回调暂时没有新数据时，转换器可能一边返回非零状态，一边吐出内部已经解码的 PCM。项目遇到的轨迹里，385 次返回状态 3 的 P0 Fill 调用中，有 373 次仍带有非零输出。正确做法是保留 Apple 的原始返回状态，同时根据实际产生的帧数和缓冲合法性判断是否接收 PCM。&lt;a class="link" href="https://developer.apple.com/documentation/audiotoolbox/audioconvertercomplexinputdataproc" target="_blank" rel="noopener"&#13;&#10; &gt;Apple 的回调说明&lt;/a&gt;也描述了输入暂时耗尽时仍可能交付待输出数据的行为。&lt;/p&gt;&#10;&lt;p&gt;实时回调还不应该为了等待设备而长时间阻塞。项目使用有界队列将 Apple 的 PCM 生产和 WASAPI 消费分开；队列不足、溢出、对象失效以及旧曲目数据，都需要明确处理和记录。&lt;/p&gt;&#10;&lt;h3 id="为什么不直接搬走-apple-的整个解码器"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e7%9b%b4%e6%8e%a5%e6%90%ac%e8%b5%b0-apple-%e7%9a%84%e6%95%b4%e4%b8%aa%e8%a7%a3%e7%a0%81%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;为什么不直接搬走 Apple 的整个解码器？&#13;&#10;&lt;/h3&gt;&lt;p&gt;早期尝试过让项目自己的工作线程复用 Apple 已经暴露的输入回调，再由另一个转换器拉取数据。它一开始可以得到 PCM，却在持续播放时出现供数停滞。&lt;/p&gt;&#10;&lt;p&gt;后来沿着调用链继续追查，发现输入回调只负责暴露当前队列记录并将其标记为已消费；真正推进队列头、更新状态和释放旧记录的动作，由外层 Apple 调度器完成。只拿回调和用户数据来用，却绕开外层调度事务，就可能反复碰到已经消费过的记录。&lt;/p&gt;&#10;&lt;p&gt;这个教训让最终方案转向更明确的分工：Apple 继续负责自己的网络、受保护媒体处理、队列、解码和调度；AMExclusive 在已验证的 PCM 边界复制数据，并接管物理输出。&lt;/p&gt;&#10;&lt;p&gt;为了让 Apple 的图继续按预期运行，项目为其下游提供软件侧的音频客户端、缓冲与时钟等调度接口；真正连接设备的是 AMExclusive 自己的独占 WASAPI 客户端。这样既不需要自行重写 Apple 的受保护媒体处理，也能避免原生共享输出和独占输出同时占用设备。（实现详见&lt;code&gt;NativeRenderGateProxy.cpp&lt;/code&gt;）这就有点类似于所谓的“蓝色药丸”，Apple 还以为自己真的还在进行正常的共享输出，实际上它供给的只是一个我们构建的、虚拟的假端点，而真实情况是我们已经把解码后 PCM 复制走，进行独占输出了。&lt;/p&gt;&#10;&lt;h3 id="本地文件的-p0-不一定是同一个函数出口"&gt;&lt;a href="#%e6%9c%ac%e5%9c%b0%e6%96%87%e4%bb%b6%e7%9a%84-p0-%e4%b8%8d%e4%b8%80%e5%ae%9a%e6%98%af%e5%90%8c%e4%b8%80%e4%b8%aa%e5%87%bd%e6%95%b0%e5%87%ba%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;本地文件的 P0 不一定是同一个函数出口&#13;&#10;&lt;/h3&gt;&lt;p&gt;上面的 ALAC/QLAC → LPCM 例子主要对应测试过的流媒体路径。普通导入的本地音频还可能先暴露一个 LPCM → LPCM 转换器，这时项目需要观察它输入侧、Apple 后续工作格式转换之前的 PCM；对于部分 AAC 路径，也存在通过类似桥接边界取数据的情况。&lt;/p&gt;&#10;&lt;p&gt;所以，P0 更适合被理解成一个逻辑边界：目前能够确认、并适合接管的解码后或后续转换前 PCM。&lt;/p&gt;&#10;&lt;h2 id="如何验证接管后的输出没有改变音乐"&gt;&lt;a href="#%e5%a6%82%e4%bd%95%e9%aa%8c%e8%af%81%e6%8e%a5%e7%ae%a1%e5%90%8e%e7%9a%84%e8%be%93%e5%87%ba%e6%b2%a1%e6%9c%89%e6%94%b9%e5%8f%98%e9%9f%b3%e4%b9%90" class="header-anchor"&gt;&lt;/a&gt;如何验证接管后的输出没有改变音乐？&#13;&#10;&lt;/h2&gt;&lt;p&gt;验证需要回答两个问题：P0 是否等于参考音源，以及 AMExclusive 最终提交给 WASAPI 的音乐样本是否仍等于参考。&lt;/p&gt;&#10;&lt;p&gt;后者不能只看&lt;code&gt;IsFormatSupported&lt;/code&gt;返回成功、播放器显示了「独占」、DAC 亮起了 96 kHz，或者程序自己的校验计数器为零。这些信息有用，但不足以单独证明整条样本序列相同。&lt;/p&gt;&#10;&lt;p&gt;在一次较早的 96 kHz / 24-bit 实验构建中，我把最终提交给 WASAPI&lt;code&gt;IAudioRenderClient::ReleaseBuffer&lt;/code&gt;之前的整数 PCM 记录下来，与独立解码的同曲目 ALAC 参考比较：&lt;/p&gt;&#10;&lt;p&gt;整首歌曲的 20,107,520 个立体声帧逐字节相等，没有发现第一个不匹配位置。&lt;/p&gt;&#10;&lt;p&gt;按约定的 PCM32 容器表示，两边音乐部分的 SHA-256 都是 e5a0e305046b2df8623e04ada7e74a85cf5818186e18cf7129917877235c6a5b。&lt;/p&gt;&#10;&lt;p&gt;该次流媒体测试还跨过了自然单曲循环边界：后续 3,554,560 帧与参考开头逐字节相等。&lt;/p&gt;&#10;&lt;p&gt;在这个验收区间内，记录到的丢帧、缓冲不足和 WASAPI 缓冲提交失败计数均为零。&lt;/p&gt;&#10;&lt;p&gt;普通导入的本地 96 kHz / 24-bit ALAC 路径也做过单独的整曲比较。&lt;/p&gt;&#10;&lt;p&gt;当前项目的目标格式协商会调用&lt;code&gt;IAudioClient::IsFormatSupported&lt;/code&gt;，并以&lt;code&gt;AUDCLNT_SHAREMODE_EXCLUSIVE&lt;/code&gt;初始化设备；请求的格式必须由设备支持，初始化仍可能失败。&lt;a class="link" href="https://learn.microsoft.com/en-us/windows/win32/coreaudio/device-formats" target="_blank" rel="noopener"&#13;&#10; &gt;Microsoft：设备格式&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="独占之外音质锁"&gt;&lt;a href="#%e7%8b%ac%e5%8d%a0%e4%b9%8b%e5%a4%96%e9%9f%b3%e8%b4%a8%e9%94%81" class="header-anchor"&gt;&lt;/a&gt;独占之外，音质锁&#13;&#10;&lt;/h2&gt;&lt;p&gt;P0 接管能决定已经选中的音频如何输出，却不能决定 Apple Music 一开始下载的是哪一种音源。&lt;/p&gt;&#10;&lt;p&gt;假设同一首歌有 48 kHz 和 96 kHz 的 ALAC 候选版本。用户打开高解析度无损，并不意味着仅仅设置「最高允许 192 kHz」就一定会选中 96 kHz：48 kHz 同样低于这个上限。如果播放器的候选选择逻辑综合了带宽等条件，低规格版本仍可能被留下。&lt;/p&gt;&#10;&lt;p&gt;这就是 AMExclusive 音质锁处理的问题：在符合用户无损设置的前提下，让高解析度无损的候选选择不要因为带宽等回退路径，悄悄降到已知的较低无损规格或有损版本。&lt;/p&gt;&#10;&lt;p&gt;首先，它读取 Apple Music 自己的音质偏好，并观察播放器设置采样率上限、音源偏好和创建无损候选过滤器的过程。对已知的高解析度无损设置，项目会恢复 192 kHz 的候选上限，并调整与无损立体声有关的偏好。&lt;/p&gt;&#10;&lt;p&gt;然后，在高解析度无损策略下，它从当前过滤评估拿到的原始候选列表中，查找 Apple 原有无损类型判断所允许、且声明采样率不超过 192 kHz 的最高采样率。例如：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;原始候选：48 kHz ALAC、96 kHz ALAC、256 kbps AAC&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;高解析度策略观察到的最高无损采样率：96 kHz&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;如果原生过滤结果只剩 48 kHz ALAC 或 AAC：&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 严格过滤后的结果可以为空，而不是把它们当成 96 kHz 继续播放&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;实现上，项目包装 Apple 的过滤器，并在原生过滤树处理后，对返回结果再做一次严格条件的交集。这个过程只能从 Apple 返回的结果中移除不符合策略的候选，不能凭空添加一个 Apple 没有返回的音源。如果严格结果为空，播放器可能继续等待，也可能出现加载失败或其他错误。&lt;/p&gt;&#10;&lt;p&gt;对于普通无损档位，当前严格过滤主要约束无损编码类型，并不像高解析度档位那样执行上述最高声明采样率选择。无损开关关闭、用户选择有损档位或设置无法确认时，相关逻辑交还给 Apple 原有行为。&lt;/p&gt;&#10;&lt;p&gt;如果某首曲目的原始候选清单根本没有 ALAC/QLAC，音质锁会保留 Apple 原来的 AAC-only 选择，而不是让这首歌永久卡在空候选集合里。相反，如果清单里存在无损候选，只是它因为网络或其他过滤条件暂时没有通过，就不能把这种情况简单当成「这首歌只有 AAC」。&lt;/p&gt;&#10;&lt;p&gt;这些处理针对的是选源，不会把 48 kHz 歌曲升采样到 96 或 192 kHz，也不能创造目录中不存在的高解析度版本。192 kHz 是高解析度候选的上限，不是每首歌的目标输出采样率；当前最高采样率选择也没有对同采样率的 16-bit 和 24-bit 候选再做位深优先级比较。（实现详见&lt;code&gt;LosslessQualityLock.cpp&lt;/code&gt;）&lt;/p&gt;&#10;&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后&#13;&#10;&lt;/h2&gt;&lt;p&gt;没有最后。AMExclusive 仍是一个非常早期且正在不断进化的项目，相信未来会有更多人享受到更高质量的流媒体音乐体验。&lt;/p&gt;&#10;</description></item></channel></rss>