AAudio 和 MMAP

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

支持 AAudio MMAP 需要:

  • 報告 HAL 的 MMAP 功能
  • 在 HAL 中實現新功能
  • 可選地為 EXCLUSIVE 模式緩衝區實現自定義 ioctl()
  • 提供額外的硬件數據路徑
  • 設置啟用 MMAP 功能的系統屬性

A音頻架構

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

AAudio 提供低延遲的數據路徑。在獨占模式下,該功能允許客戶端應用程序代碼直接寫入與 ALSA 驅動程序共享的內存映射緩衝區。在 SHARED 模式下,MMAP 緩衝區由運行在 AudioServer 中的混音器使用。在 EXCLUSIVE 模式下,延遲顯著減少,因為數據繞過了混音器。

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

在下圖中,我們可以看到脈衝編碼調製 (PCM) 數據通過 MMAP FIFO 向下流入 ALSA 驅動程序。時間戳由 AAudio 服務定期請求,然後通過原子消息隊列傳遞到客戶端的時序模型。

PCM數據流圖。
圖 1.通過 FIFO 到 ALSA 的 PCM 數據流

在 SHARED 模式下,也使用了計時模型,但它存在於 AAudioService 中。

對於音頻捕獲,使用了類似的模型,但 PCM 數據流向相反。

HAL 更改

對於 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);

通過調用 Tinyalsa 函數可以打開和關閉 MMAP 流。

查詢 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 也可以使用它。

可以使用 Android Ion 內存庫生成anon_inode:dmabuf文件描述符。

有關其他信息,請參閱以下外部資源:

  1. “Android ION 內存分配器” https://lwn.net/Articles/480055/
  2. “Android ION 概述” https://wiki.linaro.org/BenjaminGaignard/ion
  3. “集成 ION 內存分配器” https://lwn.net/Articles/565469/

HAL 更改

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 啟用 AUTO 模式:

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);