自 2025 年 3 月 27 日起,我們建議您使用 android-latest-release
而非 aosp-main
建構及貢獻 AOSP。詳情請參閱「Android 開放原始碼計畫變更」。
避免優先順序倒置
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
本文將說明 Android 音訊系統如何嘗試避免優先順序反轉,並強調您也可以使用的技巧。
這些技巧可能對開發高效音訊應用程式的開發人員、OEM 和 SoC 供應商 (正在實作音訊 HAL) 有用。請注意,實作這些技巧並不能保證能避免發生錯誤或其他失敗情形,尤其是在音訊內容以外的情況下。結果可能因人而異,請自行評估及測試。
背景
我們正在重新架構 Android AudioFlinger 音訊伺服器和 AudioTrack/AudioRecord 用戶端實作項目,以減少延遲時間。這項工作從 Android 4.1 開始,並在 4.2、4.3、4.4 和 5.0 中持續進行改善。
為了達到更低的延遲,我們需要在整個系統中進行許多變更。其中一個重要的變更,是將 CPU 資源指派給時間敏感的執行緒,並採用更可預測的排程政策。可靠的排程可減少音訊緩衝區大小和計數,同時避免發生不足和超載。
優先順序反轉
優先順序反轉是即時系統的經典失敗模式,其中高優先順序工作會在無限時間內遭到阻斷,等待低優先順序工作釋出資源,例如 (受保護的共用狀態) 互斥鎖。
在音訊系統中,優先順序倒置通常會導致異常 (點擊、彈出、中斷)、重複音訊 (使用循環緩衝區時),或回應指令的延遲。
優先順序倒置的常見解決方法是增加音訊緩衝區大小。不過,這種方法會增加延遲時間,而且只是隱藏問題,並未解決問題。因此,建議您瞭解並避免優先順序倒置情形,如下所示。
在 Android 音訊實作中,優先順序反轉最有可能發生在這些位置。因此,您應將注意力集中在以下項目:
-
在 AudioFlinger 中,正常混合器執行緒和快速混合器執行緒之間
-
快速 AudioTrack 和快速混合器執行緒的應用程式回呼執行緒之間 (兩者都具有較高的優先順序,但優先順序略有不同)
-
快速 AudioRecord 和快速擷取執行緒之間的應用程式回呼執行緒 (類似於前述)
-
在音訊硬體抽象層 (HAL) 實作中,例如電話或回音消除功能
-
核心音訊驅動程式內
-
AudioTrack 或 AudioRecord 回呼執行緒與其他應用程式執行緒之間 (這是我們無法控制的情況)
常見解決方案
常見的解決方法包括:
在 Linux 使用者空間中,停用中斷功能是不可行的,且不適用於對稱式多處理器 (SMP)。
由於優先順序繼承 futex (快速使用者空間互斥鎖) 相對較重,且需要信任的用戶端,因此不會用於音訊系統。
Android 使用的技巧
實驗會以「try lock」開始,並以逾時鎖定。這些是互斥鎖定作業的非阻斷和受限阻斷變體。嘗試鎖定和設有逾時期限的鎖定功能運作良好,但容易發生幾種不易察覺的失敗模式:如果用戶端剛好忙碌,伺服器無法保證能夠存取共用狀態;如果有長串不相關的鎖定皆逾時,累積的逾時期限可能會過長。
我們也會使用原子作業,例如:
所有這些都會傳回先前的值,並包含必要的 SMP 障礙。缺點是可能需要無限次重試。實際上,我們發現重試並不會造成問題。
注意:不可分割作業及其與記憶體屏障的互動,向來都是容易遭到誤解和誤用的部分。我們在此提供這些方法,以便完整說明,但建議您也閱讀「
Android 適用的 SMP 入門指南」一文,瞭解詳情。
我們仍保留並使用上述大部分工具,並最近新增了以下技術:
-
請為資料使用非阻斷的單一讀取器單一寫入器 FIFO 佇列。
-
請盡量複製狀態,而非在高優先順序和低優先順序模組之間共用狀態。
-
當需要共用狀態時,請將狀態限制為最大大小的 word,這樣在單一匯流排作業中,可以不重試就能以原子方式存取。
-
如果是複雜的多字狀態,請使用狀態佇列。狀態佇列基本上只是一個非阻斷的單讀取器單寫入 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
等工具可在發生優先順序反轉時提供相關資訊,但不會事先通知您。
最後的提醒
在討論結束後,請不要害怕使用互斥鎖。在一般非時間敏感的用途中,如果正確使用及實作互斥鎖,它們就是一般用途的好幫手。但在高優先順序和低優先順序工作之間,以及在時間敏感的系統中,Mutex 更有可能造成問題。
這個頁面中的內容和程式碼範例均受《內容授權》中的授權所規範。Java 與 OpenJDK 是 Oracle 和/或其關係企業的商標或註冊商標。
上次更新時間:2025-07-27 (世界標準時間)。
[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-07-27 (世界標準時間)。"],[],[],null,["# Avoid priority inversion\n\nThis article explains how the Android's audio system attempts to avoid\npriority inversion,\nand highlights techniques that you can use too.\n\n\nThese techniques may be useful to developers of high-performance\naudio apps, OEMs, and SoC providers who are implementing an audio\nHAL. Please note implementing these techniques is not\nguaranteed to prevent glitches or other failures, particularly if\nused outside of the audio context.\nYour results may vary, and you should conduct your own\nevaluation and testing.\n\nBackground\n----------\n\n\nThe Android AudioFlinger audio server and AudioTrack/AudioRecord\nclient implementation are being re-architected to reduce latency.\nThis work started in Android 4.1, and continued with further improvements\nin 4.2, 4.3, 4.4, and 5.0.\n\n\nTo achieve this lower latency, many changes were needed throughout the system. One\nimportant change is to assign CPU resources to time-critical\nthreads with a more predictable scheduling policy. Reliable scheduling\nallows the audio buffer sizes and counts to be reduced while still\navoiding underruns and overruns.\n\nPriority inversion\n------------------\n\n\n[Priority inversion](http://en.wikipedia.org/wiki/Priority_inversion)\nis a classic failure mode of real-time systems,\nwhere a higher-priority task is blocked for an unbounded time waiting\nfor a lower-priority task to release a resource such as (shared\nstate protected by) a\n[mutex](http://en.wikipedia.org/wiki/Mutual_exclusion).\n\n\nIn an audio system, priority inversion typically manifests as a\n[glitch](http://en.wikipedia.org/wiki/Glitch)\n(click, pop, dropout),\n[repeated audio](http://en.wikipedia.org/wiki/Max_Headroom_(character))\nwhen circular buffers\nare used, or delay in responding to a command.\n\n\nA common workaround for priority inversion is to increase audio buffer sizes.\nHowever, this method increases latency and merely hides the problem\ninstead of solving it. It is better to understand and prevent priority\ninversion, as seen below.\n\n\nIn the Android audio implementation, priority inversion is most\nlikely to occur in these places. And so you should focus your attention here:\n\n- between normal mixer thread and fast mixer thread in AudioFlinger\n- between application callback thread for a fast AudioTrack and fast mixer thread (they both have elevated priority, but slightly different priorities)\n- between application callback thread for a fast AudioRecord and fast capture thread (similar to previous)\n- within the audio Hardware Abstraction Layer (HAL) implementation, e.g. for telephony or echo cancellation\n- within the audio driver in kernel\n- between AudioTrack or AudioRecord callback thread and other app threads (this is out of our control)\n\nCommon solutions\n----------------\n\n\nThe typical solutions include:\n\n- disabling interrupts\n- priority inheritance mutexes\n\n\nDisabling interrupts is not feasible in Linux user space, and does\nnot work for Symmetric Multi-Processors (SMP).\n\n\nPriority inheritance\n[futexes](http://en.wikipedia.org/wiki/Futex)\n(fast user-space mutexes) are not used in the audio system because they are relatively heavyweight,\nand because they rely on a trusted client.\n\nTechniques used by Android\n--------------------------\n\n\nExperiments started with \"try lock\" and lock with timeout. These are\nnon-blocking and bounded blocking variants of the mutex lock\noperation. Try lock and lock with timeout worked fairly well but were\nsusceptible to a couple of obscure failure modes: the\nserver was not guaranteed to be able to access the shared state if\nthe client happened to be busy, and the cumulative timeout could\nbe too long if there was a long sequence of unrelated locks that\nall timed out.\n\n\nWe also use\n[atomic operations](http://en.wikipedia.org/wiki/Linearizability)\nsuch as:\n\n- increment\n- bitwise \"or\"\n- bitwise \"and\"\n\n\nAll of these return the previous value and include the necessary\nSMP barriers. The disadvantage is they can require unbounded retries.\nIn practice, we've found that the retries are not a problem.\n\n**Note:** Atomic operations and their interactions with memory barriers\nare notoriously badly misunderstood and used incorrectly. We include these methods\nhere for completeness but recommend you also read the article\n[SMP Primer for Android](https://developer.android.com/training/articles/smp.html)\nfor further information.\n\n\nWe still have and use most of the above tools, and have recently\nadded these techniques:\n\n- Use non-blocking single-reader single-writer [FIFO queues](http://en.wikipedia.org/wiki/Circular_buffer) for data.\n- Try to *copy* state rather than *share* state between high- and low-priority modules.\n- When state does need to be shared, limit the state to the maximum-size [word](http://en.wikipedia.org/wiki/Word_(computer_architecture)) that can be accessed atomically in one-bus operation without retries.\n- For complex multi-word state, use a state queue. A state queue is basically just a non-blocking single-reader single-writer FIFO queue used for state rather than data, except the writer collapses adjacent pushes into a single push.\n- Pay attention to [memory barriers](http://en.wikipedia.org/wiki/Memory_barrier) for SMP correctness.\n- [Trust, but verify](http://en.wikipedia.org/wiki/Trust,_but_verify). When sharing *state* between processes, don't assume that the state is well-formed. For example, check that indices are within bounds. This verification isn't needed between threads in the same process, between mutual trusting processes (which typically have the same UID). It's also unnecessary for shared *data* such as PCM audio where a corruption is inconsequential.\n\nNon-blocking algorithms\n-----------------------\n\n\n[Non-blocking algorithms](http://en.wikipedia.org/wiki/Non-blocking_algorithm)\nhave been a subject of much recent study.\nBut with the exception of single-reader single-writer FIFO queues,\nwe've found them to be complex and error-prone.\n\n\nStarting in Android 4.2, you can find our non-blocking,\nsingle-reader/writer classes in these locations:\n\n- frameworks/av/include/media/nbaio/\n- frameworks/av/media/libnbaio/\n- frameworks/av/services/audioflinger/StateQueue\\*\n\n\nThese were designed specifically for AudioFlinger and are not\ngeneral-purpose. Non-blocking algorithms are notorious for being\ndifficult to debug. You can look at this code as a model. But be\naware there may be bugs, and the classes are not guaranteed to be\nsuitable for other purposes.\n\n\nFor developers, some of the sample OpenSL ES application code should be updated to\nuse non-blocking algorithms or reference a non-Android open source library.\n\n\nWe have published an example non-blocking FIFO implementation that is specifically designed for\napplication code. See these files located in the platform source directory\n`frameworks/av/audio_utils`:\n\n- [include/audio_utils/fifo.h](https://android.googlesource.com/platform/system/media/+/android16-release/audio_utils/include/audio_utils/fifo.h)\n- [fifo.c](https://android.googlesource.com/platform/system/media/+/android16-release/audio_utils/fifo.c)\n- [include/audio_utils/roundup.h](https://android.googlesource.com/platform/system/media/+/android16-release/audio_utils/include/audio_utils/roundup.h)\n- [roundup.c](https://android.googlesource.com/platform/system/media/+/android16-release/audio_utils/roundup.c)\n\nTools\n-----\n\n\nTo the best of our knowledge, there are no automatic tools for\nfinding priority inversion, especially before it happens. Some\nresearch static code analysis tools are capable of finding priority\ninversions if able to access the entire codebase. Of course, if\narbitrary user code is involved (as it is here for the application)\nor is a large codebase (as for the Linux kernel and device drivers),\nstatic analysis may be impractical. The most important thing is to\nread the code very carefully and get a good grasp on the entire\nsystem and the interactions. Tools such as\n[systrace](http://developer.android.com/tools/help/systrace.html)\nand\n`ps -t -p`\nare useful for seeing priority inversion after it occurs, but do\nnot tell you in advance.\n\nA final word\n------------\n\n\nAfter all of this discussion, don't be afraid of mutexes. Mutexes\nare your friend for ordinary use, when used and implemented correctly\nin ordinary non-time-critical use cases. But between high- and\nlow-priority tasks and in time-sensitive systems mutexes are more\nlikely to cause trouble."]]