AAudio と MMAP

AAudio は、Android 8.0 リリースで導入されたオーディオ API です。Android 8.1 リリースでは機能が強化され、MMAP をサポートする HAL とドライバを併用することで、レイテンシを削減できます。このドキュメントでは、Android で AAudio の MMAP 機能をサポートするために必要なハードウェア抽象化レイヤ(HAL)とドライバの変更について説明します。

AAudio MMAP をサポートするには、以下を行う必要があります。

  • HAL の MMAP 機能をレポートする
  • HAL に新しい関数を実装する
  • (オプション)EXCLUSIVE モードのバッファ用にカスタム ioctl() を実装する
  • ハードウェア データパスを追加する
  • MMAP 機能を有効にするシステム プロパティを設定する

AAudio のアーキテクチャ

AAudio は、Open SL ES の代替となる新しいネイティブ C API です。Builder デザイン パターンを使用してオーディオ ストリームを作成します。

AAudio は、低レイテンシのデータパスを提供します。EXCLUSIVE モードでは、ALSA ドライバと共有されるメモリマップ バッファに、クライアント アプリのコードを直接書き込むことができます。SHARED モードでは、MMAP バッファは AudioServer で実行されるミキサーで使用されます。EXCLUSIVE モードでは、データがミキサーをバイパスするため、レイテンシが大幅に削減されます。

EXCLUSIVE モードでは、サービスは HAL から MMAP バッファをリクエストし、リソースを管理します。MMAP バッファは NOIRQ モードで実行されます。したがって、バッファへのアクセスを管理するために読み取り / 書き込みカウンタが共有されることはありません。代わりに、クライアントはハードウェアのタイミング モデルを管理して、バッファがいつ読み取られるかを予測します。

下記の図は、MMAP FIFO 経由で ALSA ドライバに送信されるパルス符号変調(PCM)データのフローを示しています。タイムスタンプは 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 Audio 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)に設定する必要があります。これにより、MMAP モードが Audiio HAL でサポートされていることがオーディオ フレームワークに認識されます(後述の「AAudio MMAP データパスを有効化する」をご覧ください)。

MMAP クライアントが作成されたときに開始されるストリームを Audio Policy Manager が認識できるように、audio_policy_configuration.xml ファイルには、MMAP / NOIRQ モードに固有の出力および入力プロファイルも含める必要があります。次の例をご覧ください。

<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 の位置をクエリする

タイミング モデルに返されるタイムスタンプには、フレーム位置と MONOTONIC 時間がナノ秒単位で格納されます。

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

Tinyalsa 関数を新たに呼び出すことで、HAL はこの情報を ALSA ドライバから取得できます。

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

共有メモリのファイル記述子

AAudio MMAP データパスは、ハードウェアとオーディオ サービス間で共有されるメモリ領域を使用します。共有メモリは、ALSA ドライバによって生成されたファイル記述子で参照されます。

カーネルの変更

このファイル記述子は、/dev/snd/ ドライバ ファイルに直接関連付けられている場合、SHARED モードの AAudio サービスで使用できます。ただし、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/

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

デバイスの起動後に、これらの値をオーバーライドすることもできます。 変更を反映させるには、audioserver を再起動する必要があります。 たとえば、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);