記一次Android系統下解決音訊UnderRun問題的過程
記一次Android系統下解決音訊UnderRun問題的過程
2017年01月04日 18:09:32 Qidi_Huang 閱讀數:4540 標籤: AndroidAudiounderrunxrun解決辦法 更多
版權宣告:本文為博主原創文章,若要轉載請註明出處。 https://blog.csdn.net/Qidi_Huang/article/details/54020326
【前言】
因為這幾天在為裝置從 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 毫秒。
重編系統映象後燒入裝置進行驗證,問題得到解決。
【擴充套件閱讀】