Android音频系统探究——从SoundPool到AudioHardware

    对音频系统的探索起源于工作中遇到的一个bug。平时都是力求快速解决问题,不问原因。这次时间比较宽裕,正好借着解决问题的机会,把Android的音频系统了解一下。既然由bug引发,那就从bug开始说。


一. bug现象


    Android的照相机在拍照的时候会播放一个按键音。最近的一个MID项目(基于RK3188,Android 4.2)中,测试部门反馈,拍照时按键音播放异常情况如下:

    (1)进入应用程序以后,第一次拍照,没有按键音

    (2)连续拍照,有按键音

    (3)停止连拍,等待几秒钟后,再次拍照,又没有按键音



二. 问题简化


    看CameraApp代码可以知道,播放按键音使用了SoundPool类。做一个使用SoundPool播放声音的应用程序,界面上只有一个Button,点击后播放声音。这样就能确定这单纯是声音播放问题还是复合性问题。代码很简单:

[java] view plain copy print ?
  1. protected void onCreate(Bundle savedInstanceState) {  
  2.         super.onCreate(savedInstanceState);  
  3.         setContentView(R.layout.activity_test_sound_pool);  
  4.         mSoundPool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);  
  5.         mSoundId = mSoundPool.load(this, R.raw.camera_click, 1);   //这里R.raw.camera_click是ogg格式的音频资源  
  6.           
  7.         vBtnShut = (Button) findViewById(R.id.btn_click);  
  8.         vBtnShut.setOnClickListener(new OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View v) {  
  11.                 mSoundPool.play(mSoundId, 11001);  
  12.             }  
  13.         });  
  14.     }  
  15.       
    结果表明,BUG现象仍然是一样的。我们将BUG现象做一次简化:

    idle-->play failed-->idle-->play failed-->play success-->play success-->idle-->play failed-->...

    可以总结为,每间隔几秒钟后,第一次播放音频无声音输出。 




三. 初步分析

 

   理清了现象,简化了环境,我们可以开始分析问题了:

    显而易见的是,BUG非常规律,只有相隔几秒钟后的第一次播放才出现问题,与软件逻辑密切相关,可以排除硬件问题。本质上来讲,无论使用什么软件系统,声音播放的流程一般都是——用户指定要播放的声音数据,可能是文件,可能是Buffer;Audio系统对声音数据解码,可能采用软解码,也可能采用硬解码;将解码出来的数字音频信号传给功放设备,经过D/A转换后送到扬声器,声音就播放出来了。可以说,这个流程中的第一部分,是应用程序的行为;第二部分,是Android系统的职责;第三部分,是kernel中驱动的工作。应用程序的问题可以排除,现在要解决的疑问是,是解码程序出了问题,还是驱动程序出了问题?出现了什么情况,导致了idle后播放不出来?

    


四.  代码研究


1. Android Audio框架

    首先网络上找找资料,要搞清楚Android音频的框架层次结构,才容易定位问题。用图说明——

Android音频系统探究——从SoundPool到AudioHardware_第1张图片

    有了大致的概念,开始以SoundPool为入口,摸清播放流程。其中在每个层次中要了解两点:数据如何传递,播放的动作如何执行。 也就是沿着SoundPool.load()和Sound.play()顺藤摸瓜。


2. SoundPool和AudioFlinger

    SoundPool.java基本是个空壳,直接使用了Native接口,代码没什么可看的。不过可以先看下这个类的介绍,就在SoundPool.java的开头,整一页的英文注释。幸运的是,很快就找到了我们需要看的资料:

[cpp] view plain copy print ?
  1. /** 
  2.  * The SoundPool class manages and plays audio resources for applications. 
  3.  * 
  4.  * <p>A SoundPool is a collection of samples that can be loaded into memory 
  5.  * from a resource inside the APK or from a file in the file system. The 
  6.  * SoundPool library uses the MediaPlayer service to decode the audio 
  7.  * into a raw 16-bit PCM mono or stereo stream. This allows applications 
  8.  * to ship with compressed streams without having to suffer the CPU load 
  9.  * and latency of decompressing during playback.</p> 
  10. ... ... 
  11. ... ... 
  12. */  

     挑重要的说,SoundPool是Sample的集合,能把APK里的资源或者文件系统中的文件加载到内存中,使用MediaPlayer服务把音频解码成原始的16位PCM单声道或立体声数据流。好嘛,原来解码在这里就做了。还是看看代码实现吧,免得心里不踏实。


     不去理会Jni那些手续,直接看SoundPool.cpp。上面那个测试APK的代码,调用了SoundPool的load,play两个接口,就把声音播放出来了。load一次后,可多次播放,这两个接口之所以要分开,应该就是load做了解码。先看load的实现,为满足不同音频资源的需要,load被重载了,看其中一个就行了。

[cpp] view plain copy print ?
  1. int SoundPool::load(int fd, int64_t offset, int64_t length, int priority)  
  2. {  
  3.     ALOGV("load: fd=%d, offset=%lld, length=%lld, priority=%d",  
  4.             fd, offset, length, priority);  
  5.     Mutex::Autolock lock(&mLock);  
  6.     sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length);  
  7.     mSamples.add(sample->sampleID(), sample);   //将sample对象加入管理  
  8.     doLoad(sample);   //load所在  
  9.     return sample->sampleID();  
  10. }  

    数据处理角度来说,真正的load在doLoad中:

[cpp] view plain copy print ?
  1. void SoundPool::doLoad(sp<Sample>& sample)  
  2. {  
  3.     ALOGV("doLoad: loading sample sampleID=%d", sample->sampleID());  
  4.     sample->startLoad();   //只是改变了状态  
  5.     mDecodeThread->loadSample(sample->sampleID());  //真正加载的地方  
  6. }  

    看到了mDecodeThread,眼前一亮,很可能这里就是将ogg解码成PCM的地方了。所以进入loadSample看一看:
[cpp] view plain copy print ?
  1. void SoundPoolThread::loadSample(int sampleID) {  
  2.     write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));  
  3. }  

    只是消息传递而已,找到LOAD_SAMPLE消息处理的地方:

[cpp] view plain copy print ?
  1. int SoundPoolThread::run() {  
  2.     ALOGV("run");  
  3.     for (;;) {  
  4.         SoundPoolMsg msg = read();  
  5.         ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);  
  6.         switch (msg.mMessageType) {  
  7.         case SoundPoolMsg::KILL:  
  8.             ALOGV("goodbye");  
  9.             return NO_ERROR;  
  10.         case SoundPoolMsg::LOAD_SAMPLE:   //在这里处理LOAD_SAMPLE  
  11.             doLoadSample(msg.mData);  
  12.             break;  
  13.         default:  
  14.             ALOGW("run: Unrecognized message %d\n",  
  15.                     msg.mMessageType);  
  16.             break;  
  17.         }  
  18.     }  
  19. }  
[cpp] view plain copy print ?
  1. void SoundPoolThread::doLoadSample(int sampleID) {  
  2.     sp <Sample> sample = mSoundPool->findSample(sampleID);  
  3.     status_t status = -1;  
  4.     if (sample != 0) {  
  5.         status = sample->doLoad();  
  6.     }  
  7.     mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));  
  8. }  

    看来最后是在sample->doLoad()中做的处理。进去看看,颇有惊喜:

[cpp] view plain copy print ?
  1. status_t Sample::doLoad()  
  2. {  
  3.     uint32_t sampleRate;  
  4.     int numChannels;  
  5.     audio_format_t format;  
  6.     sp<IMemory> p;  
  7.     ALOGV("Start decode");  
  8.     if (mUrl) {  
  9.         p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format);  
  10.     } else {  
  11.         p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format);  
  12.         ALOGV("close(%d)", mFd);  
  13.         ::close(mFd);  
  14.         mFd = -1;  
  15.     }  
  16.     if (p == 0) {  
  17.         ALOGE("Unable to load sample: %s", mUrl);  
  18.         return -1;  
  19.     }  
  20.     ALOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d",  
  21.             p->pointer(), p->size(), sampleRate, numChannels);  
  22.   
  23.     if (sampleRate > kMaxSampleRate) {  
  24.        ALOGE("Sample rate (%u) out of range", sampleRate);  
  25.        return - 1;  
  26.     }  
  27.   
  28.     if ((numChannels < 1) || (numChannels > 2)) {  
  29.         ALOGE("Sample channel count (%d) out of range", numChannels);  
  30.         return - 1;  
  31.     }  
  32.   
  33.     //_dumpBuffer(p->pointer(), p->size());  
  34.     uint8_t* q = static_cast<uint8_t*>(p->pointer()) + p->size() - 10;  
  35.     //_dumpBuffer(q, 10, 10, false);  
  36.   
  37.     mData = p;  
  38.     mSize = p->size();  
  39.     mSampleRate = sampleRate;  
  40.     mNumChannels = numChannels;  
  41.     mFormat = format;  
  42.     mState = READY;  
  43.     return 0;  
  44. }  


    原来Sample请来了MediaPlayer帮其解码,并计算出了采样率和帧数。到这里数据已经准备好了。接下来我们就要看Framework能否把数据正确的传递给HAL,至于MediaPlayer是如何解码的我们先不研究。

    弄清楚SoundPool的Play做了什么,也就能找到HAL的代码了。下面看只看play中的关键代码:

[cpp] view plain copy print ?
  1. int SoundPool::play(int sampleID, float leftVolume, float rightVolume,  
  2.         int priority, int loop, float rate)  
  3. {  
  4.     //...  
  5.     channel = allocateChannel_l(priority);  
  6.     //...  
  7.     channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);  
  8.     //...  
  9. }  
    调用了SoundChannel的play.好读书而不求甚解,先把代码一路追下去,不作细究。
[cpp] view plain copy print ?
  1. void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,  
  2.         float rightVolume, int priority, int loop, float rate)  
  3. {  
  4.     AudioTrack* newTrack;  
  5.     //....  
  6.     newTrack = new AudioTrack(streamType, sampleRate, sample->format(),  
  7.                 channels, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,                bufferFrames);  
  8.     //...  
  9.     mState = PLAYING;  
  10.     mAudioTrack->start();  
  11.     //...  
  12. }  

    SoundChannel::play创建了一个AudioTrack对象,在AudioTrack的构造函数中,调用了set,set又调用了createTrack_l。createTrack_I中,通过IAudioFlinger创建了一个IAudioTrack。关于AudioTrack和AudioFlinger是为何物,两者如何交换音频数据,就说来话长了。而且有很多大大分析得很详细,就不赘述了。有几篇写得很好——

  • AudioTrack分析:http://www.cnblogs.com/innost/archive/2011/01/09/1931457.html
  • AudioFlinger分析:http://www.cnblogs.com/innost/archive/2011/01/15/1936425.html
  • AudioTrack如何与AudioFlinger交换数据: http://blog.chinaunix.net/uid-26533928-id-3052398.html


    阅读这些资料我们可以知道,Android Framework的音频子系统中,每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放。换言之,AudioFlinger是Audio系统的核心服务之一,起到了承上启下的衔接作用。

    我们现在已经让SoundPool牵线,抓到AudioFlinger这条大鱼。下面着重来看AudioFlinger如何向下调用AudioHardware的。



3. AudioFlinger与AudioHardware


    这里需要一点基础知识,先要了解Android的硬件抽象接口机制,才能理解AudioFlinger如何调用到AudioHardware,相关资料:

http://blog.csdn.net/myarrow/article/details/7175204

    因为对Audio系统一无所知,所以很惭愧用了反相的代码搜索,在hardware/xxx/audio目录下查找HAL_MODULE_INFO_SYM,然后反过来到framework找HAL_MODULE_INFO_SYM的id "AUDIO_HARDWARE_MODULE_ID",过程非常笨拙,不足为道。他山之石可以攻玉,看到一篇好文,借助其中的一段分析来完成对AudioFlinger和AudioHardware关联的分析。原文地址:http://blog.csdn.net/xuesen_lin/article/details/8805108  

    当AudioPolicyService构造时创建了一个AudioPolicyDevice(mpAudioPolicyDev)并由此打开一个AudioPolicy(mpAudioPolicy)——这个Policy默认情况下的实现是legacy_audio_policy::policy(数据类型audio_policy)。同时legacy_audio_policy还包含了一个AudioPolicyInterface成员变量,它会被初始化为一个AudioPolicyManagerDefault。AudioPolicyManagerDefault的父类,即AudioPolicyManagerBase,它的构造函数中调用了mpClientInterface->loadHwModule()。

[cpp] view plain copy print ?
  1. AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface*clientInterface)…  
  2. {     
  3.     //......  
  4.     for (size_t i = 0; i < mHwModules.size();i++) {  
  5.        mHwModules[i]->mHandle = mpClientInterface->loadHwModule(mHwModules[i]->mName);  
  6.         if(mHwModules[i]->mHandle == 0) {  
  7.             continue;  
  8.         }  
  9.     //......  
  10. }  

    很明显的mpClientInterface这个变量在AudioPolicyManagerBase构造函数中做了初始化,再回溯追踪,可以发现它的根源在AudioPolicyService的构造函数中,对应的代码语句如下:

[cpp] view plain copy print ?
  1. rc =mpAudioPolicyDev->create_audio_policy(mpAudioPolicyDev, &aps_ops, this, &mpAudioPolicy);        

    在这个场景下,函数create_audio_policy对应的是create_legacy_ap,并将传入的aps_ops组装到一个AudioPolicyCompatClient对象中,也就是mpClientInterface所指向的那个对象。
    换句话说,mpClientInterface->loadHwModule实际上调用的就是aps_ops->loadHwModule,即:

[cpp] view plain copy print ?
  1. static audio_module_handle_t  aps_load_hw_module(void*service,const char *name)  
  2. {  
  3.     sp<IAudioFlinger> af= AudioSystem::get_audio_flinger();  
  4.     …  
  5.     return af->loadHwModule(name);  
  6. }  

    AudioFlinger终于出现了,同样的情况也适用于mpClientInterface->openOutput,代码如下:
[cpp] view plain copy print ?
  1. static audio_io_handle_t  aps_open_output(…)  
  2. {  
  3.     sp<IAudioFlinger> af= AudioSystem::get_audio_flinger();  
  4.     …  
  5.     return  af->openOutput((audio_module_handle_t)0,pDevices, pSamplingRate, pFormat, pChannelMask,  
  6.                          pLatencyMs, flags);  
  7. }  


   现在前方就是AudioHardware了,终于打开了从APK到HAL的通路。



4. AudioHardware


    AudioHardware有两个内部类,AudioStreamOutALSA和AudioStreamInALSA,我们要解决的是声音播放的问题,看AudioStreamOutALSA即可。 AudioStreamOutALSA代码很清晰,很快找到了我们需要的代码,写PCM数据用的函数:

[cpp] view plain copy print ?
  1. AudioHardware::AudioStreamOutALSA::AudioStreamOutALSA() :  
  2.     mHardware(0), mPcm(0), mMixer(0), mRouteCtl(0),  
  3.     mStandby(true), mDevices(0), mChannels(AUDIO_HW_OUT_CHANNELS),  
  4.     mSampleRate(AUDIO_HW_OUT_SAMPLERATE), mBufferSize(AUDIO_HW_OUT_PERIOD_BYTES),  
  5.     mDriverOp(DRV_NONE), mStandbyCnt(0)  
  6. {  
  7. #ifdef DEBUG_ALSA_OUT  
  8.     if(alsa_out_fp== NULL)  
  9.         alsa_out_fp = fopen("/data/data/out.pcm","a+");  
  10.     if(alsa_out_fp)  
  11.         ALOGI("------------>openfile success");           
  12. #endif                                                           
  13. }  
  14. ssize_t AudioHardware::AudioStreamOutALSA::write(const void* buffer, size_t bytes)  
  15. {  
  16.     //...  
  17. #ifdef DEBUG_ALSA_OUT  
  18.     if(alsa_out_fp)  
  19.         fwrite(buffer,1,bytes,alsa_out_fp);  
  20. #endif   
  21.     //...  
  22.     if (mStandby) {  
  23.         open_l();   //重新open音频设备  
  24.        mStandby = false;  
  25.     }  
  26.     //...  
  27.     ret = pcm_write(mPcm,(void*) p, bytes);  
  28.     //...  
  29. }  

    这里提供了一个很容易验证PCM数据是否正确的方法,打开DEBUG_ALSA_OUT开关后,可以将PCM流保存到“/data/data/out.pcm”文件中。到了验证数据是否正确的时候了,打开这个编译开关,得到out.pcm,将它pull到PC上,用coolEdit打开播放,发现正常播放正常。好了,我们现在可以知道,问题并不出在解码程序上了。那又是什么原因导致的呢,我们从write函数开始,研究播放的流程。

    首先,AudioStreamOutALSA的构造函数中将mStandby初始化为true。这个变量显然是作为记录音频设备待机状态用的。当mStandby==true时,每次调用write,都会调用open_l()重新开启一次音频设备,然后再做pcm_write。

    再看看open_l():

[cpp] view plain copy print ?
  1. status_t AudioHardware::AudioStreamOutALSA::open_l()  
  2. {  
  3.     //...  
  4.     mPcm = mHardware->openPcmOut_l();  
  5.     if (mPcm == NULL) {  
  6.         return NO_INIT;  
  7.     }  
  8.     //...  
  9. }  
  10. struct pcm *AudioHardware::openPcmOut_l()  
  11. {  
  12.         //...  
  13.         mPcm = pcm_open(flags);  
  14.         //...  
  15.         if (!pcm_ready(mPcm)) {  
  16.             pcm_close(mPcm);  
  17.             //...  
  18.         }  
  19.     }  
  20.     return mPcm;  
  21. }  
open_l中调用了AudioHardware::openPcmOut_l,AudioHardware::openPcmOut_l中调用了pcm_open。再到pcm_open 中去看看:
[cpp] view plain copy print ?
  1. struct pcm *pcm_open(unsigned flags)  
  2. {  
  3.     //... ...  
  4.   
  5.     if (flags & PCM_IN) {  
  6.         dname = "/dev/snd/pcmC0D0c";  
  7.         channalFlags = -1;  
  8.         startCheckCount = 0;  
  9.     } else {  
  10. #ifdef SUPPORT_USB  
  11.         dname = "/dev/snd/pcmC1D0p";  
  12. #else  
  13.         dname = "/dev/snd/pcmC0D0p";  
  14. #endif  
  15.     }  
  16.   
  17.     pcm->fd = open(dname, O_RDWR);  
  18.     if (pcm->fd < 0) {  
  19.         oops(pcm, errno, "cannot open device '%s'", dname);  
  20.         return pcm;  
  21.     }  
  22.   
  23.     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {  
  24.         oops(pcm, errno, "cannot get info - %s", dname);  
  25.         goto fail;  
  26.     }  
  27.       
  28.     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {  
  29.         oops(pcm, errno, "cannot set hw params");  
  30.         goto fail;  
  31.     }  
  32.     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {  
  33.         oops(pcm, errno, "cannot set sw params");  
  34.         goto fail;  
  35.     }  
  36.   
  37. fail:  
  38.     close(pcm->fd);  
  39.     pcm->fd = -1;  
  40.     return pcm;  
  41. }  

    果然,这里就是操作设备节点的地方了。我们先在AudioStreamOutALSA的write中加打印信息,看看第一次播放和后续播放究竟有何不同。测试结果发现,每次播放不出声音的情况,都发生mStandby==true之后,这个时候做了一次打开音频设备的动作,但此时PCM数据是正确的。我们先来看看什么时候会导致mStandby==true。
[cpp] view plain copy print ?
  1. <pre name="code" class="cpp"><pre name="code" class="cpp">status_t AudioHardware::AudioStreamOutALSA::standby()  
  2. {  
  3.         doStandby_l();  
  4. }  
  5.   
  6. void AudioHardware::AudioStreamOutALSA::doStandby_l()  
  7. {  
  8.   
  9.     if(!mStandby)  
  10.         mStandby = true;  
  11.     close_l();  
  12. }  
  13.   
  14. void AudioHardware::AudioStreamOutALSA::close_l()  
  15. {  
  16.     if (mPcm) {  
  17.         mHardware->closePcmOut_l();  
  18.         mPcm = NULL;  
  19.     }  
  20. }</pre><br><br></pre>  

    好了,现在我们可以确定,mStandby是在调用standby的时候被设置生true了。如果不总是重新打开音频设备,会不会变正常?做了一个实验,把standby函数体的代码都注释掉。这样修改后,果然开机只有一次声音播放不出来,那就是第一次。每隔一段时间,声音就播不出来的问题不见了。

     其实到现在,问题已经定位出来了。这个问题属于kernel问题,不再属于Framework了。但是还是想弄清楚,standby为什么隔一段时间被调用一次,是被谁调用的。经过一系列反查,找到了standby的真正调用处,AudioFlinger的播放线程中。具体怎么查的,还是要参考HAL知识去,就不重复记载了。


[cpp] view plain copy print ?
  1. void AudioFlinger::PlaybackThread::threadLoop_standby()  
  2. {  
  3.     ALOGV("Audio hardware entering standby, mixer %p, suspend count %d"this, mSuspended);  
  4.     mOutput->stream->common.standby(&mOutput->stream->common);  
  5. }  
  6.   
  7. bool AudioFlinger::PlaybackThread::threadLoop()  
  8. {  
  9.     // ... ...  
  10.     while (!exitPending())  
  11. <pre name="code" class="cpp">   <span style="font-family:Arial,Helvetica,sans-serif">{</span></pre>      if (CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) ||                    isSuspended())) {            if (!mStandby) {                threadLoop_standby();                mStandby = true;            }//... ...}//... ...standbyTime = systemTime() + standbyDelay;//... ...}// ... ...}  

    这里我们看到了,standby是由AudioFlinger控制的,一旦满足以下条件后,没有AudioTrack处于活动状态并且已经到达了standbyTime这个时间就进入Standby模式。那么standbyTime=systemTime() + standbyDelay,也就是过了standbyDelay这段时间后,音频系统将进入待机,关闭音频设备。最后找到standbyDelay的值是多少。

   AudioFlinger::PlaybackThread构造函数中,将standbyDelay初始化,standbyDelay(AudioFlinger::mStandbyTimeInNsecs), 

   AudioFlinger这个类第一次被引用时,就对成员变量mStandbyTimeInNsecs 进行了初始化

[cpp] view plain copy print ?
  1. void AudioFlinger::onFirstRef()  
  2. {  
  3.     //... ...  
  4.     /* TODO: move all this work into an Init() function */  
  5.     char val_str[PROPERTY_VALUE_MAX] = { 0 };  
  6.     if (property_get("ro.audio.flinger_standbytime_ms", val_str, NULL) >= 0) {  
  7.         uint32_t int_val;  
  8.         if (1 == sscanf(val_str, "%u", &int_val)) {  
  9.             mStandbyTimeInNsecs = milliseconds(int_val);  
  10.             ALOGI("Using %u mSec as standby time.", int_val);  
  11.         } else {  
  12.             mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;  
  13.             ALOGI("Using default %u mSec as standby time.",  
  14.                     (uint32_t)(mStandbyTimeInNsecs / 1000000));  
  15.         }  
  16.     }  
  17.   
  18.     mMode = AUDIO_MODE_NORMAL;  
  19. }  

    如果有ro.audio.flinger_standbytime_ms这个属性,就按这个属性值设定stand by的idle time(很可能是OEM代码),如果没有,取kDefaultStandbyTimeInNsecs的值。kDefaultStandbyTimeInNsecs是个常量,3s:
[cpp] view plain copy print ?
  1. static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);  



五. 结论及收获


    通过分析研究Android系统代码,我们虽然最终没有解决问题,但是已经定位出了问题所在的层次,确定这是一个驱动的BUG。Framework工程师的任务至此完成了。问题交付给驱动工程师,经过排查发现,是PA没有打开造成的问题。


    经验可以带来技巧,如果下次遇到类似问题,我们可以直接在AudioHardware中截获PCM,通过判断解码出的PCM流是否正确,较快速的定位到问题所在——是MediaPlayer Codec、AudioSystem、还是Driver。


你可能感兴趣的:(Android音频系统探究——从SoundPool到AudioHardware)