后台音频、AudioTrack 与 Offload 功耗
后台音频要先过资格关:媒体会话、前台服务和音频焦点(系统在多个应用之间协调播放、暂停和降低音量)规则同时满足,系统才允许持续发声;资格确定后,播放管线再在 AudioTrack、低延迟路径和硬件 Offload 之间做选择。稳定性、时延和功耗三个目标各有取舍,没有哪套配置能同时把它们推到最优。
源码判断基线:Android 17 / API 37 /
android-17.0.0_r1。
后台播放资格、生命周期与功耗
后台音频的治理范围
平台文档把 Android 17 这组限制叫“硬化”(hardening),它强制执行的是后台音频的生命周期条件,覆盖后台音频播放、音频焦点请求、音量与铃声模式修改,和音质、编码处理没有关系。生命周期条件不满足时,最隐蔽的一种现场是:播放器仍报告“正在播放”,用户却听不到声音,而下载、解码、WakeLock(唤醒锁)和前台服务可能都还在运行。所以我们排查后台音频,第一步要确认声音是否真的在输出——播放器状态本身回答不了这个问题。
排查时我们要回答两组问题:
- 系统是否允许这次音频交互:页面是否可见、前台服务(foreground service,FGS)是否存在、服务有没有 while-in-use(WIU,可见或用户操作时授予)能力、音频用途是否符合豁免条件。
- 播放失效后是否仍消耗资源:网络请求、解码线程、WakeLock(阻止设备休眠的唤醒锁)、MediaSession(系统媒体会话)和前台服务是否按停止原因释放。
这些机制在其他章节有展开:AudioFlinger(原生音频混音与输出服务)、AAudio(原生低延迟音频 API) 和音频线程调度见 1.20,后台执行规则见 5.3 与 25.2,Media3(Jetpack 媒体库)、Codec2(Android 多媒体编解码框架) 和多媒体管线见 13.12。
两级限制:FGS 基础门槛与 WIU 附加门槛
这套规则要分两级看。下文把 targetSdkVersion 简称为 target,即应用声明的目标 API 版本;两级里只有第二级看 target,把它压缩成一句“target 37 才限制后台音频”,会漏掉旧 target 应用同样要满足的基础门槛。
所有应用都要满足的基础门槛
任何运行在 Android 17 上的应用,只要在后台播放、申请音频焦点或修改音量,就必须满足以下任一条件:
- Activity 对用户可见;画中画(PiP)也属于可见场景。
- 应用正在运行一个前台服务,其类型不是
shortService(短时任务类型)。
这条规则与 targetSdkVersion 无关:没有可见 Activity、也没有合格前台服务时,target 35、36 和 37 同样都会被限制。
target 37 增加的 WIU 门槛
应用以 API 37 为 target 且位于后台时,前台服务还要带上 WIU 能力。获得 WIU 的常见路径有两条:在应用可见时启动服务,或由通知点击、桌面小组件、媒体按键等明确的用户操作触发后台启动。
WIU 由系统侧维护,应用在清单里声明不出来:ActivityManager(系统的进程和应用生命周期管理服务) 根据前台状态、前台服务的启动来源和系统授予的例外条件维护能力状态,音频服务再读取 AppOps(按操作检查权限的机制)结果。HardeningEnforcer 判断时看的是上面这些系统状态,应用的 startForeground() 调用栈、PendingIntent(可由其他组件代为触发的延迟操作) 的创建者以及某个固定的“最近交互窗口”,都落在它的判断范围之外。
精确闹钟的豁免范围
API 37 应用同时满足两个条件时,可以免除 WIU 要求:
- 已获准使用 exact alarm(精确闹钟),即持有
USE_EXACT_ALARM,或拥有SCHEDULE_EXACT_ALARM特殊访问。 - 本次音频使用
AudioAttributes.USAGE_ALARM。
这层豁免只免第二级:后台闹钟仍需要可见 Activity 或非 shortService(短时任务前台服务类型) 前台服务。用途声明应与用户可感知的功能一致,普通媒体内容想借 USAGE_ALARM 规避限制是行不通的。
版本差异表
| 状态 | target 35–36 | target 37 |
|---|---|---|
| Activity 可见 | 允许 | 允许 |
后台且没有非 shortService FGS | 播放静音、焦点失败、音量修改无效 | 同左 |
| 后台,有 FGS,但 FGS 没有 WIU | 通过 target 兼容豁免,只要求基础 FGS | 受到完整限制 |
| 后台,有带 WIU 的 FGS | 允许 | 允许 |
后台闹钟,有 FGS、exact alarm 权限和 USAGE_ALARM | 不需要 API 37 的闹钟豁免 | 免除 WIU 要求 |
表中“允许”只表示通过了后台音频硬化检查;焦点是否拿到、路由是否成功、播放是否完成,还要按各自规则另行确认——焦点锁定、设备断开、播放器错误是独立的问题,我们接下来分别看。
三类 API 的失败表现
后台音频硬化刻意让多数失败保持静默,避免不合规应用借异常改变系统行为。判断一次失败时,我们要把返回值、系统日志和播放状态放在一起看。
| 交互 | 默认限制模式 | throw(显式失败)测试模式 |
|---|---|---|
| 音频播放 | 输出被静音;播放 API 不提供对应异常或失败消息 | 有显式写入的接口持续返回错误;没有显式写入点的播放模式可能导致进程崩溃 |
requestAudioFocus() | 返回 AUDIOFOCUS_REQUEST_FAILED,且不会获得焦点 | 抛出 IllegalStateException |
| 音量与铃声模式 API | 调用被忽略,目标状态不变 | 抛出 IllegalStateException |
拿返回值来说,AudioTrack.write() 返回成功,只说明数据被客户端提交了,系统有没有把声音送到输出设备还要另查;Media3 的 playWhenReady、playbackState 描述的也只是播放器状态机,平台授权状态在别处。所以线上诊断要额外记录焦点结果、前台服务状态、Activity 可见性、音频用途和 AudioHardening 日志,这几项拼起来,我们才能还原一次静默失败。
FGS 和 MediaSession(向系统发布播放状态和控制入口的媒体会话) 的正确职责
资格的最低门槛是“非 shortService 前台服务”,但媒体应用选类型要对得上业务:持续音乐、播客和锁屏视频音频用 mediaPlayback(媒体播放) 类型,并通过 MediaSession 对接系统媒体控件。
使用 Media3 时,MediaSessionService 负责把 Player 与 MediaSession 放进服务生命周期,提供媒体通知以及外部控制入口。清单至少要声明对应的前台服务权限和服务类型。
下面的清单片段用于声明后台媒体播放服务:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application>
<service
android:name=".PlaybackService"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
</application>
这些声明解决的是服务类型和权限问题;WIU 要另行获得,清单里声明不出来。服务应在用户开始播放时启动,让 Player、MediaSession、通知和 FGS 状态跟着同一份播放意图变化。
何时保留或停止 FGS
用户已开始播放后,短暂缓冲、临时网络故障或 AUDIOFOCUS_LOSS_TRANSIENT 都还算不上播放意图结束。按官方建议,暂时性失败不足 10 分钟期间保留 mediaPlayback FGS,便于之后恢复播放。
内容播放完成、收到永久 AUDIOFOCUS_LOSS、用户明确暂停且不再继续、或发生不可恢复的错误时,应停止音频交互、结束 MediaSession 并停止 FGS。Media3 的 MediaSessionService 在播放器暂停、停止或失败超过 10 分钟且没有新的用户交互后,也会自行退出前台服务状态。
这个 10 分钟说的是暂时性失败的窗口,而非空闲额度:能确认播放意图已经结束就立即释放;只有失败仍判断为暂时性时,才保留恢复所需状态。

图:MediaSessionService暂停、停止或失败超过10分钟且无新交互会退出前台状态;不能把它当业务长期保活预算。
从 target 36 迁移到 target 37
升级 target 没有要求业务改用新的 Java 或 Kotlin 音频 API,改动集中在播放入口和服务生命周期。
建立入口清单
列出所有可能开始或恢复播放的来源,并记录该来源能否表达用户当下的操作:
| 入口 | 处理建议 |
|---|---|
| 可见页面内的播放按钮 | 在该操作中启动 mediaPlayback FGS,然后开始播放 |
| 通知播放按钮、桌面小组件、媒体按键 | 保留用户操作来源,交给 MediaSession 或受支持的后台启动路径 |
BOOT_COMPLETED | 不自动发声;提供播放恢复入口,等待用户操作 |
| 后台任务、网络恢复广播 | 可以准备轻量状态,但不要自行恢复音频交互 |
| 投屏断开后回到本机 | 若没有新的用户操作,不要假设原 FGS 仍具有 WIU;按当前服务状态决定继续或提示用户 |
| exact alarm | 只用于确有闹钟语义的功能,并同时核对权限、USAGE_ALARM 和 FGS |
还有一个更靠前的失败点:mediaPlayback FGS 本身可能没办法从后台启动——系统在走到音频硬化检查之前,就先把服务启动拒掉了。这时我们先查 ForegroundServiceStartNotAllowedException 或 ActivityManager 的拒绝记录,再查 WIU 和音频日志。
给一次播放会话补齐证据
每次会话建议记录:
- 系统 API level、
targetSdkVersion、应用版本和设备型号。 - 用户入口、Activity 可见性、FGS 类型、FGS 启动时间和启动结果。
- MediaSession 标识与状态、音频
usage、焦点请求结果和播放器错误。 - 当前音频路由(声音实际输出的设备路径)、蓝牙设备类别、网络状态、停止原因和资源释放结果。
同一台 Android 17 设备上,target 36 与 target 37 的行为也不同,所以数据分析至少要按 (API level, targetSdkVersion) 分组,只看系统版本会得出错误结论。
Android 17 r1 的源码执行路径
android-17.0.0_r1 把三类交互放在不同位置检查,它们共享同一个 hardening override(测试覆盖状态),但不是每个 API 都由 Java 和 C++ 重复判断。
| 交互 | 主要实现位置 | 判断内容 |
|---|---|---|
| 音频焦点 | AudioService 调用 HardeningEnforcer.blockFocusMethod() | OP_TAKE_AUDIO_FOCUS、OP_CONTROL_AUDIO、target、闹钟及权限豁免 |
| 音量与铃声模式 | AudioService 调用 HardeningEnforcer.blockVolumeMethod() | OP_CONTROL_AUDIO_PARTIAL、OP_CONTROL_AUDIO、target 和权限豁免 |
| 播放 | AudioFlinger Tracks.cpp 的 getHardeningDecision() 与 AfPlaybackCommon | 播放 Track(音频播放轨道)创建时确定限制级别,并持续观察两级 AppOps |
音频焦点请求先经过 HardeningEnforcer,通过后才进入 MediaFocusControl 的焦点仲裁。后台硬化返回 AUDIOFOCUS_REQUEST_FAILED 时,请求并没有进入常规焦点竞争,和“另一应用占用了焦点”是两类不同的失败,归因时我们要把这两者分开。
partial 与 full 的含义
AudioFlinger 为播放 Track 观察 OP_CONTROL_AUDIO_PARTIAL 和 OP_CONTROL_AUDIO:
- partial(基础级)对应所有应用都要满足的生命周期条件,典型失败原因是没有合格 FGS。
- full(完整级)增加 API 37 的 WIU 条件,典型失败原因是已有 FGS,但服务没有 WIU。
官方日志里的 level 也对应这两级:
level: partial:应用没有运行合格的前台服务。level: full:应用有前台服务,但该服务缺少 WIU。
限制级别还会受平台兼容条件影响:在默认策略且 hardening_strict 功能开关开启时,r1 的 C++ 决策会将旧 target、符合条件的闹钟及 BLUETOOTH_CONNECT 等兼容情形降为 partial;hardening_usage 关闭时,闹钟和辅助功能 usage 还有旧分支。系统 usage、路由或电话特权则可免于检查。强制 ENABLE / THROW 在这些分支前就返回 full,不能把默认兼容分支套到强制测试模式。Java 焦点、音量入口的条件也不完全相同,不能互相外推。我们做应用适配时,以公开的后台播放模型为准即可,内部兼容分支留给平台自己。
Offload 与 MMap 的限制适用
AfPlaybackCommon 在 Track 创建时注册两条异步 AppOps(系统按操作检查调用方是否获准的机制) 会话。Offload 把音频处理交给专用音频处理器,MMAP 走内存映射的低延迟路径——这两类 Track 一样要过检查:源码为它们设置了 40 ms 的 asyncBroadcast 异步唤醒延迟,让 partial 与 full 两个权限回调都有机会在音频线程唤醒前到达。这个延迟属于权限状态传播的实现细节,应用侧把它当播放缓冲参数去调是调不动的。

图:延迟用于让两类权限回调有机会先到达;Offload与MMAP仍不能绕过后台生命周期。
每条 Track 只记录一次对应的后台播放限制事件,避免同一 Track 反复上报;新建 Track 会重新计算决策,所以重新创建播放器修不好不合规的生命周期,只会得到一条新的受限 Track。
Java 与原生层的权限判断
Java 的焦点和音量入口用 AppOps,AudioFlinger 的播放路径一边按权限与 target 选择限制级别,一边用异步 AppOps 判断当前是否允许输出,两边各管一段。所以从 PermissionEnum 的静态权限检查,推不出“音量被禁,但 Track 一定有声”这类结论:是否静音,要同时看 Track 的 AppOps 状态、限制级别和豁免结果。

图:焦点被hardening拒绝时,请求尚未进入常规竞争;Track有写入也不证明当前被允许输出。
调试命令存在版本差异
调试命令本身存在版本差异:Android 17 官方页面在本轮 2026-10-25 核验时仍使用 set-enable-hardening,而 android-17.0.0_r1 的 AudioManagerShellCommand 使用 set-hardening 与 clear-hardening。所以命令名要按设备镜像逐一确认。
这组命令先读取设备自己的帮助,再按匹配分支启用测试模式:
# 先确认当前镜像提供的 hardening 子命令
adb shell cmd audio help | grep -i hardening
# android-17.0.0_r1
adb shell cmd audio set-hardening enable
adb shell cmd audio set-hardening throw
adb shell cmd audio clear-hardening
# Android 17 当前官方文档所述的新命令
adb shell cmd audio set-enable-hardening enable
adb shell cmd audio set-enable-hardening throw
adb shell cmd audio set-enable-hardening disable
r1 的 clear-hardening 与 set-hardening default 都恢复平台默认行为,set-hardening disable 则是强制关闭限制,两者有区别;新命令中的 disable 同样表示关闭全部限制。
r1 的测试覆盖状态会同步写入 system_server(承载多数 Java 系统服务的进程) 与 audioserver(承载原生音频服务的进程),重启后即失效。系统镜像若只提供新命令、没有 clear 或 default 子命令,要按设备帮助确认恢复方式,必要时重启专用测试设备。测试报告还要记录命令和构建号。
enable 会把完整限制应用到所有应用,并取消 target 与闹钟豁免,适合挖掘潜在问题;默认行为下的 target 36/37 对照测试仍要做。throw 还会把静默失败改成异常、持续写入错误或崩溃,只用于开发与回归环境。
取证:把许可状态和功耗放在一条时间线上
这些命令用于保存复现现场。dumpsys 导出系统服务状态,logcat 导出系统日志。batterystats --reset 会清除设备上已有的电量统计,只应在专用测试设备上执行。
# 音频策略、焦点、音量和 hardening 事件
adb shell dumpsys audio > audio.txt
adb logcat -b all -v threadtime \
| grep -Ei "AudioHardening|AudioFocus|AudioTrack" > audio-log.txt
# MediaSession 与前台服务
adb shell dumpsys media_session > media-session.txt
adb shell dumpsys activity services YOUR_PACKAGE_NAME > services.txt
# 为本次场景建立单独的电量统计窗口
adb shell dumpsys batterystats --reset
# 完成复现后导出
adb shell dumpsys batterystats > batterystats.txt
我们要把这批文件用统一的会话标识串起来,按同一时钟关联事件。dumpsys audio 说明平台为何限制交互,media_session 和服务记录说明应用向系统公开了什么状态,batterystats 说明播放停止后是否仍有 WakeLock、网络和后台活动。
Perfetto 是 Android 的系统级跟踪工具,采集应覆盖音频、调度、CPU 频率、电源、Binder(Android 跨进程调用机制) 和 ActivityManager 相关轨迹。
阅读顺序我们可以固定为:用户操作、FGS 启动、MediaSession 激活、焦点结果、Track 创建、页面退后台或锁屏、网络或路由变化、播放停止、资源释放。声音消失而 CPU、网络或 WakeLock 仍活跃时,先区分暂时缓冲、路由切换、静默限制与永久停止;只有确认播放意图已结束或恢复已无价值后,残留工作才构成清理问题。不能把合法的短暂恢复窗口直接判为泄漏。
长时播放的功耗检查
后台音频的能耗由多条路径叠加,我们先动手测量,确认主要消耗来源,再改配置。
| 资源 | 观察信号 | 处理方向 |
|---|---|---|
| 解码 | 播放线程 CPU 时间、Media3 offload 事件、设备与格式支持情况 | 长音频可评估 audio offload;音效、倍速等功能不兼容时保留普通解码 |
| 缓冲 | underrun(缓冲数据未能及时供给播放端)、加载频率、内存占用和恢复耗时 | 按内容与网络条件配置,不复制视频的缓冲参数 |
| 网络 | 失败码、重试间隔、重复下载字节、播放停止后的流量 | 使用有上限的重试退避(连续失败时逐步延长间隔);不可恢复时停止预取 |
| WakeLock | tag(用于识别持有者的标签)、持有时长、播放结束后的残留 | 为持有设置超时,并在每个终止路径释放 |
| 非播放任务 | 歌词、封面、推荐、埋点线程的 CPU 时间与唤醒 | 降低非必要任务频率,避免与解码和输出线程竞争 |
| 蓝牙 | 路由变更、断连重连、设备类别、控制器活动 | 把路由故障和播放器故障分开统计 |
Audio offload 改变的是解码与输出所使用的计算资源,后台硬化检查的是应用生命周期,两条线相互独立:offload Track 照样受 hardening 控制;反过来,LC3(Low Complexity Communication Codec,低复杂度通信编解码器) 配置匹配不上 ADSP(Audio Digital Signal Processor,音频数字信号处理器),也推不出应用会因处于后台而被静音。这里的 ADSP 指音频数字信号处理器。
codec_manager.cc 的 IsLc3ConfigMatched() 逐项比较采样率、帧时长、每条 ISO(等时)流的通道数和每帧字节数,以此判断 LC3 配置是否匹配;匹配失败影响的是编解码执行路径,FGS 或 WIU 归另一套规则决定。target_latency 表示 Bluetooth LE Audio(低功耗蓝牙音频)配置期望的时延等级,它参与配置选择;设备能耗要落到具体控制器、耳机和媒体参数上测量,仅凭“低延迟”推算不出来。
Android 17 r1 的 LeAudioService.setSystemSuspended() 会在系统进入 suspend(低功耗挂起)时停止后台扫描,恢复且确有扫描需求时再启动,这是平台蓝牙服务的省电行为,第三方播放器调不到这个接口。应用侧把路由和连接事件记录好,让系统状态变化可见,别再用持续扫描或高频重连去掩盖它。
验证场景与线上指标
回归测试要覆盖入口、target、音频用途和路由的组合,用例时长按产品的预期会话设计;除平台规定的 10 分钟暂时性失败窗口外,不必自行假定别的固定分钟数。
| 用例 | 操作 | 预期 |
|---|---|---|
| 可见页面播放 | 页面可见时开始播放 | 无其他焦点阻碍、权限与路由满足时,焦点成功并正常出声 |
| 锁屏继续播放 | 用户开始播放后锁屏 | mediaPlayback FGS 与 MediaSession 保持,通知可控制 |
| 无 FGS 的后台播放 | 停止 FGS 后继续写音频 | target 36 与 37 都受到 partial 限制 |
| FGS 没有 WIU | 从不带用户意图的后台入口启动服务并播放 | target 36 走兼容行为;target 37 受到 full 限制 |
| 暂时性弱网或焦点丢失 | 在平台建议窗口内恢复 | 保留播放意图;恢复成功后继续播放 |
| 永久焦点丢失或不可恢复错误 | 触发终止条件 | 停止播放器、MediaSession、FGS、网络和 WakeLock |
| exact alarm | 以 USAGE_ALARM 触发真实闹钟 | 权限和 FGS 满足时,target 37 免 WIU |
| 通知、媒体键恢复 | 从系统媒体控件或外设恢复 | 形成新的用户播放意图并启动所需服务 |
| 蓝牙与 LE Audio 切换 | 连接、断开并改变输出设备 | 状态和路由一致;不把路由断开统计为 hardening |
hardening enable / throw | 对相同场景分别执行 | 静默失败和显式失败路径都有可解释记录 |
线上指标应同时覆盖“用户想听却没有声音”和“用户不再听但资源仍运行”。P90、P99 表示 90% 和 99% 的样本不超过对应数值,用于观察平均值容易掩盖的高耗电会话:
| 指标 | 主要维度 |
|---|---|
| 后台播放中断率 | API level、target、入口、FGS 状态、ROM(厂商系统版本) |
| 音频焦点失败率 | AudioAttributes.usage(音频用途)、焦点类型、页面可见性、FGS 与 WIU |
AudioHardening 命中 | partial/full、包名、入口、是否设置测试覆盖状态 |
| MediaSession 异常结束 | Player 状态、Session 状态、停止原因、网络 |
| 后台耗电 P90/P99 | 会话时长、网络、路由、offload、设备 |
| 用户手动恢复播放比例 | 页面、通知、媒体键、蓝牙设备 |
小流量发布时应同时比较 target 36 与 37。若中断率上升,我们按 partial/full 区分是缺 FGS 还是缺 WIU;若声音已经停止但耗电上升,就去检查资源释放与重试状态机。
后台音频小结
Android 17 后台音频硬化分两级条件:所有应用都需要可见 Activity 或非 shortService FGS;target 37 的后台应用还需要带 WIU 的 FGS,真实闹钟可以在满足权限和 USAGE_ALARM 时免除 WIU。
应用适配的重点,是让播放入口、MediaSession、mediaPlayback FGS 和资源生命周期表达同一份用户意图;平台限制播放后,业务也要停掉无效的网络、解码和保活。验收时除了看播放器有没有报错,还要确认声音、焦点、通知、路由和资源释放都解释得通。
AudioTrack 缓冲、Offload 与设备能力
资格确定后轮到选路径:音频管线按格式、效果、延迟和硬件支持决定走不走 Offload;一旦回退到混音路径,CPU 和 WakeLock 都要重新评估。
音频 Offload 的适用范围
长音频播放的耗电常出现在屏幕关闭之后。用户在听播客、有声书、长视频背景音或语音助手响应时,界面已经不再绘制,但播放器仍可能持续做网络、解码、写入、埋点和 WakeLock 管理。此时继续用 CPU 解码和高频写入,会让本应低占用的任务持续耗电。
Audio offload(音频卸载)解决的就是这类长时间播放的 CPU 参与度问题:平台把音频处理交给专用硬件或 DSP,应用可以一次写入更多数据;硬件 buffer(缓冲区) 保存待播放内容后,框架数据管道可以暂停,CPU 因而有机会休眠。低延迟场景走的是另一条路:游戏音效、乐器、语音通话和实时互动仍应关注 AAudio 的低延迟模式、MMAP、缓冲区大小、callback(数据回调) 的稳定性与线程调度,详见 1.20 节。
我们要依次完成应用侧的四步:能力探测、接入、验证和小流量发布。AudioFlinger、AAudio 与 MMAP 的机制详见 1.20 节;MediaCodec(编解码接口)、Media3 与播放管线详见 13.12 节;Android 17 后台音频 hardening 见本文前半部分。
场景边界:什么时候值得开启 Offload
Offload 的收益来自少唤醒、少 CPU 解码和少数据搬运。适用场景一般有三个特征:播放时间长、屏幕关闭或界面参与少、媒体格式和设备硬件支持 offload。只满足其中一项,收益可能被兼容代价抵消。
| 场景 | 建议策略 | 判断依据 | 不适合的原因 |
|---|---|---|---|
| 音乐、播客、有声书锁屏播放 | 优先评估 Offload | 长时间连续播放,用户对毫秒级延迟不敏感 | 倍速、跳过静音、音效处理可能限制 Offload |
| 长视频后台音频 | 可小流量验证 | 屏幕关闭后画面不再参与,音频仍持续 | 视频前台播放时显示和解码成本常高于音频成本 |
| 短音效、按钮声、提示音 | 禁用 | 播放时间短,创建和切换成本占比高 | CPU 省电收益太小,延迟稳定性更要紧 |
| 游戏、乐器、K 歌、实时语音 | 禁用 | 需要低延迟和稳定 callback | Offload 面向省电,不面向低延迟 |
| Assistant(系统语音助手)回复音频 | 分场景处理 | 短回复更看延迟,长回复可评估省电 | Android 17 音量流隔离和后台资格要分开判断 |
| 闹钟、提醒 | 按 USAGE_ALARM 单独处理 | 与后台音频 hardening 的豁免相关 | 不应把媒体播放伪装成 alarm usage |
Media3 文档的建议与此一致:短音频或亮屏播放时,音频通常不是主要耗电项;长时间、屏幕关闭后的播放可以评估 ExoPlayer audio offload。官方同时说明设备和格式支持会变化,Offload 会限制变速、跳过静音等效果能力,所以光看 API level 下不了结论。

图:CDD的5秒是声明该能力设备的最低缓冲要求,不是所有Offload流的通用深度或性能测量。
AAudio Power Saving Offloaded 的探测流程
AAudio 的 AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED 从 API 36 可用,并且只支持输出流;API 36 同时为 AAudio 公开 MP3、AAC LC、AAC HE V1/V2、AAC ELD、AAC xHE 和 Opus 等压缩格式。Android 17 又定义了可选的 MMAP PCM(未压缩采样数据)offload,设备若声明该能力,必须满足 CDD 对 PCM 格式、采样率、声道与缓冲的要求。所以“offloaded”和“压缩格式”是两回事,探测能力时必须把完整格式带上。
与普通 AAUDIO_PERFORMANCE_MODE_POWER_SAVING 相比,offloaded 模式允许应用在短时间内写入数秒压缩数据;数据进入硬件缓冲区后,应用播放线程和框架数据管道可以暂停。这里的“数秒”来自 NDK 接口说明,具体缓冲深度以设备为准。
请求归请求,生效归生效:音频流对象(代码中的 stream)打开后,我们要读取 AAudioStream_getPerformanceMode(stream) 确认实际拿到的模式;构造 builder(流配置构建器) 时也要使用媒体本身的压缩格式、采样率和声道掩码。
下面的函数是 API 36 及以上路径的探测骨架,调用方传入媒体轨道的真实配置。本文最低覆盖 API 31,接入时还必须做运行时可用性分支;NDK 库若面向较低 minSdk 构建,新函数符号不能仅靠 Java 层版本判断保护,应按 NDK 新 API 指南选择受支持的弱符号与 __builtin_available 防护,或动态查找并回退。API 37 的 flush 扩展同样需要构建和运行时边界。
bool openOffloadProbe(
aaudio_format_t format,
int32_t sampleRate,
aaudio_channel_mask_t channelMask) {
AAudioStreamBuilder* builder = NULL;
if (AAudio_createStreamBuilder(&builder) != AAUDIO_OK) {
return false;
}
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setUsage(builder, AAUDIO_USAGE_MEDIA);
AAudioStreamBuilder_setFormat(builder, format);
AAudioStreamBuilder_setSampleRate(builder, sampleRate);
AAudioStreamBuilder_setChannelMask(builder, channelMask);
AAudioStreamBuilder_setPerformanceMode(
builder,
AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED);
AAudioStream* stream = NULL;
const aaudio_result_t result =
AAudioStreamBuilder_openStream(builder, &stream);
bool granted = false;
if (result == AAUDIO_OK) {
granted = AAudioStream_getPerformanceMode(stream)
== AAUDIO_PERFORMANCE_MODE_POWER_SAVING_OFFLOADED;
AAudioStream_close(stream);
}
AAudioStreamBuilder_delete(builder);
return granted;
}
这个返回值只说明探测流本身拿到了 offloaded 模式,正式播放器还要接着处理打开失败、断开、路由变化和常规播放路径回退,并记录 result、实际模式、格式、采样率、声道、设备路由和播放器功能开关。示例为了演示探测流程立即关闭了 stream;产品代码应在正式创建路径中记录结果,省得额外再建一条探测流。

图:示例立刻close仅展示探测;正式创建时记录实际模式,避免额外开流干扰能力与资源。
API 36 的 AAudioStreamBuilder_setPresentationEndCallback() 会在应用请求停止后,等待系统与硬件中已排队的数据播放完,再通知结束。应用提前关闭 stream,回调就不会发生;回调若与数据回调共用框架的实时线程,其中要避免执行阻塞工作。
Android 17 / API 37 又增加了 AAudio_getFlushFromFrameSupport(builder) 和 AAudioStream_flushFromFrame()。flush 指丢弃已经写入但尚未播放的数据,frame(音频帧) 是这里的位置计数单位;前者必须在打开流前查询,并要求 builder 已设置 offloaded 性能模式、格式、采样率与声道掩码。后者用 inOutPosition 传入目标 frame;成功时写回实际位置,准确 flush 无法满足时也可能回写一个建议位置。必须先检查返回码,失败时的位置不是“已经执行成功”的证明,不能直接覆盖业务播放断点。
调用返回前保持串行、别并发写数据;成功后剩余数据不足时要立即补写,否则会出现 underrun。若要求 AAUDIO_FLUSH_FROM_FRAME_ACCURATE 而设备做不到从指定位置处理,r1 会返回 AAUDIO_ERROR_OUT_OF_RANGE,一次失败也不会被当作成功 flush。

图:AAudio准确失败可返回OUT_OF_RANGE并建议另一位置;AudioTrack错误/异常各有边界,不混用两套契约。
AudioTrack Offload 的 API 37 新边界
Android 17 为 AudioTrack 增加了 codec provenance(编解码格式来源)、写入 frame 计数和按写入位置 flush,解决配置来源与深缓冲定位两个问题;至于 DSP 是否真的在工作,这些 API 本身回答不了,要靠后文功耗一节的测量来判断。
| API | 适用范围 | 能回答的问题 | 不能回答的问题 |
|---|---|---|---|
AudioTrack.Builder.setCodecProvenance() / getCodecProvenance() | API 37,配置阶段写入,实例上读取 | 原始内容的 codec media type(编解码格式 MIME 类型);未设置时返回空字符串 | 不会替应用检测 codec,也不能证明当前走 DSP 或 offload |
getWrittenFramesCount() | API 37,MODE_STREAM | 成功写入后维护的逻辑 frame 坐标;成功 flush 后改为实际 flush 位置 | 不是渲染位置,pause()、stop() 和普通 flush() 也不会重置它 |
getFlushWrittenFramesFromPositionSupport(format, attributes) | API 37,创建 offload AudioTrack 前查询 | 给定 AudioFormat / AudioAttributes 创建的 offload track 是否支持按位置 flush | 不能保证所有设备、所有格式都支持 |
flushWrittenFramesFromPosition(positionInFrames, accuracy) | API 37,只允许 offload mode | 从指定写入 frame 附近丢弃已写入但未播放的数据,返回实际 flush 位置 | 不是普通 PCM AudioTrack 的通用 seek API |
codec provenance(原始内容的编解码格式来源) 由应用在 Builder 中设置。例如原始内容是 Dolby Atmos 的 E-AC3 JOC,但交给 AudioTrack 的数据格式已经转换,此时可以传入相应的 MediaFormat MIME 常量。它是来源提示,不做运行时探测。
flushWrittenFramesFromPosition() 的 positionInFrames 必须位于 0 到 getWrittenFramesCount() 之间。FLUSH_FROM_ACCURACY_BEST_EFFORT 允许系统选择不小于目标的位置;FLUSH_FROM_ACCURACY_EXACT 要求从目标位置开始。
以 android-17.0.0_r1 为准,exact 满足不了时返回 ERROR_BAD_VALUE;非法 accuracy 或越界输入抛 IllegalArgumentException;非 offload 或未初始化抛 IllegalStateException;设备不支持则抛 UnsupportedOperationException。调用期间暂停写入;成功且 Track 仍活跃时,要根据剩余数据及时补写。
需要区分三种坐标:
getWrittenFramesCount()是应用写入坐标。flushWrittenFramesFromPosition()返回新的写入坐标。getTimestamp()提供已呈现或承诺呈现的 frame 及其时间估计。
断点续播和 seek(跳转) 应以媒体时间线为主,frame 坐标作为底层执行记录;把写入计数直接保存成“用户已经听到的位置”会出错。
这些 API 在四类场景中各有用途:
- 有声书断点续播:记录用户听到的位置、已写入 frame、实际 flush 位置,避免章节跳转后重复播放或少播。
- 切歌和 seek:在 offload buffer 较深时,把“业务目标位置”和“系统实际 flush 位置”都上报,方便定位误差。
- 广告插入:广告前后切换时确认原内容 buffer 是否已丢弃,避免广告后串音。
- 线上兼容:用 support bitmask(支持位掩码)和异常类型分组统计,识别只在某些设备、格式或蓝牙路由上失败的组合。
Media3 / ExoPlayer 如何映射到平台 Offload
Media3 提供的是播放器层偏好,满足与否由平台决定。轨道选择文档建议通过 TrackSelectionParameters 设置 AudioOffloadPreferences;只有 renderer(执行解码或输出的组件)与所选媒体轨道的组合都支持时,offload 才会启用。
这段代码用于表达 Media3 侧 offload 偏好,实际是否启用仍要通过播放器事件、平台 AudioTrack 状态和系统取证确认。
val audioOffloadPreferences = AudioOffloadPreferences.Builder()
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
.setIsGaplessSupportRequired(true)
.build()
player.trackSelectionParameters = player.trackSelectionParameters
.buildUpon()
.setAudioOffloadPreferences(audioOffloadPreferences)
.build()
setIsGaplessSupportRequired(true) 要求歌曲或章节之间 gapless(无缝衔接),也可能缩小可 offload 的设备和格式组合。音乐、播客、有声书应用的取舍不同:音乐更在意 gapless;播客更常用倍速和跳过静音;有声书更在意章节 seek 和断点续播。我们把发布开关至少按 gapless、倍速、跳过静音、空间音频和蓝牙路由拆开,只留“是否开启 Offload”一档并不够用。
Media3 的实际状态应通过事件监听器 ExoPlayer.AudioOffloadListener 观察。onOffloadedPlayback() 报告当前是否采用 offloaded playback,onSleepingForOffloadChanged() 报告播放器主循环是否因 offload scheduling(卸载调度)而暂停。该 listener 标记为 @UnstableApi,表示接口仍可能变化;升级 Media3 时要重新核对兼容性。

图:Offload状态与休眠状态是不同信号;升级库后核对事件语义,缺失时记unknown而不推断。
功耗收益怎么验证
Offload 的验收不止于“听起来没问题”:CPU、唤醒、电量和用户体验数据要来自同一次播放会话,Perfetto 可把应用 CPU 时间、线程唤醒、缓冲深度、系统电量统计和播放状态放到同一条时间线上。
| 指标 | 采集方式 | 看什么 | 解释边界 |
|---|---|---|---|
| 应用 CPU 时间 | /proc/<pid>/stat(pid 是进程 ID)、Perfetto sched、线上线程 CPU 采样 | offload 组的解码与写入线程 CPU 时间是否下降 | 不能和网络、埋点、歌词线程混在一起算 |
| 音频线程唤醒 | Perfetto 的 ftrace(内核跟踪)调度事件与 audio atrace(Android 跟踪标记)类别 | 应用写入线程、AAudio 回调和 AudioFlinger 线程的运行间隔 | 设备路由切换会改变线程形态 |
| CPU 频率与 idle | Perfetto 的 CPU frequency、idle(空闲状态)与 power 数据源 | 屏幕关闭后 CPU 的频率和空闲状态是否变化 | 受后台任务、网络和蓝牙影响 |
| 电量统计 | dumpsys batterystats、Battery Historian(电量统计可视化工具) | 播放窗口内按 UID(应用对应的 Linux 用户 ID)估算的耗电、WakeLock、网络和蓝牙活动 | 不是音频轨的直接电表读数,需要同机 A/B 对照 |
| 播放体验 | 播放中断率、seek 误差、underrun、用户手动重启播放 | 省电组是否引入可感知问题 | 体验指标必须和功耗指标一起看 |
实验组和对照组要使用同一设备、媒体、路由、音量、网络状态、屏幕状态和播放时长;Perfetto 的录制时长按场景设置,并在两组保持一致,我们只把它当本次场景的参数,不当通用标准。
这些 dumpsys 命令用于导出音频服务、AudioFlinger、MediaSession 和电量统计。batterystats --reset 会清除既有统计,只能在专用测试设备上执行:
adb shell dumpsys batterystats --reset
adb shell dumpsys audio > audio.txt
adb shell dumpsys media.audio_flinger > audio_flinger.txt
adb shell dumpsys media_session > media_session.txt
# 完成一次条件固定的播放后再导出
adb shell dumpsys batterystats > batterystats.txt
四份导出各答一问:dumpsys audio 看 AudioService 的策略与路由,media.audio_flinger 看输出线程和 Track 的状态,MediaSession 看播放器向系统公开了什么,batterystats 看播放窗口内的系统统计。Perfetto 另行按同一时钟和播放窗口录制调度、CPU frequency/idle、power、Binder、ActivityManager 和 audio 事件;播放器事件再补上请求模式、实际模式、underrun 与回退原因,这条时间线就完整了。
延迟、音质与兼容性代价
Offload 会把更多数据排入硬件 buffer,也会让部分处理从应用和框架侧移到硬件侧。省电收益来自这一步,代价也来自这一步。
| 代价 | 典型表现 | 处理策略 |
|---|---|---|
| seek / flush 精度差异 | 跳转后重复播放或少播放一段内容 | API 37 用能力查询和实际 flush 位置修正;旧版本按播放器实现选择重建 Track |
| 音效能力受限 | 均衡器、音量归一、空间音频、跳过静音不可用或退回 PCM | 产品能力优先时禁用 offload;省电优先时关闭相关效果 |
| 倍速播放受限 | 非 1.0 倍速时可能退出 offload,或受设备的 speed/pitch(速度/音调)能力与 fallback(参数不受支持时的回退策略)模式约束 | 倍速场景允许回退常规播放路径,单独观测耗电 |
| 蓝牙路由差异 | 某些耳机或车机上 offload 支持变化 | 按输出路由和设备类型分组做小流量验证 |
| HAL(硬件抽象层)差异 | 同一格式在不同 SoC(System on Chip,片上系统)上支持不同 | 不把单一机型结论外推,按设备能力表下发开关 |
| underrun 风险 | flush 后或数据补给慢时出现断续 | flush 成功后立即补数据,监控 underrun 和短写入 |
发布策略可以分成三档:
| 档位 | 条件 | 动作 |
|---|---|---|
| 可开启 | 长音频、屏幕关闭、格式支持、无倍速 / 跳过静音、目标设备 A/B 通过 | 默认启用,保留远程关闭 |
| 小流量开启 | 设备支持但功能组合复杂,例如蓝牙、gapless、章节 seek、广告插入 | 按路由和格式分组验证 |
| 禁用 | 短音效、实时互动、倍速刚需、空间音频刚需、目标设备异常率高 | 保持 PCM / low latency 路径 |
复杂功能组合应先按对应路由和格式完成同机 A/B;未满足体验与功耗阈值的组合继续使用 PCM 或低延迟路径。
Assistant 与后台音频的版本交叉
Android 17 引入 USAGE_ASSISTANT 专用音量流,Assistant 的回复音量可以和标准媒体音量分开。MODE_ASSISTANT_CONVERSATION 可以提示系统当前处于 Assistant 会话,从而改善 Assistant 音量流的控制一致性,尤其是播放前后和蓝牙外设参与时。
它解决的只是音量控制归属这一层;后台播放资格、音频是否走了 offload,我们分三条线看:
- 音量线:是否使用
USAGE_ASSISTANT,应用是否属于可使用MODE_ASSISTANT_CONVERSATION的 Assistant 集成。 - 后台线:退后台或锁屏后是否满足 Android 17 后台音频 hardening、FGS 与 WIU 规则,见本文前半部分。
- 省电线:长回复或长内容播放是否满足 offload 条件,短回复优先保证延迟和可打断性。
线上监控与远程关闭
Offload 接入必须能远程关闭并回到普通播放路径。播放器至少为每次播放会话记录一条汇总事件和若干状态变更事件,让线上数据回答四个问题:应用是否请求、系统是否满足、为何回退、体验是否变差。P90、P99 表示 90% 和 99% 的样本不超过对应值,用来观察少量高耗电会话。
| 字段 | 示例 | 用途 |
|---|---|---|
offload_requested | true / false | 区分实验组和对照组 |
offload_actual_mode | AAudio 实际 performance mode,或 Media3 的 offloaded / non-offloaded / unknown | 判断实际路径;PCM 也可能 offload,不能用 PCM 表示未启用 offload |
api_family | Media3 / AudioTrack / AAudio | 区分播放器入口 |
format | MIME、sample rate、channel count、bitrate | 分析格式支持差异 |
route | speaker、wired、Bluetooth、car | 分析输出设备差异 |
codec_provenance | API 37 返回字符串或空 | 辅助分析配置来源,不能当作 offload 证据 |
flush_support | 0 / FLUSH_WRITTEN_FRAMES_SUPPORTED | 统计设备能力 |
flush_result | actual frame、ERROR_BAD_VALUE、exception | 发现 seek / 切歌问题 |
fallback_reason | unsupported format、effect enabled、speed changed、route changed | 按回退原因控制开关 |
power_bucket | offload_on / offload_off / disabled_by_config | 标记实验分组并关联耗电 P90 / P99 |
远程开关建议按“功能组合”设计,单个布尔值不够用:
audio_offload_enabled:总开关,异常时一键关闭。audio_offload_bluetooth_enabled:蓝牙单独开关。audio_offload_gapless_required:音乐类应用单独控制。audio_offload_speed_policy:按设备的 speed-change offload 能力选择保留或退出 offload。audio_offload_flush_api37_enabled:API 37+ 精确 flush 单独做小流量验证。audio_offload_assistant_long_response_enabled:Assistant 长回复单独实验。
远程关闭条件要提前确定:后台播放中断率、seek 误差投诉、underrun 或手动重启播放比例上升,以及异常集中在单一设备族时,都应让对应组合回到普通播放路径;省电收益要建立在不损伤播放体验的前提上。
扩展:DSP Offload、Sound Dose 与 HAL 差异
DSP offload 改变音频数据经过的处理路径,音量安全、Sound Dose(累计声音暴露剂量)、路由和空间音频规则照样适用;从 getCodecProvenance() 或 offload 生效状态也推不出这些能力是否启用。验证时检查系统音量行为、路由切换、空间化状态和长时播放告警,出现异常后再查 AudioFlinger、audio policy(音频策略)与 HAL 日志。
设备清单可按 SoC、Android 构建、音频 HAL、输出路由、格式、DRM(数字版权管理)、蓝牙 codec 和空间音频状态组织。高通、联发科、Tensor 或 OEM(设备厂商)定制路径只是分组维度;结论只适用于已经完成同条件测试的组合。
Offload 小结
音频 Offload 的目标是降低长时间播放的耗电,低延迟场景应使用相应的实时音频路径。接入时我们把请求模式、实际模式、设备能力、播放器功能和用户体验放在同一套指标里:AAudio 用 AAudioStream_getPerformanceMode(stream) 确认实际模式;AudioTrack API 37 用 support bitmask(支持位掩码,以二进制位表示能力)、实际 flush 位置和 codec provenance 辅助判断;Media3 用 AudioOffloadPreferences 表达偏好,再由平台决定是否满足。
上线前做同机 A/B,线上保留远程关闭能力。CPU 时间、线程唤醒、batterystats 和播放体验同时满足验收阈值后,Offload 才进入可发布状态。
全文小结
后台音频先要证明这次播放具备可见页面、合格前台服务和必要的 WIU 条件,再选择普通混音、低延迟或 Offload 路径。前一层决定系统是否允许持续发声,后一层决定如何在时延、功能和功耗之间取舍;任何一层失败,都要同步停止无效的网络、解码、WakeLock 与服务状态。发布验证应把入口、MediaSession、FGS、实际音频路径、路由和单位会话功耗放到同一条时间线上。
参考资料
- Background audio hardening | Android Developers
- Behavior changes: Apps targeting Android 17 or higher | Android Developers
- Background playback with a MediaSessionService | Android Developers
- Manage audio focus | Android Developers
- Battery consumption | Android media | Android Developers
HardeningEnforcer.java| AOSPandroid-17.0.0_r1AudioManagerShellCommand.java| AOSPandroid-17.0.0_r1AudioManager.java| AOSPandroid-17.0.0_r1AudioService.java| AOSPandroid-17.0.0_r1MediaFocusControl.java| AOSPandroid-17.0.0_r1Tracks.cpp| AOSPandroid-17.0.0_r1LeAudioService.java| AOSPandroid-17.0.0_r1codec_manager.cc| AOSPandroid-17.0.0_r1- Perfetto | Android Developers
- dumpsys | Android Developers
- Android NDK AAudio reference
- AudioTrack API reference
AAudio.h| AOSPandroid-17.0.0_r1AudioTrack.java| AOSPandroid-17.0.0_r1- Android 17 CDD:Audio Offload
- Android 17 features: Dedicated Assistant volume stream
- Media3 ExoPlayer battery consumption
- Media3 ExoPlayer track selection
- Media3
ExoPlayer.AudioOffloadListener