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

一、问题背景

从事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都欠缺。努力!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值