您的位置:首页 > 其它

MSM8974平台功耗问题----通话过程启动Speaker导致功耗异常

2013-11-06 11:02 645 查看
一、问题背景

从事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文件中。


status_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;
}
最后调用到stopSpkrProcessing(),看这里做了什么。这里已经调到AudioSpeakerProtection.cpp

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都欠缺。努力!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息