记一次Android系统下解决音频UnderRun问题的过程

来源:互联网 发布:大连淘车网络和中软卓 编辑:程序博客网 时间:2024/06/06 18:30

【前言】

    因为这几天在为设备从 Android M 升级到 Android N 的 bringup 做准备,所以一直没写博客。趁现在刚刚把 Kernel 部分的移植做完,忙里偷闲把 2 周前解决的一个音频 UnderRun 问题记录一下,留作以后参考。

    问题现象是:使用腾讯视频 APP 播放视频,一段时间后会出现 pop-click 噪音,听起来类似“哒哒”一样的声音。


【排查问题】

    看到这个问题的现象后,我的第一猜测就是设备出现了 UnderRun。

    大胆假设后还需小心求证。于是我在代码中开启 Verbose Log打印并重新编译系统镜像烧写到设备上。然后复现问题,并查看出现问题的时间点附近的 Log 消息。日志文件中出现了大量如下记录:

[plain] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 12-08 10:09:15.565  2825  3426 V AudioFlinger: track(0xea5dda80) underrun,  framesReady(1024) < framesDesired(1026)  
  2. 12-08 10:09:15.599  2825  3426 V AudioFlinger: mixer(0xeb440000) throttle begin: ret(4096) deltaMs(0) requires sleep 10 ms  
  3. 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() 函数中找到:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. bool AudioFlinger::PlaybackThread::threadLoop()  
  2. {  
  3.     ......  
  4.     if (mType == MIXER && !mStandby) {  
  5.     // write blocked detection  
  6.     nsecs_t now = systemTime();  
  7.     nsecs_t delta = now - mLastWriteTime;    // 相邻 2 次写入音频数据操作的时间间隔  
  8.     if (delta > maxPeriod) {  
  9.         mNumDelayedWrites++;  
  10.         if ((now - lastWarning) > kWarningThrottleNs) {    // 如果本次写入数据时间与上次警告出现时间间隔大于kWarningThrottleNs(5纳秒)则判断出现underrun  
  11.             ATRACE_NAME("underrun");  
  12.             ALOGW("write blocked for %llu msecs, %d delayed writes, thread %p",  
  13.                    ns2ms(delta), mNumDelayedWrites, this);  
  14.             lastWarning = now;  
  15.         }  
  16.     }  
  17.       
  18.     if (mThreadThrottle  
  19.            && mMixerStatus == MIXER_TRACKS_READY // we are mixing (active tracks)  
  20.            && ret > 0) {                         // we wrote something  
  21.   
  22.        // The throttle smooths out sudden large data drains from the device,  
  23.        // e.g. when it comes out of standby, which often causes problems with  
  24.        // (1) mixer threads without a fast mixer (which has its own warm-up)  
  25.        // (2) minimum buffer sized tracks (even if the track is full,  
  26.        //     the app won't fill fast enough to handle the sudden draw).  
  27.   
  28.        const int32_t deltaMs = delta / 1000000;  
  29.        const int32_t throttleMs = mHalfBufferMs - deltaMs;  
  30.        if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) {  
  31.            //usleep(throttleMs * 1000);    // 通过usleep()短时间阻塞当前PlaybackThread,让app可以准备更多的数据  
  32.            usleep((throttleMs + 3) * 1000);     /* 增加 3ms 的延时时间 
  33.                                                  * 修复腾讯视频APP播放视频有噪声的问题   20161216  
  34.                                                  */  
  35.            // notify of throttle start on verbose log  
  36.            ALOGV_IF(mThreadThrottleEndMs == mThreadThrottleTimeMs,  
  37.                   "mixer(%p) throttle begin:"  
  38.                   " ret(%zd) deltaMs(%d) requires sleep %d ms",  
  39.                   this, ret, deltaMs, throttleMs);  
  40.            mThreadThrottleTimeMs += throttleMs;  
  41.        } else {  
  42.            uint32_t diff = mThreadThrottleTimeMs - mThreadThrottleEndMs;  
  43.            if (diff > 0) {  
  44.                // notify of throttle end on debug log  
  45.                ALOGD("mixer(%p) throttle end: throttle time(%u)"this, diff);  
  46.                mThreadThrottleEndMs = mThreadThrottleTimeMs;  
  47.            }  
  48.        }  
  49.     }  
  50.  }  
  51.  ......  
  52. }  


【解决问题】

    前文贴出的 Log 表明,Android 系统已经检测到了 UnderRun 问题并进行了延时处理来让 APP 准备更多的音频数据。可是我们在使用腾讯视频 APP 时依然会继续发生 UnderRun 的问题,原因在于代码中计算出的延时时间对腾讯视频 APP 来说还是太短。在 Log 中我们可以看到需要的数据量为 1026 帧但实际准备好的数据为 1024 帧,所以我们可以稍微增加 usleep() 的延时时间来为 PlaybackThread 准备足够的数据。经过试验,我决定在原有延时时间上增加 3 毫秒。

    重编系统镜像后烧入设备进行验证,问题得到解决。


【扩展阅读】

    [1] 《音频出现Xrun(underrun或overrun)的原因与解决办法》

转载至:http://blog.csdn.net/Qidi_Huang/article/details/54020326

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机图片字模糊怎么办 天然气手册丢了怎么办 消消乐登录异常怎么办 新手想学考古怎么办 省份证改名字怎么办 文曲星放太久开不了机怎么办 小狗吃火腿肠皮怎么办 虚火引起的牙痛怎么办 牙髓炎怎么办立刻止疼 小蜜丸吃不下去怎么办 铜钱的字不认识怎么办 古钱币出手好烦怎么办 安装目录不可写怎么办 手机不支持exfat格式怎么办 windows7图标变大了怎么办 igs格式烂曲面怎么办 手机桌面文件夹打不开怎么办 苹果下载不了150怎么办 iphone6速度变慢怎么办 苹果手机微信打不开pdf怎么办 苹果手机打不开pdf怎么办 pdf文件超过了怎么办 pdf电脑删不了怎么办 联想笔记本摄像头横屏调竖屏怎么办 pdf文件打开失败怎么办 pdf复制文字乱码怎么办 电子发票乱码了怎么办 超星尔雅挂了怎么办 电脑应用双击打不开怎么办 电脑控制面板打不开怎么办 转换器无法打开文件怎么办 电脑文件无法打开怎么办 手机上jpg打不开怎么办 脸上全是黄褐斑怎么办 容易发胖的体质怎么办 感冒后一直咳嗽怎么办 感冒咳嗽怎么办小窍门 到了减肥平台期怎么办 减肥遇见平台期怎么办 脚冻伤了痒怎么办 冬天脚后跟冻了怎么办