分类:android 316人阅读 评论(0) 收藏 举报 对音频系统的探索起源于工作中遇到的一个bug。平时都是力求快速解决问题,不问原因。这次时间比较宽裕,正好借着解决问题的机会,把Android的音频系统了解一下。既然由bug引发,那就从bug开始说。


一. bug现象


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

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

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

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



二. 问题简化


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

[java] view plain copy
  1. protectedvoidonCreate(BundlesavedInstanceState){
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_test_sound_pool);
  4. mSoundPool=newSoundPool(10,AudioManager.STREAM_SYSTEM,5);
  5. mSoundId=mSoundPool.load(this,R.raw.camera_click,1);//这里R.raw.camera_click是ogg格式的音频资源
  6. vBtnShut=(Button)findViewById(R.id.btn_click);
  7. vBtnShut.setOnClickListener(newOnClickListener(){
  8. @Override
  9. publicvoidonClick(Viewv){
  10. mSoundPool.play(mSoundId,1,1,0,0,1);
  11. }
  12. });
  13. }
[java] view plain copy
  1. protectedvoidonCreate(BundlesavedInstanceState){
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_test_sound_pool);
  4. mSoundPool=newSoundPool(10,AudioManager.STREAM_SYSTEM,5);
  5. mSoundId=mSoundPool.load(this,R.raw.camera_click,1);//这里R.raw.camera_click是ogg格式的音频资源
  6. vBtnShut=(Button)findViewById(R.id.btn_click);
  7. vBtnShut.setOnClickListener(newOnClickListener(){
  8. @Override
  9. publicvoidonClick(Viewv){
  10. mSoundPool.play(mSoundId,1,1,0,0,1);
  11. }
  12. });
  13. }
结果表明,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
  1. /**
  2. *TheSoundPoolclassmanagesandplaysaudioresourcesforapplications.
  3. *
  4. *<p>ASoundPoolisacollectionofsamplesthatcanbeloadedintomemory
  5. *fromaresourceinsidetheAPKorfromafileinthefilesystem.The
  6. *SoundPoollibraryusestheMediaPlayerservicetodecodetheaudio
  7. *intoaraw16-bitPCMmonoorstereostream.Thisallowsapplications
  8. *toshipwithcompressedstreamswithouthavingtosuffertheCPUload
  9. *andlatencyofdecompressingduringplayback.</p>
  10. ......
  11. ......
  12. */
[cpp] view plain copy
  1. /**
  2. *TheSoundPoolclassmanagesandplaysaudioresourcesforapplications.
  3. *
  4. *<p>ASoundPoolisacollectionofsamplesthatcanbeloadedintomemory
  5. *fromaresourceinsidetheAPKorfromafileinthefilesystem.The
  6. *SoundPoollibraryusestheMediaPlayerservicetodecodetheaudio
  7. *intoaraw16-bitPCMmonoorstereostream.Thisallowsapplications
  8. *toshipwithcompressedstreamswithouthavingtosuffertheCPUload
  9. *andlatencyofdecompressingduringplayback.</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
  1. intSoundPool::load(intfd,int64_toffset,int64_tlength,intpriority)
  2. {
  3. ALOGV("load:fd=%d,offset=%lld,length=%lld,priority=%d",
  4. fd,offset,length,priority);
  5. Mutex::Autolocklock(&mLock);
  6. sp<Sample>sample=newSample(++mNextSampleID,fd,offset,length);
  7. mSamples.add(sample->sampleID(),sample);//将sample对象加入管理
  8. doLoad(sample);//load所在
  9. returnsample->sampleID();
  10. }
[cpp] view plain copy
  1. intSoundPool::load(intfd,int64_toffset,int64_tlength,intpriority)
  2. {
  3. ALOGV("load:fd=%d,offset=%lld,length=%lld,priority=%d",
  4. fd,offset,length,priority);
  5. Mutex::Autolocklock(&mLock);
  6. sp<Sample>sample=newSample(++mNextSampleID,fd,offset,length);
  7. mSamples.add(sample->sampleID(),sample);//将sample对象加入管理
  8. doLoad(sample);//load所在
  9. returnsample->sampleID();
  10. }

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

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

看到了mDecodeThread,眼前一亮,很可能这里就是将ogg解码成PCM的地方了。所以进入loadSample看一看:

[cpp] view plain copy
  1. voidSoundPoolThread::loadSample(intsampleID){
  2. write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE,sampleID));
  3. }
[cpp] view plain copy
  1. voidSoundPoolThread::loadSample(intsampleID){
  2. write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE,sampleID));
  3. }

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

[cpp] view plain copy
  1. intSoundPoolThread::run(){
  2. ALOGV("run");
  3. for(;;){
  4. SoundPoolMsgmsg=read();
  5. ALOGV("Gotmessagem=%d,mData=%d",msg.mMessageType,msg.mData);
  6. switch(msg.mMessageType){
  7. caseSoundPoolMsg::KILL:
  8. ALOGV("goodbye");
  9. returnNO_ERROR;
  10. caseSoundPoolMsg::LOAD_SAMPLE://在这里处理LOAD_SAMPLE
  11. doLoadSample(msg.mData);
  12. break;
  13. default:
  14. ALOGW("run:Unrecognizedmessage%d\n",
  15. msg.mMessageType);
  16. break;
  17. }
  18. }
  19. }
[cpp] view plain copy
  1. intSoundPoolThread::run(){
  2. ALOGV("run");
  3. for(;;){
  4. SoundPoolMsgmsg=read();
  5. ALOGV("Gotmessagem=%d,mData=%d",msg.mMessageType,msg.mData);
  6. switch(msg.mMessageType){
  7. caseSoundPoolMsg::KILL:
  8. ALOGV("goodbye");
  9. returnNO_ERROR;
  10. caseSoundPoolMsg::LOAD_SAMPLE://在这里处理LOAD_SAMPLE
  11. doLoadSample(msg.mData);
  12. break;
  13. default:
  14. ALOGW("run:Unrecognizedmessage%d\n",
  15. msg.mMessageType);
  16. break;
  17. }
  18. }
  19. }
[cpp] view plain copy
  1. voidSoundPoolThread::doLoadSample(intsampleID){
  2. sp<Sample>sample=mSoundPool->findSample(sampleID);
  3. status_tstatus=-1;
  4. if(sample!=0){
  5. status=sample->doLoad();
  6. }
  7. mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED,sampleID,status));
  8. }
[cpp] view plain copy
  1. voidSoundPoolThread::doLoadSample(intsampleID){
  2. sp<Sample>sample=mSoundPool->findSample(sampleID);
  3. status_tstatus=-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
  1. status_tSample::doLoad()
  2. {
  3. uint32_tsampleRate;
  4. intnumChannels;
  5. audio_format_tformat;
  6. sp<IMemory>p;
  7. ALOGV("Startdecode");
  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("Unabletoloadsample:%s",mUrl);
  18. return-1;
  19. }
  20. ALOGV("pointer=%p,size=%u,sampleRate=%u,numChannels=%d",
  21. p->pointer(),p->size(),sampleRate,numChannels);
  22. if(sampleRate>kMaxSampleRate){
  23. ALOGE("Samplerate(%u)outofrange",sampleRate);
  24. return-1;
  25. }
  26. if((numChannels<1)||(numChannels>2)){
  27. ALOGE("Samplechannelcount(%d)outofrange",numChannels);
  28. return-1;
  29. }
  30. //_dumpBuffer(p->pointer(),p->size());
  31. uint8_t*q=static_cast<uint8_t*>(p->pointer())+p->size()-10;
  32. //_dumpBuffer(q,10,10,false);
  33. mData=p;
  34. mSize=p->size();
  35. mSampleRate=sampleRate;
  36. mNumChannels=numChannels;
  37. mFormat=format;
  38. mState=READY;
  39. return0;
  40. }
[cpp] view plain copy
  1. status_tSample::doLoad()
  2. {
  3. uint32_tsampleRate;
  4. intnumChannels;
  5. audio_format_tformat;
  6. sp<IMemory>p;
  7. ALOGV("Startdecode");
  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("Unabletoloadsample:%s",mUrl);
  18. return-1;
  19. }
  20. ALOGV("pointer=%p,size=%u,sampleRate=%u,numChannels=%d",
  21. p->pointer(),p->size(),sampleRate,numChannels);
  22. if(sampleRate>kMaxSampleRate){
  23. ALOGE("Samplerate(%u)outofrange",sampleRate);
  24. return-1;
  25. }
  26. if((numChannels<1)||(numChannels>2)){
  27. ALOGE("Samplechannelcount(%d)outofrange",numChannels);
  28. return-1;
  29. }
  30. //_dumpBuffer(p->pointer(),p->size());
  31. uint8_t*q=static_cast<uint8_t*>(p->pointer())+p->size()-10;
  32. //_dumpBuffer(q,10,10,false);
  33. mData=p;
  34. mSize=p->size();
  35. mSampleRate=sampleRate;
  36. mNumChannels=numChannels;
  37. mFormat=format;
  38. mState=READY;
  39. return0;
  40. }


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

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

[cpp] view plain copy
  1. intSoundPool::play(intsampleID,floatleftVolume,floatrightVolume,
  2. intpriority,intloop,floatrate)
  3. {
  4. //...
  5. channel=allocateChannel_l(priority);
  6. //...
  7. channel->play(sample,channelID,leftVolume,rightVolume,priority,loop,rate);
  8. //...
  9. }
[cpp] view plain copy
  1. intSoundPool::play(intsampleID,floatleftVolume,floatrightVolume,
  2. intpriority,intloop,floatrate)
  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
  1. voidSoundChannel::play(constsp<Sample>&sample,intnextChannelID,floatleftVolume,
  2. floatrightVolume,intpriority,intloop,floatrate)
  3. {
  4. AudioTrack*newTrack;
  5. //....
  6. newTrack=newAudioTrack(streamType,sampleRate,sample->format(),
  7. channels,frameCount,AUDIO_OUTPUT_FLAG_FAST,callback,userData,bufferFrames);
  8. //...
  9. mState=PLAYING;
  10. mAudioTrack->start();
  11. //...
  12. }
[cpp] view plain copy
  1. voidSoundChannel::play(constsp<Sample>&sample,intnextChannelID,floatleftVolume,
  2. floatrightVolume,intpriority,intloop,floatrate)
  3. {
  4. AudioTrack*newTrack;
  5. //....
  6. newTrack=newAudioTrack(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
  1. AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface*clientInterface)…
  2. {
  3. //......
  4. for(size_ti=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. }
[cpp] view plain copy
  1. AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface*clientInterface)…
  2. {
  3. //......
  4. for(size_ti=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
  1. rc=mpAudioPolicyDev->create_audio_policy(mpAudioPolicyDev,&aps_ops,this,&mpAudioPolicy);
[cpp] view plain copy
  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
  1. staticaudio_module_handle_taps_load_hw_module(void*service,constchar*name)
  2. {
  3. sp<IAudioFlinger>af=AudioSystem::get_audio_flinger();
  4. returnaf->loadHwModule(name);
  5. }
[cpp] view plain copy
  1. staticaudio_module_handle_taps_load_hw_module(void*service,constchar*name)
  2. {
  3. sp<IAudioFlinger>af=AudioSystem::get_audio_flinger();
  4. returnaf->loadHwModule(name);
  5. }

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


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



4. AudioHardware


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

[cpp] view plain copy
  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. #ifdefDEBUG_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("------------>openfilesuccess");
  12. #endif
  13. }
  14. ssize_tAudioHardware::AudioStreamOutALSA::write(constvoid*buffer,size_tbytes)
  15. {
  16. //...
  17. #ifdefDEBUG_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. }
[cpp] view plain copy
  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. #ifdefDEBUG_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("------------>openfilesuccess");
  12. #endif
  13. }
  14. ssize_tAudioHardware::AudioStreamOutALSA::write(constvoid*buffer,size_tbytes)
  15. {
  16. //...
  17. #ifdefDEBUG_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
  1. status_tAudioHardware::AudioStreamOutALSA::open_l()
  2. {
  3. //...
  4. mPcm=mHardware->openPcmOut_l();
  5. if(mPcm==NULL){
  6. returnNO_INIT;
  7. }
  8. //...
  9. }
  10. structpcm*AudioHardware::openPcmOut_l()
  11. {
  12. //...
  13. mPcm=pcm_open(flags);
  14. //...
  15. if(!pcm_ready(mPcm)){
  16. pcm_close(mPcm);
  17. //...
  18. }
  19. }
  20. returnmPcm;
  21. }
[cpp] view plain copy
  1. status_tAudioHardware::AudioStreamOutALSA::open_l()
  2. {
  3. //...
  4. mPcm=mHardware->openPcmOut_l();
  5. if(mPcm==NULL){
  6. returnNO_INIT;
  7. }
  8. //...
  9. }
  10. structpcm*AudioHardware::openPcmOut_l()
  11. {
  12. //...
  13. mPcm=pcm_open(flags);
  14. //...
  15. if(!pcm_ready(mPcm)){
  16. pcm_close(mPcm);
  17. //...
  18. }
  19. }
  20. returnmPcm;
  21. }
open_l中调用了AudioHardware::openPcmOut_l,AudioHardware::openPcmOut_l中调用了pcm_open。再到pcm_open 中去看看:

[cpp] view plain copy
  1. structpcm*pcm_open(unsignedflags)
  2. {
  3. //......
  4. if(flags&PCM_IN){
  5. dname="/dev/snd/pcmC0D0c";
  6. channalFlags=-1;
  7. startCheckCount=0;
  8. }else{
  9. #ifdefSUPPORT_USB
  10. dname="/dev/snd/pcmC1D0p";
  11. #else
  12. dname="/dev/snd/pcmC0D0p";
  13. #endif
  14. }
  15. pcm->fd=open(dname,O_RDWR);
  16. if(pcm->fd<0){
  17. oops(pcm,errno,"cannotopendevice'%s'",dname);
  18. returnpcm;
  19. }
  20. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_INFO,&info)){
  21. oops(pcm,errno,"cannotgetinfo-%s",dname);
  22. gotofail;
  23. }
  24. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_HW_PARAMS,&params)){
  25. oops(pcm,errno,"cannotsethwparams");
  26. gotofail;
  27. }
  28. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_SW_PARAMS,&sparams)){
  29. oops(pcm,errno,"cannotsetswparams");
  30. gotofail;
  31. }
  32. fail:
  33. close(pcm->fd);
  34. pcm->fd=-1;
  35. returnpcm;
  36. }
[cpp] view plain copy
  1. structpcm*pcm_open(unsignedflags)
  2. {
  3. //......
  4. if(flags&PCM_IN){
  5. dname="/dev/snd/pcmC0D0c";
  6. channalFlags=-1;
  7. startCheckCount=0;
  8. }else{
  9. #ifdefSUPPORT_USB
  10. dname="/dev/snd/pcmC1D0p";
  11. #else
  12. dname="/dev/snd/pcmC0D0p";
  13. #endif
  14. }
  15. pcm->fd=open(dname,O_RDWR);
  16. if(pcm->fd<0){
  17. oops(pcm,errno,"cannotopendevice'%s'",dname);
  18. returnpcm;
  19. }
  20. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_INFO,&info)){
  21. oops(pcm,errno,"cannotgetinfo-%s",dname);
  22. gotofail;
  23. }
  24. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_HW_PARAMS,&params)){
  25. oops(pcm,errno,"cannotsethwparams");
  26. gotofail;
  27. }
  28. if(ioctl(pcm->fd,SNDRV_PCM_IOCTL_SW_PARAMS,&sparams)){
  29. oops(pcm,errno,"cannotsetswparams");
  30. gotofail;
  31. }
  32. fail:
  33. close(pcm->fd);
  34. pcm->fd=-1;
  35. returnpcm;
  36. }

果然,这里就是操作设备节点的地方了。我们先在AudioStreamOutALSA的write中加打印信息,看看第一次播放和后续播放究竟有何不同。测试结果发现,每次播放不出声音的情况,都发生mStandby==true之后,这个时候做了一次打开音频设备的动作,但此时PCM数据是正确的。我们先来看看什么时候会导致mStandby==true。

[cpp] view plain copy
  1. <PREclass=cppname="code"><PREclass=cppname="code">status_tAudioHardware::AudioStreamOutALSA::standby()
  2. {
  3. doStandby_l();
  4. }
  5. voidAudioHardware::AudioStreamOutALSA::doStandby_l()
  6. {
  7. if(!mStandby)
  8. mStandby=true;
  9. close_l();
  10. }
  11. voidAudioHardware::AudioStreamOutALSA::close_l()
  12. {
  13. if(mPcm){
  14. mHardware->closePcmOut_l();
  15. mPcm=NULL;
  16. }
  17. }</PRE><BR><BR></PRE>
[cpp] view plain copy
  1. <divclass="dp-highlighterbg_cpp"><divclass="bar"><divclass="tools"><strong>[cpp]</strong><atarget=_blankclass="ViewSource"title="viewplain"href="http://blog.csdn.net/special_lin/article/details/12849637#">viewplain</a><atarget=_blankclass="CopyToClipboard"title="copy"href="http://blog.csdn.net/special_lin/article/details/12849637#">copy</a><atarget=_blankclass="PrintSource"title="print"href="http://blog.csdn.net/special_lin/article/details/12849637#">print</a><atarget=_blankclass="About"title="?"href="http://blog.csdn.net/special_lin/article/details/12849637#">?</a></div></div><olclass="dp-cpp"><liclass="alt"><span><span><PRE</span><spanclass="keyword">class</span><span>=cppname=</span><spanclass="string">"code"</span><span>>status_tAudioHardware::AudioStreamOutALSA::standby()</span></span></li><li><span>{</span></li><liclass="alt"><span>doStandby_l();</span></li><li><span>}</span></li><liclass="alt"><span></span></li><li><span></span><spanclass="keyword">void</span><span>AudioHardware::AudioStreamOutALSA::doStandby_l()</span></li><liclass="alt"><span>{</span></li><li><span></span></li><liclass="alt"><span></span><spanclass="keyword">if</span><span>(!mStandby)</span></li><li><span>mStandby=</span><spanclass="keyword">true</span><span>;</span></li><liclass="alt"><span>close_l();</span></li><li><span>}</span></li><liclass="alt"><span></span></li><li><span></span><spanclass="keyword">void</span><span>AudioHardware::AudioStreamOutALSA::close_l()</span></li><liclass="alt"><span>{</span></li><li><span></span><spanclass="keyword">if</span><span>(mPcm){</span></li><liclass="alt"><span>mHardware->closePcmOut_l();</span></li><li><span>mPcm=NULL;</span></li><liclass="alt"><span>}</span></li><li><span>}</PRE><BR><BR></span></li></ol></div><prestyle="DISPLAY:none"class="cpp"name="code"><divclass="dp-highlighterbg_cpp"><divclass="bar"><divclass="tools"><strong>[cpp]</strong><atarget=_blankclass="ViewSource"title="viewplain"href="http://blog.csdn.net/special_lin/article/details/12849637#">viewplain</a><atarget=_blankclass="CopyToClipboard"title="copy"href="http://blog.csdn.net/special_lin/article/details/12849637#">copy</a><atarget=_blankclass="PrintSource"title="print"href="http://blog.csdn.net/special_lin/article/details/12849637#">print</a><atarget=_blankclass="About"title="?"href="http://blog.csdn.net/special_lin/article/details/12849637#">?</a></div></div><olclass="dp-cpp"><liclass="alt"><span><span>status_tAudioHardware::AudioStreamOutALSA::standby()</span></span></li><li><span>{</span></li><liclass="alt"><span>doStandby_l();</span></li><li><span>}</span></li><liclass="alt"><span></span></li><li><span></span><spanclass="keyword">void</span><span>AudioHardware::AudioStreamOutALSA::doStandby_l()</span></li><liclass="alt"><span>{</span></li><li><span></span></li><liclass="alt"><span></span><spanclass="keyword">if</span><span>(!mStandby)</span></li><li><span>mStandby=</span><spanclass="keyword">true</span><span>;</span></li><liclass="alt"><span>close_l();</span></li><li><span>}</span></li><liclass="alt"><span></span></li><li><span></span><spanclass="keyword">void</span><span>AudioHardware::AudioStreamOutALSA::close_l()</span></li><liclass="alt"><span>{</span></li><li><span></span><spanclass="keyword">if</span><span>(mPcm){</span></li><liclass="alt"><span>mHardware->closePcmOut_l();</span></li><li><span>mPcm=NULL;</span></li><liclass="alt"><span>}</span></li><li><span>}</span></li></ol></div><prestyle="DISPLAY:none"class="cpp"name="code">status_tAudioHardware::AudioStreamOutALSA::standby()
  2. {
  3. doStandby_l();
  4. }
  5. voidAudioHardware::AudioStreamOutALSA::doStandby_l()
  6. {
  7. if(!mStandby)
  8. mStandby=true;
  9. close_l();
  10. }
  11. voidAudioHardware::AudioStreamOutALSA::close_l()
  12. {
  13. if(mPcm){
  14. mHardware->closePcmOut_l();
  15. mPcm=NULL;
  16. }
  17. }


   
   

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

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


[cpp] view plain copy
  1. voidAudioFlinger::PlaybackThread::threadLoop_standby()
  2. {
  3. ALOGV("Audiohardwareenteringstandby,mixer%p,suspendcount%d",this,mSuspended);
  4. mOutput->stream->common.standby(&mOutput->stream->common);
  5. }
  6. boolAudioFlinger::PlaybackThread::threadLoop()
  7. {
  8. //......
  9. while(!exitPending())
  10. <PREclass=cppname="code"><SPANstyle="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;//......}//......}
[cpp] view plain copy
  1. voidAudioFlinger::PlaybackThread::threadLoop_standby()
  2. {
  3. ALOGV("Audiohardwareenteringstandby,mixer%p,suspendcount%d",this,mSuspended);
  4. mOutput->stream->common.standby(&mOutput->stream->common);
  5. }
  6. boolAudioFlinger::PlaybackThread::threadLoop()
  7. {
  8. //......
  9. while(!exitPending())
  10. <divclass="dp-highlighterbg_cpp"><divclass="bar"><divclass="tools"><strong>[cpp]</strong><atarget=_blankclass="ViewSource"title="viewplain"href="http://blog.csdn.net/special_lin/article/details/12849637#">viewplain</a><atarget=_blankclass="CopyToClipboard"title="copy"href="http://blog.csdn.net/special_lin/article/details/12849637#">copy</a><atarget=_blankclass="PrintSource"title="print"href="http://blog.csdn.net/special_lin/article/details/12849637#">print</a><atarget=_blankclass="About"title="?"href="http://blog.csdn.net/special_lin/article/details/12849637#">?</a></div></div><olclass="dp-cpp"><liclass="alt"><span><span><SPANstyle=</span><spanclass="string">"FONT-FAMILY:Arial,Helvetica,sans-serif"</span><span>>{</SPAN></span></span></li></ol></div><prestyle="DISPLAY:none"class="cpp"name="code"><spanstyle="font-family:Arial,Helvetica,sans-serif;"><span>{</span></span>
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
  1. voidAudioFlinger::onFirstRef()
  2. {
  3. //......
  4. /*TODO:moveallthisworkintoanInit()function*/
  5. charval_str[PROPERTY_VALUE_MAX]={0};
  6. if(property_get("ro.audio.flinger_standbytime_ms",val_str,NULL)>=0){
  7. uint32_tint_val;
  8. if(1==sscanf(val_str,"%u",&int_val)){
  9. mStandbyTimeInNsecs=milliseconds(int_val);
  10. ALOGI("Using%umSecasstandbytime.",int_val);
  11. }else{
  12. mStandbyTimeInNsecs=kDefaultStandbyTimeInNsecs;
  13. ALOGI("Usingdefault%umSecasstandbytime.",
  14. (uint32_t)(mStandbyTimeInNsecs/1000000));
  15. }
  16. }
  17. mMode=AUDIO_MODE_NORMAL;
  18. }
[cpp] view plain copy
  1. voidAudioFlinger::onFirstRef()
  2. {
  3. //......
  4. /*TODO:moveallthisworkintoanInit()function*/
  5. charval_str[PROPERTY_VALUE_MAX]={0};
  6. if(property_get("ro.audio.flinger_standbytime_ms",val_str,NULL)>=0){
  7. uint32_tint_val;
  8. if(1==sscanf(val_str,"%u",&int_val)){
  9. mStandbyTimeInNsecs=milliseconds(int_val);
  10. ALOGI("Using%umSecasstandbytime.",int_val);
  11. }else{
  12. mStandbyTimeInNsecs=kDefaultStandbyTimeInNsecs;
  13. ALOGI("Usingdefault%umSecasstandbytime.",
  14. (uint32_t)(mStandbyTimeInNsecs/1000000));
  15. }
  16. }
  17. mMode=AUDIO_MODE_NORMAL;
  18. }

如果有ro.audio.flinger_standbytime_ms这个属性,就按这个属性值设定stand by的idle time(很可能是OEM代码),如果没有,取kDefaultStandbyTimeInNsecs的值。kDefaultStandbyTimeInNsecs是个常量,3s:

[cpp] view plain copy
  1. staticconstnsecs_tkDefaultStandbyTimeInNsecs=seconds(3);
[cpp] view plain copy
  1. staticconstnsecs_tkDefaultStandbyTimeInNsecs=seconds(3);



五. 结论及收获


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


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

原文转自:http://blog.csdn.net/special_lin/article/details/12849637

更多相关文章

  1. Android第一行代码学习笔记三----广播
  2. android截屏代码实现方法
  3. 《第一行代码》学习笔记一
  4. 从零开始--系统深入学习android(实践-让我们开始写代码-Android框
  5. [Android]从新旧API看android代码演进

随机推荐

  1. IJKPlayer 实现视频播放业务
  2. Android(安卓)pmem分析
  3. 编译Android(安卓)VNC Server
  4. Android(安卓)SDK下载项的说明
  5. Android(安卓)DataBinding 使用指南
  6. android aidl出现无法import
  7. Android开机画面修改
  8. android tabhost 图标
  9. android定时器Timer与TimerTask
  10. Android(安卓)OpenGL ES 读书笔记(1)