【问题概要】

    上一次我介绍了一种 Android 系统下发生音频 underrun 问题的解决方法(参见《记一次Android系统下解决音频UnderRun问题的过程》),这之后平静了一段时间,测试组同事也没有再报告相关的噪声问题。

由于从 Android 6.0 开始已经支持应用多开以及多窗口的功能,所以她们先在后台运行了一个程序(比如 阴阳师、全民飞机大战 这样的游戏),再在前台播放视频,于是噪声大量出现了。上次问题的情况比较简单,出现噪声时 framesReady 的值与 framesDesired 的值始终相差 2。所以我通过在 Android 原有处理 underrun 问题的方法的延时基础上增加 3 毫秒,解决了问题。但这次的问题,我们从 Log 中可以看到差值分布范围广,使用固定的延时时间已经无法消除噪声。

    在 Log 中搜索包含“underrun”关键字的内容可以看到如下记录:

 

03-07 18:44:04.290  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(407) < framesDesired(516)
03-07 18:44:04.470  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(31) < framesDesired(516)
03-07 18:44:04.570  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(47) < framesDesired(516)
03-07 18:44:04.730  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(79) < framesDesired(516)
03-07 18:44:04.950  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(119) < framesDesired(516)
03-07 18:44:05.007  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(127) < framesDesired(516)
03-07 18:44:05.139  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(151) < framesDesired(516)
03-07 18:44:05.231  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(167) < framesDesired(516)
03-07 18:44:05.323  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(183) < framesDesired(516)
03-07 18:44:05.415  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(199) < framesDesired(516)
03-07 18:44:05.507  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(215) < framesDesired(516)
03-07 18:44:05.563  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(223) < framesDesired(516)
03-07 18:44:05.614  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(231) < framesDesired(516)
03-07 18:44:05.669  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(239) < framesDesired(516)
03-07 18:44:05.783  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(263) < framesDesired(516)
03-07 18:44:05.840  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(271) < framesDesired(516)
03-07 18:44:05.886  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(279) < framesDesired(516)
03-07 18:44:05.937  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(287) < framesDesired(516)
03-07 18:44:05.996  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(295) < framesDesired(516)
03-07 18:44:06.130  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(319) < framesDesired(516)
03-07 18:44:06.187  2828  3521 V AudioFlinger: track(0xf5ea3a80) underrun,  framesReady(327) < framesDesired(516)

【解决问题】

 

根据差值大小来调节延时时间来解决问题。只要我们检测到 framesReady 小于 framesDesired,我们就进行 1 毫秒延时,然后再获取延时后的 framesReady 值与 framesDesired 值进行比较,如果 framesReady 仍然小于 framesDesired,那么则继续延时 1 毫秒。如此循环,直到 framesReady 值大于等于 framesDesired 或者超时退出。这次的改动是在上次修复的代码基础上进行的(参见《记一次Android系统下解决音频UnderRun问题的过程》)。话不多说,直接把修复问题的 Patch 贴上来吧,如下:

 

diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3c941bc..614a5c2 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1454,6 +1454,8 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
                                              type_t type,
                                              bool systemReady)
     :   ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type, systemReady),
+        mLackFrames(0),
+        mNeededFrames(0),
         mNormalFrameCount(0), mSinkBuffer(NULL),
         mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
         mMixerBuffer(NULL),
@@ -2729,6 +2731,7 @@ void AudioFlinger::PlaybackThread::detachAuxEffect_l(int effectId)
 bool AudioFlinger::PlaybackThread::threadLoop()
 {
     Vector< sp<Track> > tracksToRemove;
+    size_t minReadyFrames = 0;
 
     mStandbyTimeNs = systemTime();
 
@@ -3012,10 +3015,32 @@ bool AudioFlinger::PlaybackThread::threadLoop()
                         const int32_t deltaMs = delta / 1000000;
                         const int32_t throttleMs = mHalfBufferMs - deltaMs;
                         if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {
-                            //usleep(throttleMs * 1000);    // We can prolong this sleep time to prepare much data for threadLoop_write()
-                            usleep((throttleMs + 3) * 1000);     /* Delay more 3ms to prepare much data to 
-                                                                  * fix tencent video player underrun bug   20161216 
-                                                                  */
+                            //usleep(throttleMs * 1000);    // This is the original method to solve underrun problem by Android
+                            
+			     /* In short, the underrun problem occurs due to framesReady less than 
+			      * desiredFrames. So we can simply wait the upper level prepares enough 
+			      * data for writing when we detected framesReady < desiredFrames.
+			      * Use do...while loop to wait and judge if we have enough data.
+			      * Fix all underrun problem - 20170401
+			      */
+			     ALOGV("Qidi - real mLackFrames = %d", mLackFrames);
+			     if(mLackFrames > 0) {    // 只要检测到framesReady小于framesDesired则执行下方的延时等待
+				  size_t count = mActiveTracks.size();
+				  size_t tmpReadyFrames = 0;
+				  unsigned char waitTimeout = 0;
+                                 minReadyFrames = mNeededFrames;
+				  #define MAX_TIME 5
+				  do {
+					usleep(1000);    // 延时1毫米,然后判断准备好的数据是否足够,以及是否超时
+	        			for (size_t i = 0; i < count; i++) {
+	                			sp<Track> t = mActiveTracks[i].promote();
+						Track* const track = t.get();
+						tmpReadyFrames = track->framesReady();
+						minReadyFrames =  tmpReadyFrames > minReadyFrames ? minReadyFrames : tmpReadyFrames;
+						ALOGV("Qidi - minReadyFrames=%d, mNeededFrames=%d", minReadyFrames, mNeededFrames);
+	        			}
+	        		  }while((minReadyFrames < mNeededFrames) && (waitTimeout++ < MAX_TIME));    // 如果数据不足且没有超时,则继续等待
+			     }
+			     mLackFrames  = 0;
+			     minReadyFrames = 0;
+			
                             // notify of throttle start on verbose log
                             ALOGV_IF(mThreadThrottleEndMs == mThreadThrottleTimeMs,
                                     "mixer(%p) throttle begin:"
@@ -3915,7 +3940,8 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
         // add frames already consumed but not yet released by the resampler
         // because mAudioTrackServerProxy->framesReady() will include these frames
         desiredFrames += mAudioMixer->getUnreleasedFrames(track->name());
-
+        mNeededFrames = desiredFrames;
+		
         uint32_t minFrames = 1;
         if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
                 (mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
@@ -4147,6 +4173,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
             if (framesReady < desiredFrames && !track->isStopped() && !track->isPaused()) {    // underrun occurs
                 ALOGV("track(%p) underrun,  framesReady(%zu) < framesDesired(%zd)",
                         track, framesReady, desiredFrames);
+	       mLackFrames = desiredFrames - framesReady;
                 track->mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);
             }
             // clear effect chain input buffer if an active track underruns to avoid sending
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 46ac300..14d88c6 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -480,6 +480,12 @@ public:
 //FIXME may be more appropriate if expressed in time units. Need to revise how underrun is handled
     // for offloaded tracks
     static const int8_t kMaxTrackRetriesOffload = 20;
+    size_t    mLackFrames;
+	 // mLackFrames is used for adjusting underrun delay time dynamically.
+	 // such delay is performed in MixerThread::prepareTracks_l() when 
+	 // underrun occurrs.
+    size_t	mNeededFrames;
+	 // mNeededFrames is equal to desiredFrames in MixerThread::prepareTracks_l()
 
     PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                    audio_io_handle_t id, audio_devices_t device, type_t type, bool systemReady);

    修改完成后,编译系统并烧写镜像文件到设备上。经测试,该方法对所有之前出现过的 underrun 问题均有效。