音訊調試

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

三通水槽

「tee sink」是一個 AudioFlinger 偵錯功能,僅在自訂版本中可用,用於保留最近音訊的一小段以供以後分析。這樣可以將實際播放或錄製的內容與預期內容進行比較。

為了隱私,預設情況下,在編譯時和運行時,Tee 接收器都是禁用的。要使用 tee 接收器,您需要透過重新編譯並設定屬性來啟用它。調試完成後請務必停用此功能; T 卹水槽不應在生產版本中保持啟用狀態。

本節的說明適用於 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.cppAudioFlinger::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

媒體紀錄

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崩潰或掛起也可以進行檢查。
  • 每個時間軸都是非阻塞的。
  • 對性能的干擾較小。 (當然,任何形式的日誌記錄都不是完全非侵入性的。)

建築學

下圖展示了在引入media.log之前mediaserver進程和init進程的關係:

media.log 之前的架構

圖 1. media.log 之前的架構

值得注意的點:

  • init forks 並執行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 。只有當屬性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