一、问题背景

从事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 mLockMutex::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 delayif (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 bufferIPCThreadState::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 runningif (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 nowsq->push(FastMixerStateQueue::BLOCK_UNTIL_ACKED);if (kUseFastMixer == FastMixer_Dynamic) {mNormalSink = mOutputSink;}
#ifdef AUDIO_WATCHDOGif (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 problemif(!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都欠缺。努力!

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

  1. 嘿嘿这个好玩---新型Android手机病毒现身 可对通话过程录音

    据美国科技杂志<网络世界>(Network World)网络版报道,一种针对谷歌Android智能手机的新型病毒已经现身,它能在用户毫不知情状态下对通话过程进行录音.安全专家指出,此类病毒 ...

  2. mysql安装教程博音网_RTSP视频平台EasyNVR使用mysql数据源启动报错unknow drivermysql优化...

    原标题:RTSP视频平台EasyNVR使用mysql数据源启动报错unknow driver"mysql"优化 我们上一篇讲了TSINGSEE青犀视频开发的视频平台默认都是使用的s ...

  3. 老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 1...

    老李推荐: 第8章4节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-启动AndroidDebugBridge 上一节我们看到在启动AndroidDebugBri ...

  4. MTK驱动(53)---平台DTS文件匹配过程

    MTK平台DTS文件匹配过程 1. lk中platform paramter lk阶段会从boot image 解析出dtb数据,然后通过atag方式将dtb数据传递给了内核.    在mtk平台lk ...

  5. linux重启domino服务,在Unix平台中通过脚本来启动和停止Domino

    在Unix平台中通过脚本来启动和停止Domino和Domino控制器所用的参数 2008-12-1 14:28:18 :Lotus Domino 平台:UNIX, Solaris, Linux, AI ...

  6. android音频系统(7):通话过程中的音频输出设备切换

    前言:由于通话比较特殊,Android对于通话过程中音频输出设备的切换做了特殊处理,它在上层也是通过切换音频播放状态来完成切换操作的,android用CallAudioState来封装通话过程中的音频 ...

  7. 编写一个USB接口程序,模拟计算机启动过程和关闭过程启动过程中要加载鼠标、键盘、麦克风等USB设备,具体要求如下: (1)定义一个接口USB,包含两个抽象方法turnOn()he turnOff(),

    一.好物推荐 给大家推荐三款蓝牙耳机,下面的链接可以直接购买: 1.https://item.taobao.com/item.htm?ft=t&id=643733003968 2.https: ...

  8. switchresx卸载_SwitchResX for Mac使用过程启动问题解答

    SwitchResX for Mac是专为Mac用户设计的分辨率修改器软件,SwitchResX for Mac能够非常方便快速的帮助您更改Mac显示屏的分辨率,本次小编为您带来SwitchResX ...

  9. 问题:CDH平台Hbase的Master无法启动问题,Failed to create or set permission on staging directory

    CDH平台Hbase的Master无法启动问题,Failed to create or set permission on staging directory 问题: 自查: 解决: 问题: Mast ...

最新文章

  1. TCP/IP记一次关于IP地址和MAC物理地址的思考
  2. ImportError: libcublas.so.9.0: cannot open shared object file: No such file or directory
  3. 优酷土豆CEO:打造爆款产品的3个关键步骤
  4. WPF特效-实现弧形旋转轮播图
  5. java工作笔记019---java8新特性判断非null
  6. oracle中入库判断空串,不同数据库和SpringDataJPA对字段值null,''空值的判断
  7. stack-based buffer overflow basic paper
  8. 联想官方一键关闭Win10Defender、关闭Win10自动更新工具
  9. tfs管理java代码_TFS2010 版本控制权限设置
  10. 第一个Python爬虫-抓取煎蛋网上图片
  11. Domino多瑙河EAP3以及Nomad Web 1.0.5
  12. MySQL5.7修改默认root密码
  13. Google-Hacking语法总结
  14. 牛客网 HJ55 挑7
  15. 深入理解计算机系统2——信息表示和处理
  16. 运行python的两种方式磁盘式_day03-python-学习笔记
  17. 简单算法之丢手绢游戏/c++
  18. Caused by: java.sql.SQLException: Access denied for user ‘root‘@‘localhost‘ (using password: YES)
  19. JAVA毕业设计健康医疗预约系统计算机源码+lw文档+系统+调试部署+数据库
  20. Doris(原Palo)简介

热门文章

  1. 大学物理·第5章【静电场】
  2. ABP VNext学习日记7
  3. PMP到底是啥,你竟然还不知道?
  4. 技术男眼中的“TD式创新”:陈年旧账应该这么算
  5. WEB前端之ELEMENT-TABLE
  6. Matlab - 复数
  7. 装备制造业解决方案,实现供应链的产供销,智能化、协同化、平台化
  8. wpf的path画三角形、四边形
  9. 财路网每日原创推送: 新华网:十字路口的区块链
  10. 《计算机网络--自顶向下方法》第二章--应用层