【前言】
因为这几天在为设备从Android M升级到Android N的bringup做准备,所以一直没写博客。趁现在刚刚把Kernel部分的移植做完,忙里偷闲把2周前解决的一个音频UnderRun问题记录一下,留作以后参考。
问题现象是:使用腾讯视频APP播放视频,一段时间后会出现pop-click噪音,听起来类似“哒哒”一样的声音。
【排查问题】
看到这个问题的现象后,我的第一猜测就是设备出现了UnderRun。
大胆假设后还需小心求证。于是我在代码中开启Verbose Log打印并重新编译系统镜像烧写到设备上。然后复现问题,并查看出现问题的时间点附近的Log消息。日志文件中出现了大量如下记录:
12-08 10:09:15.565 2825 3426 V AudioFlinger: track(0xea5dda80) underrun, framesReady(1024) < framesDesired(1026)
12-08 10:09:15.599 2825 3426 V AudioFlinger: mixer(0xeb440000) throttle begin: ret(4096) deltaMs(0) requires sleep 10 ms
12-08 10:09:15.625 2825 3426 D AudioFlinger: mixer(0xeb440000) throttle end: throttle time(20)
果然这是个UnderRun问题,Log中的信息证实了猜想:音频播放需要1026帧数据,但APP只准备好了1024帧。但我们也知道,Android系统对于underrun出现后是有一套默认的处理流程来消除问题的,也就是紧接着的2条和throttle相关的Log所对应的操作。
简单介绍一下Android系统默认处理underrun问题的流程:当检测到当前写入音频数据的时间与上次出现警告的时间间隔大于预定的最大时间间隔(5纳秒)后,系统将判定音频播放过程出现了underrun。然后系统会调用usleep()函数对当前PlaybackThread进行短时间阻塞,这样上层APP就能为PlaybackThread准备好更多音频数据。这个usleep()的时长是根据相邻2次写入音频数据的时间间隔实时计算出的。
相应的代码可以在 frameworks/av/sevices/audioflinger/Threads.cpp 中的AudioFlinger::PlaybackThread::threadLoop()函数中找到:
-
bool AudioFlinger::PlaybackThread::threadLoop()
-
{
-
......
-
if (mType == MIXER && !mStandby) {
-
// write blocked detection
-
nsecs_t now = systemTime();
-
nsecs_t delta = now - mLastWriteTime; // 相邻 2 次写入音频数据操作的时间间隔
-
if (delta > maxPeriod) {
-
mNumDelayedWrites++;
-
if ((now - lastWarning) > kWarningThrottleNs) { // 如果本次写入数据时间与上次警告出现时间间隔大于kWarningThrottleNs(5纳秒)则判断出现underrun
-
ATRACE_NAME("underrun");
-
ALOGW("write blocked for %llu msecs, %d delayed writes, thread %p",
-
ns2ms(delta), mNumDelayedWrites, this);
-
lastWarning = now;
-
}
-
}
-
-
if (mThreadThrottle
-
&& mMixerStatus == MIXER_TRACKS_READY // we are mixing (active tracks)
-
&& ret > 0) { // we wrote something
-
-
// The throttle smooths out sudden large data drains from the device,
-
// e.g. when it comes out of standby, which often causes problems with
-
// (1) mixer threads without a fast mixer (which has its own warm-up)
-
// (2) minimum buffer sized tracks (even if the track is full,
-
// the app won't fill fast enough to handle the sudden draw).
-
-
const int32_t deltaMs = delta / 1000000;
-
const int32_t throttleMs = mHalfBufferMs - deltaMs;
-
if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {
-
//usleep(throttleMs * 1000); // 通过usleep()短时间阻塞当前PlaybackThread,让app可以准备更多的数据
-
usleep((throttleMs + 3) * 1000); /* 增加 3ms 的延时时间
-
* 修复腾讯视频APP播放视频有噪声的问题 20161216
-
*/
-
// notify of throttle start on verbose log
-
ALOGV_IF(mThreadThrottleEndMs == mThreadThrottleTimeMs,
-
"mixer(%p) throttle begin:"
-
" ret(%zd) deltaMs(%d) requires sleep %d ms",
-
this, ret, deltaMs, throttleMs);
-
mThreadThrottleTimeMs += throttleMs;
-
} else {
-
uint32_t diff = mThreadThrottleTimeMs - mThreadThrottleEndMs;
-
if (diff > 0) {
-
// notify of throttle end on debug log
-
ALOGD("mixer(%p) throttle end: throttle time(%u)", this, diff);
-
mThreadThrottleEndMs = mThreadThrottleTimeMs;
-
}
-
}
-
}
-
}
-
......
-
}
【解决问题】
前文贴出的Log表明,Android系统已经检测到了UnderRun问题并进行了延时处理来让APP准备更多的音频数据。可是我们在使用腾讯视频APP时依然会继续发生UnderRun的问题,原因在于代码中计算出的延时时间对腾讯视频APP来说还是太短。在Log中我们可以看到需要的数据量为1026帧但实际准备好的数据为1024帧,所以我们可以稍微增加usleep()的延时时间来为PlaybackThread准备足够的数据。经过试验,我决定在原有延时时间上增加3毫秒。
重编系统镜像后烧入设备进行验证,问题得到解决。
【扩展阅读】
[1] 《音频出现Xrun(underrun或overrun)的原因与解决办法》
阅读(11100) | 评论(0) | 转发(0) |