一、问题背景
从事Android功耗管理的开发人员都有这个感触:功耗涉及的问题有很多方面,有时候很难定位具体的模块或APP。但这次比较幸运,遇到的问题是这样的:
1. 正常make a call;
2. 通话过程中,打开免提,然后关掉免提;
3. 挂电话,等待系统睡眠。
最终导致系统睡眠后多出大概40mA的电流消耗。
这个问题从测试的步骤大致也可以分析,应该是audio某个PCM开启了,但是当speaker关闭时并没有关掉它。接下来重点看一下audio这一块的东西。
二、Audio play和capture的一些原理
声明:我本身并不是负责audio这一块,这里整理的东西也是请教我们team的大牛学来的,肯定有不少错误的地方,主要是为自己理解。读者可以自己去了解这部分内容。
目前关于audio的处理机制都是通过ALSA(advanced linux sound architecture),下面的一张图是AP和CODEC之间通过ALSA沟通的简单机制:
图1
其中ALSA架构是android本身就有的,底层driver基本都是器件厂商提供的,一般不会有bug。关键的地方在于HAL层对audio的一些操作以及ALSA lib的一些关键接口,这部分是平台厂商自己实现的,所以这是重点。
三、alsa_sound/ALSADevice.cpp
在平台厂商的此目录下,比如android/hardware/qcom/audio/alsa_sound/,包含了user space对audio的所有操作。
对ALSA device的管理操作在ALSADevice.cpp中,因为这里是通话过程中切换speaker导致的问题,来看一下具体操作的函数。
当具体开关speaker的时候,会执行ALSADevcie::switchDevice( )函数,关键代码为:
if (rxDevice != NULL) {
if ((strncmp(mCurRxUCMDevice, "None", 4)) &&
((mADSPState == ADSP_UP_AFTER_SSR) ||
(strncmp(rxDevice, mCurRxUCMDevice, MAX_STR_LEN)) || (inCallDevSwitch == true))) {
if ((use_case != NULL) && (strncmp(use_case, SND_USE_CASE_VERB_INACTIVE,
strlen(SND_USE_CASE_VERB_INACTIVE)))) {
usecase_type = getUseCaseType(use_case);
if (usecase_type & USECASE_TYPE_RX) {
ALOGD("Deroute use case %s type is %d\n", use_case, usecase_type);
strlcpy(useCaseNode.useCase, use_case, MAX_STR_LEN);
snd_use_case_set(handle->ucMgr, "_verb", SND_USE_CASE_VERB_INACTIVE);
mUseCaseList.push_front(useCaseNode);
}
}
if (mods_size) {
for(index = 0; index < mods_size; index++) {
usecase_type = getUseCaseType(mods_list[index]);
if (usecase_type & USECASE_TYPE_RX) {
ALOGD("Deroute use case %s type is %d\n", mods_list[index], usecase_type);
strlcpy(useCaseNode.useCase, mods_list[index], MAX_STR_LEN);
snd_use_case_set(handle->ucMgr, "_dismod", mods_list[index]);
mUseCaseList.push_back(useCaseNode);
}
}
}
snd_use_case_set(handle->ucMgr, "_disdev", mCurRxUCMDevice);
}
}
.......
ALOGD("%s,rxDev:%s, txDev:%s, curRxDev:%s, curTxDev:%s, devices=0x%x, current_verb=%s, use case: %s, type=%d, handle->usercase=%s\n", __FUNCTION__, rxDevice, txDevice, mCurRxUCMDevice, mCurTxUCMDevice, devices, handle->ucMgr->card_ctxt_ptr->current_verb, (use_case==NULL)?"null":use_case, usecase_type,(handle->useCase == NULL)? "null":handle->useCase);
if (rxDevice != NULL) {
snd_use_case_set(handle->ucMgr, "_enadev", rxDevice);
if (!strncmp(rxDevice, "Speaker", sizeof(mCurRxUCMDevice)) ||
!strncmp(rxDevice, "Speaker Protected", sizeof(mCurRxUCMDevice))) {
gettimeofday(&mSpkLastUsedTime, NULL);
mSpkrInUse = true;
if (mSpkrCalibrationDone && mSpkrProt) {
mSpkrProt->startSpkrProcessing();
}
}
strlcpy(mCurRxUCMDevice, rxDevice, sizeof(mCurRxUCMDevice));
}
if (txDevice != NULL) {
snd_use_case_set(handle->ucMgr, "_enadev", txDevice);
strlcpy(mCurTxUCMDevice, txDevice, sizeof(mCurTxUCMDevice));
}
rxDevice是在切换后的接收device。在 if (rxDevice != NULL)的判断中,rxDevice会与“Speaker”作比较,如果满足条件,mSpkrInUse标志位将设置为true。然后执行mSpkrProt->startSpkrProcessing(),这个函数就不继续分析了。
Speaker开启的过程是没有问题,接下来就是操作底层driver让speaker发声就行了。
四、关闭speaker
从上面的startSpkrProcessing()可以看出,关闭speaker的操作应该对应是stopSpkrProcessing(),从代码来看它是怎么调用的。
在一个thread loop中,如果某个audio output没有输出,就会进入standby。LINUX/android/frameworks/av/services/audioflinger/Threads.cpp下面的
AudioFlinger::PlaybackThread::threadLoop()可以很明显看出来。
while (!exitPending()) { cpuStats.sample(myName); Vector< sp<EffectChain> > effectChains; processConfigEvents(); { // scope for mLock Mutex::Autolock _l(mLock); if (logString != NULL) { mNBLogWriter->logTimestamp(); mNBLogWriter->log(logString); logString = NULL; } if (checkForNewParameters_l()) { cacheParameters_l(); } saveOutputTracks(); // put audio hardware into standby after short delay if (CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) || isSuspended())) { if (!mStandby) { threadLoop_standby(); mStandby = true; } if (!mActiveTracks.size() && 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; checkSilentMode_l(); standbyTime = systemTime() + standbyDelay; sleepTime = idleSleepTime; if (mType == MIXER) { sleepTimeShift = 0; } continue; } }
if (CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) || isSuspended()))且当前没有stand by,那么可以进入threadLoop_standby();这个if判断的条件,这里不细说了,从表面上也可以看得出来,应该是没有有效的音道或歌曲且系统时间大于standby时间(应该是standby设定的时间已经过了)或者device已经是suspended。
void AudioFlinger::MixerThread::threadLoop_standby() { // Idle the fast mixer if it's currently running if (mFastMixer != NULL) { FastMixerStateQueue *sq = mFastMixer->sq(); FastMixerState *state = sq->begin(); if (!(state->mCommand & FastMixerState::IDLE)) { state->mCommand = FastMixerState::COLD_IDLE; state->mColdFutexAddr = &mFastMixerFutex; state->mColdGen++; mFastMixerFutex = 0; sq->end(); // BLOCK_UNTIL_PUSHED would be insufficient, as we need it to stop doing I/O now sq->push(FastMixerStateQueue::BLOCK_UNTIL_ACKED); if (kUseFastMixer == FastMixer_Dynamic) { mNormalSink = mOutputSink; } #ifdef AUDIO_WATCHDOG if (mAudioWatchdog != 0) { mAudioWatchdog->pause(); } #endif } else { sq->end(false /*didModify*/); } } PlaybackThread::threadLoop_standby(); } // shared by MIXER and DIRECT, overridden by DUPLICATING void AudioFlinger::PlaybackThread::threadLoop_standby() { ALOGV("Audio hardware entering standby, mixer %p, suspend count %d", this, mSuspended); mOutput->stream->common.standby(&mOutput->stream->common); }
从上面的代码看,最终会调用standby(&mOutput->stream->common),这里会调用到ALSADevice::standby(alsa_handle_t *handle)中。
这个函数在ALSADevice.cpp文件中。
最后调用到stopSpkrProcessing(),看这里做了什么。这里已经调到AudioSpeakerProtection.cppstatus_t ALSADevice::standby(alsa_handle_t *handle) { int ret; status_t err = NO_ERROR; struct pcm *h = handle->rxHandle; handle->rxHandle = 0; ALOGD("standby: handle %p h %p", handle, h); if (h) { ALOGD("standby rxHandle\n"); err = pcm_close(h); if(err != NO_ERROR) { ALOGE("standby: pcm_close failed for rxHandle with err %d", err); } } h = handle->handle; handle->handle = 0; if (h) { ALOGD("standby handle h %p\n", h); err = pcm_close(h); if(err != NO_ERROR) { ALOGE("standby: pcm_close failed for handle with err %d", err); } disableDevice(handle); } if (mSpkrCalibrationDone && mSpkrProt) { mSpkrProt->stopSpkrProcessing(); } return err; }
void AudioSpeakerProtection::stopSpkrProcessing()
{
Mutex::Autolock autolock1(mMutexSpkrProt);
if (mSpkrProcessingState == SPKR_PROCESSING_IN_IDLE) {
ALOGV("Processing is not enabled in stop");
return;
}
unsigned long sec;
if (mALSADevice->isSpeakerinUse(sec)) {
ALOGV("spkr_prot_thread in spk use skip stop");
return;
}
for(ALSAHandleList::iterator it = mParent->mDeviceList.begin();
it != mParent->mDeviceList.end(); ++it) {
if(!strcmp(it->useCase, SND_USE_CASE_MOD_SPKR_PROT_TX) ||
!strcmp(it->useCase, SND_USE_CASE_VERB_SPKR_PROT_TX)) {
mALSADevice->close(&(*it));
mParent->mDeviceList.erase(it);
mSpkrProcessingState = SPKR_PROCESSING_IN_IDLE;
ALOGV("Spkr Processing Idle");
break;
}
}
}
在这个函数中,首先上个锁;然后判断speaker processing是不是已经idle了,如果已经idle了,那么没必要去stop了;然后判断speaker是不是还在用,如果在用,直接返回;最后如果前面都没有return,那么做一系列操作,将ALSA device对应的pcm关掉。
四、问题点以及解决方案
注意第二个if判断,如果speaker是在用,那么直接return!来看一下具体是怎么判断的。
bool ALSADevice::isSpeakerinUse(unsigned long &secs)
{
struct timeval usage;
gettimeofday(&usage, NULL);
if (mSpkrInUse) {
secs = 0;
return true;
} else {
secs = usage.tv_sec - mSpkLastUsedTime.tv_sec;
return false;
}
}
看这里!如果mSpkrInUse为true,那么 isSpeakerinUse ()也马上返回true,那么在stopSpkrProcessing()中直接return!那么PCM port就没办法关闭,最终的结果就是耗电!!!
那怎么改呢?回到ALSADevice::switchDevice(),再看这一代码段:
if (rxDevice != NULL) {
snd_use_case_set(handle->ucMgr, "_enadev", rxDevice);
if (!strncmp(rxDevice, "Speaker", sizeof(mCurRxUCMDevice)) ||
!strncmp(rxDevice, "Speaker Protected", sizeof(mCurRxUCMDevice))) {
gettimeofday(&mSpkLastUsedTime, NULL);
mSpkrInUse = true;
if (mSpkrCalibrationDone && mSpkrProt) {
mSpkrProt->startSpkrProcessing();
}
}
strlcpy(mCurRxUCMDevice, rxDevice, sizeof(mCurRxUCMDevice));
}
如果切换到的rxDevice是speaker,mSpkrInUse标志位设置为true;但是如果speaker关闭后,并没有将mSpkrInUse设置为false的操作。需要有一个判断,如果发生切换的时候,当前使用的device是speaker或speaker protected,也就是说speaker马上会被切换成其他device,那么就设置mSpkrInUse为false。
添加的代码如下:
ALOGD("%s,rxDev:%s, txDev:%s, curRxDev:%s, curTxDev:%s, devices=0x%x, current_verb=%s, use case: %s, type=%d, handle->usercase=%s\n", __FUNCTION__, rxDevice, txDevice, mCurRxUCMDevice, mCurTxUCMDevice, devices, handle->ucMgr->card_ctxt_ptr->current_verb, (use_case==NULL)?"null":use_case, usecase_type,(handle->useCase == NULL)? "null":handle->useCase);
//fix speaker use state peoblem.
//it should be put to not use state when not use it
// otherwise, it may cause "pcm 21" not be closed while system is in low power state
// and this will lead to power consumption problem
if(!strncmp(mCurRxUCMDevice, "Speaker", sizeof(mCurRxUCMDevice)) || !strncmp(mCurRxUCMDevice, "Speaker Protected", sizeof(mCurRxUCMDevice)))
{
gettimeofday(&mSpkLastUsedTime, NULL);
mSpkrInUse = false;
}
//fix speaker use state peoblem.
if (rxDevice != NULL) {
snd_use_case_set(handle->ucMgr, "_enadev", rxDevice);
if (!strncmp(rxDevice, "Speaker", sizeof(mCurRxUCMDevice)) ||
!strncmp(rxDevice, "Speaker Protected", sizeof(mCurRxUCMDevice))) {
gettimeofday(&mSpkLastUsedTime, NULL);
mSpkrInUse = true;
if (mSpkrCalibrationDone && mSpkrProt) {
mSpkrProt->startSpkrProcessing();
}
}
strlcpy(mCurRxUCMDevice, rxDevice, sizeof(mCurRxUCMDevice));
}
这里就能达到预期的效果了。改过的代码可以通过adb看一下有没有生效。 五、总结
没什么好总结的,了解的太少,看code和写code都欠缺。努力!