本文介紹了調試 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
編譯時設置
cd frameworks/av/services/audioflinger
- 編輯
Configuration.h
。 - 取消註釋
#define TEE_SINK
。 - 重新構建
libaudioflinger.so
。 -
adb root
-
adb remount
- 將新的
libaudioflinger.so
推送或同步到設備的/system/lib
。
運行時設置
adb shell getprop | grep ro.debuggable
確認輸出為:[ro.debuggable]: [1]
-
adb shell
-
ls -ld /data/misc/audioserver
確認輸出為:
drwx------ media media ... media
如果該目錄不存在,請按如下方式創建:
mkdir /data/misc/audioserver
chown media:media /data/misc/audioserver
-
echo af.tee=# > /data/local.prop
其中af.tee
值是下面描述的數字。 -
chmod 644 /data/local.prop
-
reboot
af.tee
屬性的值
af.tee
的值是一個介於 0 和 7 之間的數字,表示幾個位的總和,每個特徵一個。請參閱 AudioFlinger.cpp 中AudioFlinger.cpp
AudioFlinger::AudioFlinger()
中的代碼以了解每個位的說明,但簡要說明:
- 1 = 輸入
- 2 = FastMixer 輸出
- 4 = 每軌 AudioRecord 和 AudioTrack
目前還沒有深度緩衝區或普通混合器的位,但您可以使用“4”獲得類似的結果。
測試和獲取數據
- 運行您的音頻測試。
-
adb shell dumpsys media.audio_flinger
- 在
dumpsys
輸出中查找一行,如下所示:
tee copied to /data/misc/audioserver/20131010101147_2.wav
這是一個 PCM .wav 文件。 - 然後
adb pull
任何感興趣的/data/misc/audioserver/*.wav
文件;請注意,特定於軌道的轉儲文件名不會出現在dumpsys
輸出中,但在軌道關閉時仍會保存到/data/misc/audioserver
。 - 在與他人共享之前,請查看轉儲文件是否存在隱私問題。
建議
嘗試這些想法以獲得更有用的結果:
- 禁用觸摸聲音和按鍵點擊以減少測試輸出的中斷。
- 最大化所有捲。
- 如果您的測試不感興趣,請禁用通過麥克風發出聲音或錄音的應用程序。
- 特定於軌道的轉儲僅在軌道關閉時保存;您可能需要強制關閉應用程序以轉儲其特定於軌道的數據
- 測試後立即執行
dumpsys
;可用的記錄空間有限。 - 為確保您不會丟失轉儲文件,請定期將它們上傳到您的主機。只保留有限數量的轉儲文件;達到該限制後,舊轉儲將被刪除。
恢復
如上所述,不應啟用 tee sink 功能。按如下方式恢復您的構建和設備:
- 將源代碼更改還原為
Configuration.h
。 - 重新構建
libaudioflinger.so
。 - 將恢復的
libaudioflinger.so
推送或同步到設備的/system/lib
。 -
adb shell
-
rm /data/local.prop
-
rm /data/misc/audioserver/*.wav
-
reboot
媒體日誌
ALOGx 宏
Android SDK 中的標準 Java 語言日誌記錄 API 是android.util.Log 。
Android NDK 中對應的 C 語言 API 是<android/log.h>
中聲明的__android_log_print
。
在 Android 框架的原生部分中,我們更喜歡名為ALOGE
、 ALOGW
、 ALOGI
、 ALOGV
等的宏。它們在<utils/Log.h>
中聲明,為了本文的目的,我們將它們統稱為ALOGx
.
所有這些 API 都易於使用且易於理解,因此它們在整個 Android 平台中無處不在。特別是mediaserver
進程,包括 AudioFlinger 聲音服務器,廣泛使用ALOGx
。
然而, ALOGx
和朋友有一些限制:
- 它們容易受到“日誌垃圾郵件”的影響:日誌緩衝區是共享資源,因此很容易由於不相關的日誌條目而溢出,從而導致信息丟失。默認情況下,
ALOGV
變體在編譯時被禁用。但是當然,如果啟用它,它也會導致日誌垃圾郵件。 - 底層內核系統調用可能會阻塞,可能導致優先級反轉,從而導致測量干擾和不准確。這對於諸如
FastMixer
和FastCapture
等時間要求嚴格的線程來說尤其重要。 - 如果禁用特定日誌以減少日誌垃圾郵件,則該日誌將捕獲的任何信息都將丟失。在很明顯該日誌會很有趣之後,無法追溯啟用特定日誌。
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
之前:
值得注意的點:
-
init
分叉並執行mediaserver
。 -
init
檢測到mediaserver
的死亡,並根據需要重新分叉。 - 未顯示
ALOGx
日誌記錄。
下圖顯示了將media.log
添加到架構後的新組件關係:
重要變化:
- 客戶端使用
NBLOG
API 構建日誌條目並將它們附加到共享內存中的循環緩衝區。 -
MediaLogService
可以隨時轉儲循環緩衝區的內容。 - 循環緩衝區的設計方式使得共享內存的任何損壞都不會導致
MediaLogService
崩潰,並且它仍然能夠轉儲盡可能多的不受損壞影響的緩衝區。 - 循環緩衝區對於寫入新條目和讀取現有條目都是非阻塞和無鎖的。
- 不需要內核系統調用來寫入或讀取循環緩衝區(可選的時間戳除外)。
在哪裡使用
從 Android 4.4 開始,AudioFlinger 中只有少數幾個日誌點使用了media.log
系統。儘管新的 API 不像ALOGx
那樣容易使用,但它們也不是特別難。我們鼓勵您在必不可少的情況下學習新的日誌記錄系統。特別是,推薦用於必須頻繁、定期且無阻塞地運行的 AudioFlinger 線程,例如FastMixer
和FastCapture
線程。
如何使用
添加日誌
首先,您需要將日誌添加到您的代碼中。
在FastMixer
和FastCapture
線程中,使用如下代碼:
logWriter->log("string"); logWriter->logf("format", parameters); logWriter->logTimestamp();
由於此NBLog
時間線僅由FastMixer
和FastCapture
線程使用,因此不需要互斥。
在其他 AudioFlinger 線程中,使用mNBLogWriter
:
mNBLogWriter->log("string"); mNBLogWriter->logf("format", parameters); mNBLogWriter->logTimestamp();
對於FastMixer
和FastCapture
以外的線程,線程本身和活頁夾操作都可以使用線程的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_harness
為1
時才有效。您可以通過以下方式啟用它:
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