音頻調試

本文介紹了調試 Android 音頻的一些提示和技巧。

三通水槽

“tee sink”是一個 AudioFlinger 調試功能,僅在自定義版本中可用,用於保留最近音頻的一小段以供以後分析。這允許在實際播放或錄製的內容與預期內容之間進行比較。

出於隱私考慮,默認情況下,在編譯時和運行時禁用 tee sink。要使用 tee sink,您需要通過重新編譯以及設置屬性來啟用它。請務必在完成調試後禁用此功能;不應在生產版本中啟用 tee sink。

本節中的說明適用於 Android 7.x 及更高版本。對於 Android 5.x 和 6.x,將/data/misc/audioserver替換為/data/misc/media 。此外,您必須使用 userdebug 或 eng build。如果您使用 userdebug 構建,則使用以下命令禁用驗證:

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.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 ;可用的記錄空間有限。
  • 為確保您不會丟失轉儲文件,請定期將它們上傳到您的主機。只保留有限數量的轉儲文件;達到該限制後,舊轉儲將被刪除。

恢復

如上所述,不應啟用 tee sink 功能。按如下方式恢復您的構建和設備:

  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

媒體日誌

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之前:

media.log 之前的架構

圖 1. media.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();

由於此NBLog時間線僅由FastMixerFastCapture線程使用,因此不需要互斥。

在其他 AudioFlinger 線程中,使用mNBLogWriter

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

對於FastMixerFastCapture以外的線程,線程本身和活頁夾操作都可以使用線程的NBLog時間線。 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現在將顯示兩個進程:
  • 媒體日誌
  • 媒體服務器

記下mediaserver的進程 ID 以備後用。

顯示時間線

您可以隨時手動請求日誌轉儲。此命令顯示所有活動和最近時間線的日誌,然後清除它們:

dumpsys media.log

請注意,按照設計時間線是獨立的,並且沒有用於合併時間線的工具。

媒體服務器死亡後恢復日誌

現在嘗試殺死mediaserver進程: kill -9 # ,其中 # 是您之前記下的進程 ID。您應該在主logcat中看到來自media.log的轉儲,顯示導致崩潰的所有日誌。

dumpsys media.log