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 サービスによって定期的にリクエストされ、アトミック メッセージ キューを介してクライアントのタイミング モデルに渡されます。
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 メモリ ライブラリを使用することで生成できます。
詳細については、以下の外部リソースをご覧ください。
- Android ION メモリ アロケータ: https://lwn.net/Articles/480055/
- Android ION の概要: https://wiki.linaro.org/BenjaminGaignard/ion
- 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);