
Android 13 不操作一段时间后,喇叭会“啪”地一响
一、解决方法
修改 frameworks/av/services/audioflinger/Threads.cpp 中的 bool AudioFlinger::PlaybackThread::threadLoop()里的以下代码,防止不操作一段时间后,喇叭被关闭:
Index: frameworks/av/services/audioflinger/Threads.cpp
===================================================================
@@ -3862,7 +3862,7 @@
continue;
}
- if ((mActiveTracks.isEmpty() && systemTime() > mStandbyTimeNs) ||
+ if (/*(mActiveTracks.isEmpty() && systemTime() > mStandbyTimeNs) || */
isSuspended()) {
// put audio hardware into standby after short delay
if (shouldStandby_l()) {
二、问题原因
1、核心代码
Android AudioFlinger 中 PlaybackThread(回放线程)的核心循环 threadLoop() 中的以下代码的核心职责:当没有音频需要播放时,将音频硬件(HAL)置于待机状态,并将线程自身挂起(休眠),以节省 CPU 和电池功耗。
// frameworks/av/services/audioflinger/Threads.cpp 的 bool AudioFlinger::PlaybackThread::threadLoop()里的部分代码:
if ((mActiveTracks.isEmpty() && systemTime() > mStandbyTimeNs) ||
isSuspended()) {
// put audio hardware into standby after short delay
if (shouldStandby_l()) {
threadLoop_standby();
// This is where we go into standby
if (!mStandby) {
LOG_AUDIO_STATE();
mThreadMetrics.logEndInterval();
mThreadSnapshot.onEnd();
mStandby = true;
}
sendStatistics(false /* force */);
}
if (mActiveTracks.isEmpty() && mConfigEvents.isEmpty()) {
// we're about to wait, flush the binder command buffer
IPCThreadState::self()->flushCommands();
clearOutputTracks();
if (exitPending()) {
break;
}
releaseWakeLock_l();
// wait until we have something to do...
ALOGV("%s going to sleep", myName.string());
mWaitWorkCV.wait(mLock);
ALOGV("%s waking up", myName.string());
acquireWakeLock_l();
mMixerStatus = MIXER_IDLE;
mMixerStatusIgnoringFastTracks = MIXER_IDLE;
mBytesWritten = 0;
mBytesRemaining = 0;
checkSilentMode_l();
mStandbyTimeNs = systemTime() + mStandbyDelayNs;
mSleepTimeUs = mIdleSleepTimeUs;
if (mType == MIXER || mType == SPATIALIZER) {
sleepTimeShift = 0;
}
continue;
}
}
2、代码详解(参考智谱GLM-5)
上面这段代码是 Android AudioFlinger 中 PlaybackThread(回放线程)核心循环 threadLoop() 的一部分。它的核心职责是:当没有音频需要播放时,将音频硬件(HAL)置于待机状态,并将线程自身挂起(休眠),以节省 CPU 和电池功耗。 这里包含了一个非常经典的 “两级休眠”机制:第一级是让底层音频硬件 Standby,第二级是让 AudioFlinger 线程自己 Sleep。 下面为你逐块详细拆解这段代码:
1. 进入休眠的触发条件
if ((mActiveTracks.isEmpty() && systemTime() > mStandbyTimeNs) || isSuspended()) {mActiveTracks.isEmpty() && systemTime() > mStandbyTimeNs:当前没有活跃的音轨(没有音频在播放),并且当前系统时间已经超过了预设的待机时间(mStandbyTimeNs)。
- 设计意图:引入延时防抖。如果音轨刚被移除,不要立刻进入待机,而是等一小段时间(通常是几秒),防止用户快速切换歌曲或滑动视频时,音频设备频繁在休眠和唤醒之间切换(导致爆音或卡顿)。
isSuspended():线程被挂起。通常是因为音频策略发生了路由变化(比如拔插耳机),或者有高优先级的音频焦点事件(比如来电),强制要求该线程暂停工作。
2. 第一级休眠:硬件层面待机
if (shouldStandby_l()) {
threadLoop_standby();
// This is where we go into standby
if (!mStandby) {
LOG_AUDIO_STATE();
mThreadMetrics.logEndInterval();
mThreadSnapshot.onEnd();
mStandby = true;
}
sendStatistics(false /* force */);
}shouldStandby_l():再次确认是否真的需要待机(后缀_l表示调用时必须已持有mLock锁)。因为前面判断条件到执行到这里可能有微小的时序变化。threadLoop_standby():真正调用底层 Audio HAL 接口(如out->stream->standby()),通知底层音频硬件芯片关闭输出、降低功耗。if (!mStandby) { mStandby = true; }:状态切换保护。只在状态发生翻转(从活跃变为待机)的那一瞬间,去记录日志、上报 Metrics 统计数据。避免重复上报。sendStatistics(false):向系统发送音频相关的统计信息(比如本次播放了多长时间、消耗了多少字节等)。
3. 第二级休眠的判断条件
if (mActiveTracks.isEmpty() && mConfigEvents.isEmpty()) {硬件虽然待机了,但 线程本身还在死循环 while(threadLoop()) 空转,这会浪费 CPU。什么时候能让线程也睡着?
mActiveTracks.isEmpty():依然没有音轨。mConfigEvents.isEmpty():没有待处理的配置事件(比如音量调节、路由更改、静音切换等)。如果有配置事件,线程必须留下来处理它们,不能睡。
4. 睡眠前的收尾工作
// we're about to wait, flush the binder command buffer
IPCThreadState::self()->flushCommands();
clearOutputTracks();
if (exitPending()) {
break;
}flushCommands():非常关键的一行。AudioFlinger 线程同时也是 Binder 线程,负责接收来自上层(如 APP 的 AudioManager)的指令。在进入等待之前,必须清空 Binder 命令缓冲区。如果不这样做,如果此时刚好有 APP 通过 Binder 调用了该线程的方法,可能会导致死锁或 ANR。clearOutputTracks():清理输出流复制的音轨(用于混音后分流到其他设备,如蓝牙 A2DP)。既然都要睡了,这些临时通道就没用了。exitPending():检查 AudioFlinger 服务是否正在退出(比如系统关机或 AudioFlinger 崩溃重启)。如果是要退出,直接break跳出死循环,结束线程。
5. 第二级休眠:线程层面挂起
releaseWakeLock_l();
// wait until we have something to do...
ALOGV("%s going to sleep", myName.string());
mWaitWorkCV.wait(mLock);
ALOGV("%s waking up", myName.string());
acquireWakeLock_l();这是最核心的睡眠逻辑。
releaseWakeLock_l():释放 WakeLock(电源锁)。允许 CPU 进入深度休眠状态。mWaitWorkCV.wait(mLock):调用条件变量的wait方法。此操作会自动释放mLock互斥锁,并将当前线程挂起(阻塞),直到有其他线程调用mWaitWorkCV.signal()或broadcast()唤醒它。 当被唤醒时,wait会自动重新获取mLock。
- 谁会唤醒它? 通常是其他线程调用了
start(开始播放)、createAudioTrack,或者音频策略触发了配置更改(这些操作在修改完状态后都会去signal这个条件变量)。
acquireWakeLock_l():线程被唤醒了,说明有活儿干了。立刻重新获取 WakeLock,防止 CPU 在混音过程中睡着。
6. 唤醒后的状态重置
mMixerStatus = MIXER_IDLE;
mMixerStatusIgnoringFastTracks = MIXER_IDLE;
mBytesWritten = 0;
mBytesRemaining = 0;
checkSilentMode_l();
mStandbyTimeNs = systemTime() + mStandbyDelayNs;
mSleepTimeUs = mIdleSleepTimeUs;
if (mType == MIXER || mType == SPATIALIZER) {
sleepTimeShift = 0;
}
continue;
}线程醒来后,并没有立刻开始混音,而是先重置一堆状态,然后通过 continue 跳过本次循环剩余的代码,回到 threadLoop() 的开头重新评估当前状况。
- 混音状态清零:将混音器状态设为
IDLE,清空写入字节数统计。 checkSilentMode_l():检查当前是否处于静音模式(比如勿扰模式),如果是,接下来的混音逻辑可能会有不同优化。mStandbyTimeNs = systemTime() + mStandbyDelayNs:关键重置。重新设置硬件待机的倒计时。如果唤醒后发现是误唤醒(比如一个空事件),在接下来的循环里需要重新等待这么长时间才能再次让硬件 Standby。mSleepTimeUs/sleepTimeShift:重置线程的轮询睡眠时间(用于混音线程在没有数据时的短时间 usleep,区别于这里的深睡)。
总结流程图
这段代码可以用以下状态机来理解:
- 发现没活了 (没音轨 + 超时) ➡️
- 关停硬件 (HAL Standby,省电) ➡️
- 确认没后续任务 (没配置事件) ➡️
- 清理现场 (刷 Binder、清 Track) ➡️
- 释放电源锁 ➡️
- 线程深度睡眠 (
wait条件变量) ➡️ - (不知过了多久,被新任务唤醒) ➡️
- 拿回电源锁 & 重置计时器 (
continue回到循环顶部准备干活)
编辑于 2026-04-24 · 著作权归作者所有