本文說明 Android 音訊系統如何避免優先權反轉,並重點介紹您也可以使用的技術。
這些技術可能對高音訊效能應用程式的開發人員、OEM 和 SoC 供應商有所幫助,他們正在實作音訊 HAL。請注意,實作這些技術不保證能避免故障或其他失敗情況,特別是在音訊環境以外使用時。結果可能有所不同,建議您自行進行評估和測試。
背景
Android AudioFlinger 音訊伺服器和 AudioTrack/AudioRecord 用戶端實作項目正在重新架構,以減少延遲。這項工作始於 Android 4.1,並在 4.2、4.3、4.4 和 5.0 中持續改善。
為達到更低的延遲時間,我們在整個系統中進行了許多變更。其中一項重要變更,是透過更可預測的排程政策,將 CPU 資源指派給時間關鍵執行緒。可靠的排程可減少音訊緩衝區大小和計數,同時避免緩衝區不足和溢位。
優先權反轉
優先權反轉是即時系統的經典故障模式,其中優先權較高的工作會無限期遭到封鎖,等待優先權較低的工作釋出資源,例如 (受互斥鎖保護的) 共享狀態。互斥鎖。
在音訊系統中,優先權反轉通常會表現為故障 (按一下、彈出、中斷)、使用循環緩衝區時重複音訊,或延遲回應指令。
解決優先權反轉問題的常見方法是增加音訊緩衝區大小。不過,這種方法會增加延遲時間,而且只是隱藏問題,並未解決問題。建議您瞭解並避免發生優先權反轉,如下所示。
在 Android 音訊實作中,優先權反轉最有可能發生在下列位置。因此,您應該將注意力集中在以下位置:
- between normal mixer thread and fast mixer thread in AudioFlinger
- 快速 AudioTrack 的應用程式回呼執行緒和快速混音器執行緒之間 (兩者都具有較高的優先順序,但優先順序略有不同)
- 應用程式回呼執行緒之間,適用於快速 AudioRecord 和快速擷取執行緒 (與先前類似)
- 在音訊硬體抽象層 (HAL) 實作中,例如用於電話或回音消除
- 核心中的音訊驅動程式
- AudioTrack 或 AudioRecord 回呼執行緒與其他應用程式執行緒之間 (這不在我們的掌控範圍內)
常見解決方案
常見解決方法包括:
- 停用中斷
- 優先權繼承互斥鎖
在 Linux 使用者空間中停用中斷不可行,而且不適用於對稱多處理器 (SMP)。
音訊系統不會使用優先權繼承 futexes (快速使用者空間互斥鎖),因為這類互斥鎖相對較為耗用資源,而且需要受信任的用戶端。
Android 使用的技術
實驗以「try lock」開始,並以逾時鎖定。這是互斥鎖定作業的非阻斷式和有界阻斷式變體。嘗試使用鎖定和鎖定 (含逾時) 的效果相當不錯,但容易受到幾種不明顯的失敗模式影響:如果用戶端剛好很忙,伺服器不一定能存取共用狀態;如果有一連串不相關的鎖定都逾時,累積的逾時時間可能會過長。
我們也會使用原子作業,例如:
- 增加
- 位元「or」
- 位元「and」
這些函式都會傳回先前的值,並包含必要的 SMP 障礙。缺點是可能需要無限次重試。實務上,我們發現重試並非問題。
注意:原子作業及其與記憶體屏障的互動,是眾所皆知容易誤解和誤用的概念。我們在此列出這些方法是為了完整性,但建議您也閱讀 Android SMP 基礎知識 一文,瞭解更多資訊。
我們仍使用上述大部分工具,並於近期新增下列技術:
- 使用非封鎖單一讀取器單一寫入器 先進先出 (FIFO) 佇列 處理資料。
- 請嘗試複製狀態,而不是在高優先順序和低優先順序模組之間共用狀態。
- 如果需要共用狀態,請將狀態限制為可在單一匯流排作業中以不可分割的方式存取,且不需重試的最大字元大小。
- 如要處理複雜的多字詞狀態,請使用狀態佇列。狀態佇列基本上就是非封鎖的單一讀取器單一寫入器 FIFO 佇列,用於狀態而非資料,但寫入器會將相鄰的推送作業摺疊成單一推送作業。
- 請注意 SMP 正確性的記憶體屏障。
- 信任但仍須驗證。 在程序之間共用狀態時,請勿假設狀態格式正確。舉例來說,請檢查索引是否在界限內。在相同程序中的執行緒之間,以及相互信任的程序之間 (通常具有相同的 UID),不需要進行這項驗證。對於 PCM 音訊等共用資料,如果損毀不會造成重大影響,也不需要使用這個方法。
非阻斷演算法
非封鎖演算法 是近期許多研究的主題。 但除了單一讀取器單一寫入器 FIFO 佇列之外, 我們發現這些佇列複雜且容易出錯。
從 Android 4.2 開始,您可以在下列位置找到非封鎖的單一讀取器/寫入器類別:
- frameworks/av/include/media/nbaio/
- frameworks/av/media/libnbaio/
- frameworks/av/services/audioflinger/StateQueue*
這些緩衝區專為 AudioFlinger 設計,並非一般用途。非封鎖演算法難以偵錯是眾所皆知的事實。您可以將這段程式碼視為模型。但請注意,這些類別可能含有錯誤,且不保證適用於其他用途。
對開發人員來說,部分 OpenSL ES 應用程式程式碼範例應更新為使用非封鎖演算法,或參照非 Android 開放原始碼程式庫。
我們已發布專為應用程式碼設計的非封鎖 FIFO 實作範例。請查看平台來源目錄 frameworks/av/audio_utils
中的這些檔案:
工具
據我們所知,目前沒有自動工具可找出優先權反轉問題,尤其是在問題發生前。如果能夠存取整個程式碼集,部分研究靜態程式碼分析工具就能找出優先權反轉。當然,如果涉及任意使用者程式碼 (如應用程式的情況) 或大型程式碼集 (如 Linux 核心和裝置驅動程式),靜態分析可能不切實際。最重要的是仔細閱讀程式碼,並充分掌握整個系統和互動。發生優先權反轉後,您可以使用 systrace 和 ps -t -p
等工具查看,但這些工具無法事先告知您。
最後提醒
經過上述討論,您應該不會再害怕互斥鎖。在一般非時間關鍵用途中,只要正確使用及實作互斥鎖,互斥鎖就是您的好幫手。但在高優先順序和低優先順序工作之間,以及在時間敏感型系統中,互斥鎖定更有可能造成問題。