Android 13 不操作一段时间后,喇叭会“啪”地一响

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,区别于这里的深睡)。
    总结流程图
    这段代码可以用以下状态机来理解:
  1. 发现没活了 (没音轨 + 超时) ➡️
  2. 关停硬件 (HAL Standby,省电) ➡️
  3. 确认没后续任务 (没配置事件) ➡️
  4. 清理现场 (刷 Binder、清 Track) ➡️
  5. 释放电源锁 ➡️
  6. 线程深度睡眠 (wait 条件变量) ➡️
  7. (不知过了多久,被新任务唤醒) ➡️
  8. 拿回电源锁 & 重置计时器 (continue 回到循环顶部准备干活)
编辑于 2026-04-24 · 著作权归作者所有