Android 音频栈:为什么你的音乐被重采样了
深入了解 Android 如何处理音频——从应用到扬声器。了解为什么 AudioFlinger 会重采样你的音乐以及你能做什么。
Android 如何播放音频
你的 Android 手机发出的每一个声音——通知、电话、游戏效果、音乐——在到达你的耳朵之前都要经过同一个音频管道。理解这个管道是理解为什么你精心策划的无损音乐收藏可能听起来没有预期那么纯净的关键。
标准的 Android 音频路径如下:
应用 -> AudioTrack API -> AudioFlinger -> HAL(硬件抽象层)-> 硬件(扬声器、DAC、蓝牙无线电)
当音乐播放器应用想要产生声音时,它创建一个 AudioTrack(或使用较新的 AAudio API,它仍然通过相同的系统路由)。应用向这个 track 写入 PCM 音频采样——以源文件包含的任何采样率和位深度解码的音频数据。
然后这些采样进入 AudioFlinger,Android 的中央音频混音和路由服务。AudioFlinger 是 Android 音频的交通控制器。它从系统上的每个应用获取音频流,将它们混合在一起,应用系统级效果(如系统音量),并将结果路由到正确的输出设备。它作为具有提升优先级的原生系统服务运行,你设备上的每一个音频采样都要经过它。
AudioFlinger 下面是硬件抽象层(HAL),一个由手机制造商编写的设备特定翻译层。HAL 将 AudioFlinger 的输出转换为实际硬件期望的格式——无论是内部 DAC 的 I2S 流、外部 DAC 的 USB 音频包,还是无线耳机的编码蓝牙音频。
从系统设计角度来看,这个架构很优雅。任何应用都可以播放音频而无需担心硬件细节,多个音频流无缝混合,系统维护对路由和音量策略的控制。但对于音乐播放,它引入了一个问题。
AudioFlinger 问题
AudioFlinger 以固定采样率运行。在大多数 Android 设备上,这个速率是 44,100 Hz 或 48,000 Hz——设备制造商在配置 HAL 时决定,通常在系统运行时无法更改。现代手机上最常见的默认值是 48 kHz。
这个固定速率的存在是因为 AudioFlinger 本质上是一个混音器。它需要将来自多个来源的音频——你的音乐、来电通知、导航指示、电话——合并成发送到硬件的单一输出流。混合音频要求所有流采样率相同。AudioFlinger 不是每次新流开始时动态改变混音器速率(这会中断所有其他活动流),而是选择一个速率并将所有内容重采样以匹配。
所以设备上的每个音频流都被重采样到混音器的固定速率:
- 你的 96 kHz 高解析度 FLAC?降采样到 48 kHz。
- 你的 44.1 kHz CD 翻录在 48 kHz 混音器上播放?升采样到 48 kHz。
- 你的 48 kHz 播客在 48 kHz 设备上?未经修改通过——你运气好。
- 你的 DSD64 转换为 176.4 kHz PCM?降采样到 48 kHz。
Google 为通用场景设计了 AudioFlinger,坦率地说,对消费级手机来说这是一个合理的工程决定。一个播放通知声的同时串流 Spotify 和导航的设备需要一个共同的混音速率。但它从来不是为发烧友播放设计的,重采样品质虽然足够,但不是你在保真度是唯一目标时会选择的。
AudioFlinger 内置的重采样器多年来有所改进。早期 Android 版本使用低品质的线性插值器。现代版本(Android 5.0 及以后)使用多相 sinc 重采样器,产生合理的结果。但”合理”和”透明”不是一回事——每次重采样操作都会引入某种程度的量化噪声和潜在的混叠失真,无论多小。
采样率协商
情况并不完全绝望。Android 确实提供了应用影响输出采样率的机制,尽管结果……不一致。
当应用通过 AAudio API(Android 的现代原生音频接口)创建音频流时,它可以请求特定的采样率。Android 将尝试满足这个请求,应用然后可以检查实际授予的速率。如果设备硬件和 HAL 支持请求的速率,你可能获得无重采样的原生播放。
Android 12 及以后版本通过更好地支持原生采样率切换来改进了这一点。在兼容设备上,系统可以更改 HAL 输出速率以匹配应用请求的,特别是对于 USB 音频设备。设计良好的应用可以在曲目的原生速率下获得逐位精确输出。
但”正确支持”在那句话中承担了很大的工作量。体验令人抓狂地不一致:
- Samsung 在其 HAL 实现中将采样率锁定为固定值。无论应用请求什么,硬件总是以 48 kHz 运行。
- Pixel 设备通常更宽容,让你协商。但即使在这里,行为在不同硬件代之间也有所不同。
- OnePlus?取决于固件版本。这就是 Android 音频开发的现实。
- 一些 OEM 允许动态速率切换,但仅限于某些输出设备(USB DAC 可以,耳机插孔不行)。
- 某些设备上的开发者设置公开了”USB 音频路由”切换或采样率覆盖,但这些对普通用户是隐藏的且未标准化。
实际结果是应用开发者不能依赖获得任何特定的采样率。应用必须探测设备、请求速率、检查实际接收的内容并相应调整。这种探测-请求-验证模式是唯一可靠的方法。
USB DAC 绕过
USB 音频 class 1 和 class 2 设备提供了 Android 上高品质音频输出最有前景的路径。当你连接 USB DAC 时,Android 的 USB 音频驱动程序创建一个新的音频输出设备,AudioFlinger 可以将其作为目标。因为 USB DAC 通常支持多个采样率并向主机报告其功能,所以获得原生速率播放的可能性更大。
使用 USB DAC 的音频链如下:
应用 -> AAudio API -> AudioFlinger -> USB Audio HAL -> USB 音频类驱动程序 -> USB DAC
注意 AudioFlinger 仍在路径中。与桌面操作系统不同——Windows 上的 WASAPI 独占模式或 macOS CoreAudio 中的独占模式可以绕过系统混音器——Android 不提供对音频硬件的真正独占访问。AudioFlinger 始终位于应用和设备之间。始终如此。
然而,当条件完美对齐时,AudioFlinger 可以作为直通而不是重采样器:
- 应用通过 AAudio 请求曲目的原生采样率。
- AudioFlinger 检查 USB audio HAL 是否支持该速率。
- 如果支持,AudioFlinger 以该速率配置其输出。
- 因为唯一活跃的音频流匹配混音器速率,不会发生重采样。
- 采样未经修改地通过 AudioFlinger 并以原始速率到达 USB DAC。
这是 Android 上最接近逐位精确的方式。采样在数学上没有被改变,即使它们在技术上经过了混音器。要使此工作,你还需要没有系统声音或其他音频流同时活跃——任何其他正在播放的内容会迫使 AudioFlinger 回到混音和可能的重采样。
应用必须处理显著的复杂性才能使其工作:探测 USB 设备的支持速率、请求正确的速率、验证授予的速率,以及优雅地处理设备断开。应用还需要管理自己的音频缓冲区大小,因为 USB DAC 与手机内置音频有不同的延迟特性。
蓝牙:另一层转换
蓝牙音频在 AudioFlinger 之上添加了完全独立的转换步骤。在你的音频通过系统混音器后,它进入蓝牙音频栈,在无线传输之前被编码为蓝牙音频编解码器。
链路变成:
应用 -> AudioFlinger -> 蓝牙编解码器编码器 -> 无线传输 -> 耳机/音箱
每个蓝牙编解码器都是有损的。没有例外。蓝牙链路上的可用带宽对于高采样率的未压缩音频来说根本不够。编解码器的区别在于它们压缩的程度以及在可用带宽内实现的品质。
SBC(Sub-Band Codec)是通用基线——每个蓝牙音频设备都支持它。它最高约 345 kbps 和 48 kHz。它随着更好的编码器实现有了显著改善,但它仍然是最低公分母。
Sony 开发的 LDAC 从 Android 8.0 开始被纳入 Android 开源项目,提供高达 990 kbps 和 96 kHz 的最高品质。在其最佳设置下,LDAC 通常被认为对大多数内容是透明的——意味着训练有素的听众在受控测试中难以将其与有线原始音频区分开来。但它仍然是有损压缩。
有关所有蓝牙编解码器——LDAC、aptX、aptX HD、aptX Adaptive、AAC、SBC 和 LC3——的详细比较,请参阅我们的蓝牙音频编解码器指南。
重要的一点是:即使你绕过了 AudioFlinger 的重采样(通过获得原生速率输出),蓝牙编码之后会重新压缩一切。通过蓝牙的逐位精确播放在物理上是不可能的。你能做的最好的事情是向蓝牙编码器提供最高品质的源信号,让它进行最好的压缩。
Echobox 如何驾驭 Android 音频栈
我们从头开始设计 Echobox,以适应(和绕过)Android 音频系统的限制。我们的三层架构——Flutter 用于 UI,Rust 用于音频编排,Zig 用于实时输出——给了我们对你的音频在管道每个阶段发生什么的不寻常控制。
架构
Flutter 层处理你看到和交互的一切——音乐库浏览器、正在播放屏幕和设置。它从不直接接触音频数据。
Rust 引擎位于中间,处理繁重的工作:文件解码(通过 Symphonia 库处理 FLAC、MP3、AAC 和 DSD 等格式)、采样率转换、格式归一化、音频分析和状态管理。我们选择 Rust 是因为音频处理需要既正确又快速——它的内存安全和零成本抽象的组合意味着我们不必在两者之间选择。
Zig 层运行实时音频回调——当操作系统每约 10 毫秒请求下一块音频采样时触发的代码。这段代码必须以零分配和零阻塞操作立即响应。我们使用 Zig 是因为它保证没有隐藏的控制流和没有隐藏的内存分配——你可以阅读代码并确切知道它在运行时会做什么。Zig 回调从无锁环形缓冲区(由 Rust 引擎填充)读取预解码的采样,并应用七阶段 DSP 链:ReplayGain、前置放大、参数均衡器、交叉馈送、音量、图形均衡器和限制器。
智能采样率处理
我们不是盲目地以任何速率发送音频然后听天由命,而是主动与设备协商:
- 通过打开一个临时 AAudio 流并让 Android 报告当前输出设备的最佳速率来探测设备的原生速率。
- 在初始化实际播放流时请求该速率。
- 通过读回 Android 实际提供的内容来验证授予的速率——因为授予的速率可能与请求的不同。
- 当曲目的采样率与设备速率不同时,使用高品质 sinc 插值重采样器智能地重采样。这个重采样器使用带有 BlackmanHarris 窗函数的 256 抽头 FIR 滤波器,明显好于 AudioFlinger 的内置重采样器。
这里的关键优势是避免双重重采样。如果一个简单的应用解码 44.1 kHz 的 FLAC 并以 44.1 kHz 输出到 48 kHz 设备,AudioFlinger 会用自己的算法将其重采样到 48 kHz。我们通过检测设备运行在 48 kHz 并自行执行一次干净的高品质重采样到 48 kHz 来避免这个问题——所以 AudioFlinger 没有什么需要转换的了。
逐位精确模式
对于 USB DAC 用户,Echobox 提供逐位精确模式,将 Android 的功能发挥到极致:
- 向 DAC 请求曲目的精确原生采样率。
- 绕过整个 DSP 链——没有均衡器、没有音量调整、没有 ReplayGain、没有限制器。解码采样未经修改通过。
- 如果 DAC 无法支持请求的速率,播放以清晰的错误失败,而不是悄悄重采样。这是故意的——如果你要求逐位精确,你值得知道什么时候你没有得到它。
信号路径透明度
也许最重要的是,Echobox 向你精确展示正在发生什么。信号路径诊断显示实时揭示完整的音频链:源格式和采样率、重采样是否活跃及其品质、哪些 DSP 阶段已启用、设备实际运行在什么输出速率,以及如果适用正在使用哪种蓝牙编解码器。
这种透明度在音乐播放器应用中是罕见的。大多数播放器是一个黑盒——你按下播放并信任正确的事情正在发生。Echobox 让你验证。如果你的 96 kHz FLAC 因为手机内部 DAC 不支持 96 kHz 而被重采样到 48 kHz,你会看到。如果你的 USB DAC 正在接受 96 kHz 且 DSP 链被完全绕过,你也会看到。
路由感知行为
Echobox 按路由类型分类每个输出设备——本地扬声器、USB DAC、蓝牙或网络渲染器——并相应调整其行为。当检测到蓝牙时,逐位精确模式自动禁用(因为它在有损无线编解码器上毫无意义),DSP 处理保持活跃,这样你可以使用均衡器和音量控制。当连接 USB DAC 时,完整的采样率协商和逐位精确选项变得可用。
这种路由感知策略意味着你不需要在每次切换耳机和扬声器时手动调整设置。应用检测变化并为每种输出类型做出智能默认。对于向网络音箱的 UPnP/DLNA 串流,Echobox 添加了另一层智能——检测设备功能并在需要时转码。如果你正在评估什么让音乐播放器在音频栈之外真正达到发烧级,我们的发烧友音乐播放器指南涵盖了完整的画面。你还可以查看路线图了解 Android 之外的平台可用性。
Android 音频的现实
- AudioFlinger 是瓶颈。Android 上的每个声音都通过这个系统混音器,它以固定采样率(通常 48 kHz)运行。所有音频在到达硬件之前都被重采样到这个速率。
- Google 为一般用途设计了 AudioFlinger,而非发烧友播放。将通知、电话和音乐混合成一个流需要共同的采样率,重采样是权衡。这是一个合理的设计决定——只是不是为我们考虑做出的。
- USB DAC 提供了 Android 上的最佳品质路径。它们支持多个采样率,正确使用时,AudioFlinger 可以不重采样地传递音频。
- Android 不提供真正的独占模式。与 Windows 上的 WASAPI 独占或 macOS 上的独占模式不同,没有方法完全绕过 AudioFlinger。混音器始终在路径中,即使它作为直通。
- 蓝牙在一切之上又添加了一层有损转换。没有蓝牙编解码器是无损的,通过无线的逐位精确播放是不可能的。
- Echobox 通过探测设备速率、在需要时执行高品质重采样、提供逐位精确 USB DAC 输出,以及通过信号路径诊断向你展示确切情况来处理这些复杂性。我们的 Rust/Zig 架构给了我们大多数应用根本没有的对音频管道的控制。
- 诚实的底线:在 Android 上,让你的音乐从应用到耳朵而不经历不必要的处理,要么需要具有适当驱动程序支持的 USB DAC,要么接受系统会重采样。我们构建 Echobox 是为了给你驾驭这个现实的工具和透明度——如果你是一个正在构建类似东西的开发者,我们希望这个指南能为你节省我们花了数月才弄明白的时间。