本文介紹了調試 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
編譯時設定
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::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
;可用的錄音空間有限。 - 為了確保您不會遺失轉儲文件,請定期將它們上傳到您的主機。僅保留有限數量的轉儲文件;達到該限制後,較舊的轉儲將被刪除。
恢復
如上所述,T 型水槽功能不應保持啟用狀態。按如下方式恢復您的版本和裝置:
- 將原始碼變更還原到
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
崩潰或掛起也可以進行檢查。 - 每個時間軸都是非阻塞的。
- 對性能的干擾較小。 (當然,任何形式的日誌記錄都不是完全非侵入性的。)
建築學
下圖展示了在引入media.log
之前mediaserver
進程和init
進程的關係:
值得注意的點:
-
init
forks 並執行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
。只有當屬性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