音訊偵錯

本文將說明一些 Android 音訊偵錯的訣竅。

Tee 接收器

「tee sink」是 AudioFlinger 偵錯功能,僅適用於自訂版本,可保留近期音訊的短片段,供日後分析。這樣一來,您就能比較實際播放或錄製的內容,與預期的內容。

為了保護隱私,TEE 接收器預設會在編譯和執行期間停用。如要使用 tee 匯出端,您必須重新編譯並設定屬性才能啟用。請務必在偵錯完成後停用這項功能;在正式版版本中,請勿讓 tee sink 保持啟用狀態。

本節的操作說明適用於 Android 7.x 以上版本。如果是 Android 5.x 和 6.x,請將 /data/misc/audioserver 替換為 /data/misc/media。此外,您必須使用 userdebug 或 eng 版本。如果您使用的是 userdebug 版本,請使用以下指令停用 verity:

adb root && adb disable-verity && adb reboot

編譯時間設定

  1. cd frameworks/av/services/audioflinger
  2. 編輯 Configuration.h
  3. 取消註解 #define TEE_SINK
  4. 重新建構 libaudioflinger.so
  5. adb root
  6. adb remount
  7. 將新的 libaudioflinger.so 推送或同步至裝置的 /system/lib

執行階段設定

  1. adb shell getprop | grep ro.debuggable
    確認輸出內容為:[ro.debuggable]: [1]
  2. adb shell
  3. ls -ld /data/misc/audioserver

    確認輸出內容如下:

    drwx------ media media ... media
    

    如果目錄不存在,請按照下列步驟建立:

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    其中 af.tee 值為下方所述的數字。
  5. chmod 644 /data/local.prop
  6. reboot

af.tee 屬性值

af.tee 的值是介於 0 和 7 之間的數字,表示每個功能的多個位元總和。請參閱 AudioFlinger.cpp 中的 AudioFlinger::AudioFlinger() 程式碼,瞭解每個位元簡短的說明:

  • 1 = 輸入
  • 2 = FastMixer 輸出
  • 4 = 每個音軌的 AudioRecord 和 AudioTrack

目前尚未有深層緩衝區或一般混合器的位元,但您可以使用「4」取得類似的結果。

測試及取得資料

  1. 執行音訊測試。
  2. adb shell dumpsys media.audio_flinger
  3. dumpsys 輸出內容中尋找類似以下的這一行:
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    這是 PCM .wav 檔案。
  4. 接著 adb pull 任何感興趣的 /data/misc/audioserver/*.wav 檔案;請注意,軌道專屬的傾印檔案名稱不會顯示在 dumpsys 輸出內容中,但會在軌道關閉時儲存至 /data/misc/audioserver
  5. 請先檢查轉儲檔案是否有隱私權疑慮,再與他人分享。

建議

不妨試試以下實用做法,取得更實用的結果:

  • 停用觸控聲和按鍵點擊聲,以減少測試輸出內容的干擾。
  • 將所有音量設為最大。
  • 如果測試不涉及音訊或麥克風錄音,請停用相關應用程式。
  • 系統只會在關閉追蹤後儲存追蹤專屬的傾印,因此您可能需要強制關閉應用程式,才能傾印其追蹤專屬資料
  • 請在測試後立即執行 dumpsys,因為錄製空間有限。
  • 為確保不會遺失傾印檔案,請定期將這些檔案上傳至主機。系統只會保留有限數量的傾印檔案;達到該限制後,系統會移除較舊的傾印檔案。

還原

如上所述,請勿啟用 T 型接頭功能。依下列步驟還原建構和裝置:

  1. 將原始碼變更還原為 Configuration.h
  2. 重新建構 libaudioflinger.so
  3. 將已還原的 libaudioflinger.so 推送或同步至裝置的 /system/lib
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

ALOGx 巨集

Android SDK 中的標準 Java 語言記錄 API 為 android.util.Log

Android NDK 中的對應 C 語言 API 是 <android/log.h> 中宣告的 __android_log_print

在 Android 架構的原生部分中,我們建議使用名為 ALOGEALOGWALOGIALOGV 等的巨集。這些巨集會在 <utils/Log.h> 中宣告,為了方便說明,我們會將這些巨集統稱為 ALOGx

所有這些 API 都易於使用且易於理解,因此在 Android 平台上非常普遍。具體來說,mediaserver 程序 (包括 AudioFlinger 音訊伺服器) 會大量使用 ALOGx

不過,ALOGx 和朋友有以下限制:

  • 這些記錄檔容易產生「記錄檔垃圾郵件」:記錄檔緩衝區是共用資源,因此很容易因不相關的記錄檔項目而溢位,導致遺漏資訊。ALOGV 變化版本預設會在編譯期間停用。但如果啟用,也可能會導致記錄垃圾郵件。
  • 底層核心系統呼叫可能會阻斷,進而導致優先順序反轉,並因此造成測量干擾和不準確。這對 FastMixerFastCapture 等時間敏感的執行緒特別重要。
  • 如果您為了減少記錄垃圾郵件而停用特定記錄,則該記錄原本會擷取的任何資訊都會遺失。您無法在啟用特定記錄,因為這時記錄可能已經失去參考價值。

NBLOG、media.log 和 MediaLogService

NBLOG API 和相關的 media.log 程序和 MediaLogService 服務,共同組成較新的媒體記錄系統,專門用於解決上述問題。我們會以「media.log」一詞泛指這三者,但嚴格來說,NBLOG 是 C++ 記錄 API、media.log 是 Linux 處理程序名稱,而 MediaLogService 是用於檢查記錄的 Android 繫結器服務。

media.log「時間軸」是一連串記錄項目,其相對順序會保留。依照慣例,每個執行緒都應使用自己的時間軸。

優點

media.log 系統的優點如下:

  • 除非必要,否則不會將資料填入主記錄。
  • 即使 mediaserver 發生當機或掛起的情形,也能進行檢查。
  • 每個時間軸都不會阻斷。
  • 對效能造成的干擾較小。(當然,沒有任何形式的記錄功能是完全不干擾的)。

建築

下圖顯示 mediaserver 程序與 init 程序的關係,此時尚未導入 media.log

媒體.log 之前的架構

圖 1. 媒體.log 之前的架構

重要事項:

  • init 分支並執行 mediaserver
  • init 會偵測 mediaserver 的死亡,並視需要重新分支。
  • 不會顯示 ALOGx 記錄。

下圖顯示在架構中加入 media.log 後,元件之間的新關係:

導入 media.log 後的架構

圖 2. 導入 media.log 後的架構

重要異動:

  • 用戶端會使用 NBLOG API 建構記錄項目,並將這些項目附加至共用記憶體中的循環緩衝區。
  • MediaLogService 可隨時轉儲循環緩衝區的內容。
  • 環形緩衝區的設計方式可確保共用記憶體的任何毀損情形都不會導致 MediaLogService 當機,且仍可傾印未受毀損情形影響的大部分緩衝區。
  • 循環緩衝區不阻斷且無鎖定,可用於寫入新項目和讀取現有項目。
  • 寫入或讀取環形緩衝區時,不需要使用核心系統呼叫 (除了選用的時間戳記)。

適用商家

在 Android 4.4 中,AudioFlinger 中只有少數記錄點會使用 media.log 系統。雖然新版 API 不如 ALOGx 容易使用,但也不會太難。建議您瞭解新記錄系統,以便在必要時使用。特別是,建議將 AudioFlinger 執行緒用於必須經常執行、定期執行且不會封鎖的情況,例如 FastMixerFastCapture 執行緒。

使用方法

新增記錄

首先,您需要在程式碼中加入記錄。

FastMixerFastCapture 執行緒中,請使用類似以下的程式碼:

logWriter->log("string");
logWriter->logf("format", parameters);
logWriter->logTimestamp();

由於只有 FastMixerFastCapture 執行緒會使用這個 NBLog 時間軸,因此不需要互相排除。

在其他 AudioFlinger 執行緒中,請使用 mNBLogWriter

mNBLogWriter->log("string");
mNBLogWriter->logf("format", parameters);
mNBLogWriter->logTimestamp();

對於 FastMixerFastCapture 以外的執行緒,執行緒的 NBLog 時間軸可由執行緒本身和 Binder 作業使用。NBLog::Writer 不會為每個時間軸提供任何隱含的互斥排除,因此請務必確保所有記錄都發生在線程的互斥 mLock 已保留的情況下。

新增記錄後,請重新建構 AudioFlinger。

注意:每個執行緒都需要一個獨立的 NBLog::Writer 時間軸,以確保執行緒安全性,因為時間軸會刻意省略互斥鎖。如果您希望有多個執行緒使用相同的時間軸,可以使用現有的互斥鎖來保護 (如上文所述,針對 mLock)。您也可以使用 NBLog::LockedWriter 包裝函式,而非 NBLog::Writer。不過,這會抵銷這個 API 的主要優點:非阻斷行為。

完整的 NBLog API 位於 frameworks/av/include/media/nbaio/NBLog.h

啟用 media.log

media.log 預設為停用。只有在屬性 ro.test_harness1 時才會啟用。如要啟用這項功能,請按照下列步驟操作:

adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot

重新啟動時連線會中斷,因此:

adb shell
現在 ps media 指令會顯示兩個程序:
  • media.log
  • mediaserver

請記下 mediaserver 的程序 ID,以便稍後使用。

顯示時間軸

您隨時可以手動要求記錄傾印。這個指令會顯示所有有效和最近時間軸的記錄,然後清除這些記錄:

dumpsys media.log

請注意,根據設計,時間軸是獨立的,沒有合併時間軸的功能。

在媒體伺服器終止後復原記錄

接著,請嘗試終止 mediaserver 程序:kill -9 #,其中 # 是您先前記下的程序 ID。您應該會在主 logcat 中看到來自 media.log 的傾印,顯示導致當機的所有記錄。

dumpsys media.log