注意:本文基于 Android 7.0 进行分析
【问题概要】
上一次我介绍了一种 Android 系统下发生音频 underrun 问题的解决方法(参见《记一次Android系统下解决音频UnderRun问题的过程》),这之后平静了一段时间,测试组同事也没有再报告相关的噪声问题。
但就在前 2 天,测试组同事告诉我说她们又听见噪声了,并且这次的使用场景比上次复杂了许多——由于从 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 的差值不再固定,那么我们也应该根据差值大小来调节延时时间来解决问题。只要我们检测到 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 问题均有效。