AAudio 和 MMAP

AAudio 是 Android 8.0 版本中引入的音訊 API。 Android 8.1 版本具有增強功能,可減少與支援 MMAP 的 HAL 和驅動程式結合使用時的延遲。本文檔描述了在 Android 中支援 AAudio 的 MMAP 功能所需的硬體抽象層 (HAL) 和驅動程式變更。

支援 AAudio MMAP 需要:

  • 報告 HAL 的 MMAP 功能
  • 在 HAL 中實現新功能
  • 可以選擇為 EXCLUSIVE 模式緩衝區實作自訂 ioctl()
  • 提供額外的硬體資料路徑
  • 設定啟用 MMAP 功能的系統屬性

AA音訊架構

AAudio是一種新的原生 C API,提供 Open SL ES 的替代方案。它使用建構器設計模式來創建音訊串流。

AAudio 提供低延遲資料路徑。在獨佔模式下,此功能允許客戶端應用程式程式碼直接寫入與 ALSA 驅動程式共享的記憶體映射緩衝區。在共享模式下,MMAP 緩衝區由音訊伺服器中執行的混音器使用。在 EXCLUSIVE 模式下,延遲顯著減少,因為資料繞過混頻器。

在 EXCLUSIVE 模式下,服務從 HAL 要求 MMAP 緩衝區並管理資源。 MMAP 緩衝區在 NOIRQ 模式下運行,因此沒有共用讀取/寫入計數器來管理對緩衝區的存取。相反,客戶端維護硬體的時序模型並預測何時讀取緩衝區。

在下圖中,我們可以看到脈衝編碼調變 (PCM) 資料透過 MMAP FIFO 向下流入 ALSA 驅動程式。 AAudio 服務會定期要求時間戳,然後透過原子訊息佇列傳遞到客戶端的計時模型。

PCM 資料流程圖。
圖 1. PCM 資料透過 FIFO 流向 ALSA

在共享模式下,也使用時序模型,但它位於 AAudioService 中。

對於音訊捕獲,使用類似的模型,但 PCM 資料流向相反。

哈爾變化

關於tinyALSA,請參閱:

external/tinyalsa/include/tinyalsa/asoundlib.h
external/tinyalsa/include/tinyalsa/pcm.c
int pcm_start(struct pcm *pcm);
int pcm_stop(struct pcm *pcm);
int pcm_mmap_begin(struct pcm *pcm, void **areas,
           unsigned int *offset,
           unsigned int *frames);
int pcm_get_poll_fd(struct pcm *pcm);
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset,
           unsigned int frames);
int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr,
           struct timespec *tstamp);

舊版 HAL,請參閱:

hardware/libhardware/include/hardware/audio.h
hardware/qcom/audio/hal/audio_hw.c
int start(const struct audio_stream_out* stream);
int stop(const struct audio_stream_out* stream);
int create_mmap_buffer(const struct audio_stream_out *stream,
                        int32_t min_size_frames,
                        struct audio_mmap_buffer_info *info);
int get_mmap_position(const struct audio_stream_out *stream,
                        struct audio_mmap_position *position);

對於 HIDL 音訊 HAL:

hardware/interfaces/audio/2.0/IStream.hal
hardware/interfaces/audio/2.0/types.hal
hardware/interfaces/audio/2.0/default/Stream.h
start() generates (Result retval);
stop() generates (Result retval) ;
createMmapBuffer(int32_t minSizeFrames)
       generates (Result retval, MmapBufferInfo info);
getMmapPosition()
       generates (Result retval, MmapPosition position);

報告 MMAP 支持

系統屬性「aaudio.mmap_policy」應設定為 2 (AAUDIO_POLICY_AUTO),以便音訊框架知道音訊 HAL 支援 MMAP 模式。 (請參閱下面的「啟用 AAudio MMAP 資料路徑」。)

audio_policy_configuration.xml 文件還必須包含特定於 MMAP/NO IRQ 模式的輸出和輸入配置文件,以便音訊策略管理器知道建立 MMAP 用戶端時要開啟哪個流:

<mixPort name="mmap_no_irq_out" role="source"
            flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>

<mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>

開啟和關閉 MMAP 流

createMmapBuffer(int32_t minSizeFrames)
            generates (Result retval, MmapBufferInfo info);

MMAP 流可以透過呼叫 Tinyalsa 函數來開啟和關閉。

查詢MMAP位置

傳回計時模型的時間戳包含幀位置和單調時間(以奈秒為單位):

getMmapPosition()
        generates (Result retval, MmapPosition position);

HAL 可以透過呼叫新的 Tinyalsa 函數從 ALSA 驅動程式取得此資訊:

int pcm_mmap_get_hw_ptr(struct pcm* pcm,
                        unsigned int *hw_ptr,
                        struct timespec *tstamp);

共享記憶體的檔案描述符

AAudio MMAP 資料路徑使用硬體和音訊服務之間共享的記憶體區域。使用 ALSA 驅動程式產生的檔案描述符引用共享記憶體。

核心變化

如果檔案描述子直接與/dev/snd/驅動程式檔案關聯,則 AAudio 服務可以在 SHARED 模式下使用它。但對於 EXCLUSIVE 模式,描述符無法傳遞給客戶端程式碼。 /dev/snd/檔案描述符將為客戶端提供過於廣泛的存取權限,因此它會被 SELinux 阻止。

為了支援 EXCLUSIVE 模式,需要將/dev/snd/描述符轉換為anon_inode:dmabuf檔案描述子。 SELinux 允許將該檔案描述符傳遞給客戶端。 AAudioService 也可以使用它。

anon_inode:dmabuf檔案描述子可以使用 Android Ion 記憶體庫產生。

有關更多信息,請參閱以下外部資源:

  1. “Android ION 記憶體分配器” https://lwn.net/Articles/480055/
  2. “Android ION 概述” https://wiki.linaro.org/BenjaminGaignard/ion
  3. “整合 ION 記憶體分配器” https://lwn.net/Articles/565469/

哈爾變化

AAudio 服務需要知道是否支援anon_inode:dmabuf 。在 Android 10.0 之前,唯一的方法是將 MMAP 緩衝區的大小作為負數傳遞,例如。 -2048 而不是 2048(如果支援)。在 Android 10.0 及更高版本中,您可以設定AUDIO_MMAP_APPLICATION_SHAREABLE標誌。

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

音頻子系統的變化

AAudio 在音訊子系統的音訊前端需要一個額外的資料路徑,以便它可以與原始 AudioFlinger 路徑並行操作。此舊路徑用於所有其他系統聲音和應用程式聲音。此功能可由 DSP 中的軟體混合器或 SOC 中的硬體混合器提供。

啟用 AAudio MMAP 資料路徑

如果不支援 MMAP 或無法開啟串流,AAudio 將使用舊版 AudioFlinger 資料路徑。因此 AAudio 將與不支援 MMAP/NOIRQ 路徑的音訊設備一起使用。

在測試 AAudio 的 MMAP 支援時,了解您是否真正測試 MMAP 資料路徑或僅測試舊資料路徑非常重要。以下介紹如何啟用或強制指定資料路徑,以及如何查詢流所使用的路徑。

系統屬性

您可以透過系統屬性設定 MMAP 策略:

  • 1 = AAUDIO_POLICY_NEVER - 僅使用舊路徑。甚至不要嘗試使用 MMAP。
  • 2 = AAUDIO_POLICY_AUTO - 嘗試使用 MMAP。如果失敗或不可用,則使用舊路徑。
  • 3 = AAUDIO_POLICY_ALWAYS - 僅使用 MMAP 路徑。不要退回到遺留路徑。

這些可以在設備 Makefile 中設置,如下所示:

# Enable AAudio MMAP/NOIRQ data path.
# 2 is AAUDIO_POLICY_AUTO so it will try MMAP then fallback to Legacy path.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2
# Allow EXCLUSIVE then fall back to SHARED.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_exclusive_policy=2

您也可以在裝置啟動後覆寫這些值。您需要重新啟動音訊伺服器才能使變更生效。例如,若要為 MMAP 啟用自動模式:

adb root
adb shell setprop aaudio.mmap_policy 2
adb shell killall audioserver

ndk/sysroot/usr/include/aaudio/AAudioTesting.h中提供了一些函數,讓您可以覆蓋使用 MMAP 路徑的策略:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

若要找出流是否正在使用 MMAP 路徑,請呼叫:

bool AAudioStream_isMMapUsed(AAudioStream* stream);