概述
從 ALSA API 的早期開始,它就被定義為支持 PCM,或考慮到了 IEC61937 等固定比特率的載荷。參數和返回值以幀計算是常態,這使得擴展已有的 API 以支持壓縮數據流充滿挑戰。
最近這些年,音頻數字信號處理器 (DSP) 常常被集成進片上系統 (SoC) 設計中,且 DSPs 也常被集成進音頻編解碼器 (這里的音頻編解碼器與 AAC 之類的音頻數據壓縮方案不同,它是指主要用于完成模擬信號和數字信號轉換的器件) 中。與基于主機的處理相比,在 DSP 這樣的處理器上處理壓縮數據可以顯著降低功耗。Linux 對這類硬件的支持不是很好,主要是因為主線內核中缺乏可用的通用 API。
不是要求更改 ALSA PCM 接口的 API 而破壞兼容性,而是引入了一個新的 “壓縮數據” API,為音頻 DSP 提供控制和數據流接口。
這個 API 的設計靈感來自于 Intel Moorestown SOC 的 2 年經驗,通過許多必須的更正把 API 上傳到主線內核而不是 staging 樹中,使其可供其他人使用。
需求
主要的需求包括如下這些:
-
字節計數和時間之間的分離。壓縮的格式可能每個文件都有一個文件頭,或者完全沒有文件頭。幀和幀之間的載荷大小可能會變化。因此,當處理壓縮數據時,可靠地估計音頻緩沖區的時長是不可能的。需要專門的機制來實現可靠的音頻-視頻同步,這需要精確地報告給定時間點,已經渲染的采樣數。
-
處理多種格式。PCM 數據只需要采樣率、通道數和位寬的規范。相反地,壓縮數據可能是各種各樣的格式。音頻 DSP 也可以在固件中嵌入對有限數量的音頻編碼器和解碼器的支持,或者可以通過庫的動態下載支持更多選擇。
-
聚焦于主要的格式。這個 API 可以為用于音頻和視頻采集和播放的最流行的格式提供支持。它可能隨著音頻壓縮技術的進步,而添加新的格式。
-
處理多種配置。即使對于像 AAC 這樣給定的格式,一些實現可能支持 AAC 多通道而不是 HE-AAC 立體聲。同樣,WMA10 M3 級別可能需要許多內存和 CPU 周期。新的 API 需要提供一個通用的方式來列出這些格式。
-
僅渲染/獲取。這個 API 不提供任何硬件加速方法,其中 PCM 采樣被返回給用戶空間,以做更多處理。這個 API 聚焦于給 DSP 提供壓縮數據流,并假設解碼之后的數據被路由給一個物理輸出或邏輯后端。
-
復雜性隱藏。對于每個壓縮格式,現有的用戶空間多媒體框架都具有現成的枚舉/結構體。這個新 API 假設有一個平臺特有的兼容性層,轉換并利用音頻 DSP 的能力,如 Android HAL 或 PulseAudio sinks。根據構造,常規的應用程序不應該使用此 API。
設計
新 API 在流控制方面與 PCM API 有許多相同的概念。無論內容是什么,啟動 (start),暫停 (pause),恢復 (resume),排空 (drain),和停止 (stop) 命令具有相同的語義。
內存環形緩沖區被分割為一系列片段的概念借鑒自 ALSA PCM API。然而,只能指定字節大小。
拖動/trick 模式假定由主機處理。
不支持快退/快進的概念。提交到環形緩沖區的數據不能失效,除非刪除所有緩沖區。
壓縮數據 API 對于數據如何被提交給音頻 DSP 不做任何假設。從主存儲器傳輸到嵌入式音頻集群或外部 DSP 的 SPI 接口的 DMA 傳輸都是可能的。與在 ALSA PCM 的情況中一樣,暴露了一組核心例程;每個驅動程序實現者都必須編寫對一組強制例程的支持,并可能使用可選例程。
主要補充內容是
get_caps
這個例程返回支持的音頻格式列表。在采集流上查詢 codecs 將返回編碼器,對播放流則將列出解碼器。
get_codec_caps
對于每個 codec,這個例程返回能力 (capabilities) 的列表。這個例程的意圖是確保所有的能力都對應于有效的設置,并最小化配置失敗的風險。例如,對于諸如 AAC 之類的復雜編解碼器,支持的通道數可能取決于特定的配置 (profile)。如果通過單個描述符來暴露能力 (capabilities),可能發生配置 (profile)/通道數/格式的特定結合無法支持的情況。同樣,嵌入式 DSP 的內存和 CPU 周期有限,某些實現可能會使能力 (capabilities) 列表變得動態并依賴于現有工作負載。除了編解碼器設置之外,此例程還返回實現處理的最小緩沖區大小。該信息可以是 DMA 緩沖區大小、同步所需的字節數等的函數,并且可以由用戶空間使用來定義在開始播放之前需要在環形緩沖區中寫入多少內容。
set_params
這個例程設置為特定編解碼器選擇的配置。參數中最重要的字段是編解碼器類型;在大多數情況下解碼器將忽略其它參數,而編碼器將與設置保持嚴格一致。
get_params
這個例程返回 DSP 使用的實際設置。對設置的更改應該仍然是例外。
get_timestamp
時間戳變成多字段結構。它列出了傳輸的字節數、處理的采樣數以及渲染/采集的采樣數。所有這些值均可用于確定平均比特率、確定環形緩沖區是否需要重新填充或由于 DSP 上的解碼/編碼/IO 導致的延遲。
請注意,編解碼器/配置 (profile)/模式列表源自 OpenMAX AL 規范,而不是重新發明輪子。修改包括:
- 添加 FLAC 和 IEC 格式
- 編碼器/解碼器能力 (capabilities) 的合并
- 配置 (profile)/模式列表為位掩碼,使描述符更加緊湊
- 為解碼器添加 set_params (在 OpenMAX AL 中缺失)
- 添加 AMR/AMR-WB 編碼模式 (在 OpenMAX AL 中缺失)
- 為 WMA 添加格式信息
- 需要時添加編碼選項 (源自 OpenMAX AL)
- 添加 rateControlSupported (在 OpenMAX AL 中缺失)
狀態機
壓縮音頻流狀態機描述如下
+----------+| || OPEN || |+----------+||| compr_set_params()|vcompr_free() +----------+
+------------------------------------| |
| | SETUP |
| +-------------------------| |<-------------------------+
| | compr_write() +----------+ |
| | ^ |
| | | compr_drain_notify() |
| | | or |
| | | compr_stop() |
| | | |
| | +----------+ |
| | | | |
| | | DRAIN | |
| | | | |
| | +----------+ |
| | ^ |
| | | |
| | | compr_drain() |
| | | |
| v | |
| +----------+ +----------+ |
| | | compr_start() | | compr_stop() |
| | PREPARE |------------------->| RUNNING |--------------------------+
| | | | | |
| +----------+ +----------+ |
| | | ^ |
| |compr_free() | | |
| | compr_pause() | | compr_resume() |
| | | | |
| v v | |
| +----------+ +----------+ |
| | | | | compr_stop() |
+--->| FREE | | PAUSE |---------------------------+| | | |+----------+ +----------+
無縫播放
當播放唱片時,解碼器能夠跳過編碼器延遲和填充,并直接從一個曲目內容移動到另一個曲目內容。最終用戶可以將其視為無縫播放,因為我們在從一個曲目切換到另一個曲目時沒有靜音。
此外,由于編碼可能會產生低強度噪聲。所有類型的壓縮數據都很難達到完美的無縫效果,但對于大多數音樂內容來說效果很好。解碼器需要知道編碼器延遲和編碼器填充。所以我們需要將其傳遞給 DSP。該元數據是從 ID3/MP4 頭中提取的,默認情況下不存在于比特流中,因此需要一個新的接口來將此信息傳遞給 DSP。此外,DSP 和用戶空間需要從一個曲目切換到另一個曲目,并開始使用第二個曲目的數據。
主要補充內容是:
set_metadata
該例程設置編碼器延遲和編碼器填充。解碼器可以使用它來去除靜音。這需要在寫入曲目中的數據之前進行設置。
set_next_track
該例程告訴 DSP,在此之后發送的元數據和寫入操作將對應于后續曲目。
partial_drain
當到達文件末尾時調用此函數。用戶空間可以通知 DSP 已達到 EOF,現在 DSP 可以開始跳過填充延遲。下一次寫入數據也將屬于下一個曲目。
無縫播放的順序流程為:
- 打開
- 獲得能力 (caps)/編解碼器能力 (caps)
- 設置參數
- 設置第一首曲目的元數據
- 填充第一首曲目的數據
- 觸發啟動
- 用戶空間結束所有的發送,
- 通過發送 set_next_track 指示下一首曲目的數據,
- 設置下一首曲目的元數據
- 然后調用 partial_drain 刷新 DSP 中緩沖區的大部分
- 填充下一首曲目的數據
- DSP 切換到第二首曲目
(注意:partial_drain 和寫入下一首曲目的數據的順序也可以反過來)
無縫播放狀態機
對于無縫播放,我們從運行狀態轉移到部分耗盡狀態并返回,同時設置元數據和下一首曲目的信號
+----------+compr_drain_notify() | |
+------------------------>| RUNNING |
| | |
| +----------+
| |
| |
| | compr_next_track()
| |
| V
| +----------+
| compr_set_params() | |
| +-----------|NEXT_TRACK|
| | | |
| | +--+-------+
| | | |
| +--------------+ |
| |
| | compr_partial_drain()
| |
| V
| +----------+
| | |
+------------------------ | PARTIAL_ || DRAIN |+----------+
不支持
-
支持 VoIP/電路交換呼叫不是此 API 的目標。支持動態比特率變化需要 DSP 和主機堆棧之間的緊密耦合,從而限制了節能。
-
不支持丟包隱藏。這將需要一個額外的接口,以便解碼器在傳輸過程中丟失幀時合成數據。將來可能會添加此功能。
-
這個 API 不處理音量控制/路由。公開壓縮數據接口的設備將被視為常規 ALSA 設備;改變音量和路由信息將通過常規 ALSA kcontrol 提供。
-
嵌入式音效。無論輸入是 PCM 還是壓縮的,都應以相同的方式啟用此類音效。
-
多通道 IEC 編碼。不清楚是否需要這樣做。
-
如上所述,不支持編碼/解碼加速。可以將解碼器的輸出路由到采集流,甚至實現轉碼功能。此路由將通過 ALSA kcontrol 啟用。
-
音頻策略/資源管理。該 API 不提供任何掛鉤來查詢音頻 DSP 的利用率,也不提供任何搶占機制。
-
沒有 underrun/overrun 的概念。由于寫入的字節本質上是壓縮的,并且寫入/讀取的數據不會及時直接轉換為渲染輸出,因此這不會處理 underrun/overrun 問題,可能會在用戶庫中處理
作者
- Mark Brown 和 Liam Girdwood 討論了此 API 的需求
- Harsha Priya 在 intel_sst 壓縮 API 方面的工作
- Rakesh Ughreja 提供了寶貴的反饋
- Sing Nallasellan、Sikkandar Madar 和 Prasanna Samaga 在真實平臺上演示并量化了音頻卸載的優勢。
原文
Done.