AAudio

AAudio は、Android O リリースで導入された新しい Android C API です。低レイテンシが要求される高性能なオーディオ アプリ用に設計されています。アプリは、ストリームに対してデータを読み書きすることによって AAudio と通信します。

AAudio API は意図的に機能を最小にしているため、以下の機能は実行しません。

  • オーディオ機器の列挙
  • オーディオ エンドポイント間の自動ルーティング
  • ファイル I/O
  • 圧縮されたオーディオのデコード
  • 1 回のコールバックでのすべての入力 / ストリームの自動プレゼンテーション

はじめに

AAudio は C++ コードから呼び出すことができます。AAudio 機能セットをアプリに追加するには、AAudio.h ヘッダー ファイルをインクルードします。

#include <aaudio/AAudio.h>

オーディオ ストリーム

AAudio は、Android デバイス上のアプリとオーディオ入出力間でオーディオ データをやり取りします。アプリは、構造体 AAudioStream で表現される「オーディオ ストリーム」に対して読み書きすることにより、データをやり取りします。読み取り / 書き込み呼び出しは、ブロック呼び出しにも非ブロック呼び出しにもできます。

ストリームは以下の要素によって定義されます。

  • ストリーム内のデータのソースまたはシンクである「オーディオ機器」
  • ストリームにオーディオ機器への排他的アクセス権が付与されているかどうかを特定する「共有モード」。付与されていない場合は、オーディオ機器へのアクセス権が複数のストリームで共有されます。
  • ストリーム内のオーディオ データの「形式」

オーディオ機器

各ストリームは 1 台のオーディオ機器にアタッチされます。

オーディオ機器とは、デジタル オーディオ データの連続ストリームのソースまたはシンクとして機能するハードウェア インターフェースまたは仮想エンドポイントです。アプリを実行している「Android デバイス」(スマートフォンやスマートウォッチ)と「オーディオ機器」(内蔵マイクや Bluetooth ヘッドセット)を混同しないでください。

AudioManager のメソッド getDevices() を使用して、Android デバイス上で使用可能なオーディオ機器を検出できます。このメソッドは、各デバイスの type に関する情報を返します。

オーディオ機器ごとに、Android デバイス上で一意の ID が割り当てられます。この ID を使用して、オーディオ ストリームを特定のオーディオ機器にバインドすることができます。ほとんどの場合、デフォルトのメイン機器は、自分で指定するのではなく、AAudio に選択させることができます。

ストリームが入力用か出力用かは、ストリームにアタッチされているオーディオ機器が判断します。ストリームは一方向にしかデータを移動することができません。ストリームを定義するときに、その方向も設定します。ストリームを開くと、Android がオーディオ機器とストリームの方向が一致していることを確認します。

共有モード

ストリームには共有モードがあります。

  • AAUDIO_SHARING_MODE_EXCLUSIVE は、ストリームにオーディオ機器への排他的アクセス権が付与されていることを意味します。そのオーディオ機器は、他のオーディオ ストリームからは使用できません。オーディオ機器が使用中の場合は、ストリームに排他的アクセス権を付与できない可能性があります。排他的ストリームでは通常、レイテンシが低くなりますが、切断される可能性も高くなります。排他的ストリームは不要になったらすぐに閉じて、他のアプリがオーディオ機器にアクセスできるようにする必要があります。排他的ストリームでは可能な限りレイテンシが低くなります。
  • AAUDIO_SHARING_MODE_SHARED は、AAudio によるオーディオの混合を許可します。AAudio は、同じオーディオ機器に割り当てられたすべての共有ストリームを混合します。

ストリームを作成するときに、明示的に共有モードを設定できます。共有モードのデフォルトは SHARED です。

オーディオ形式

ストリームで渡されるデータには通常のデジタル オーディオ属性が割り当てられます。属性には次のものがあります。

  • サンプルデータ形式
  • チャンネル数(フレームあたりのサンプル数)
  • サンプルレート

AAudio は以下のサンプル形式を許可します。

aaudio_format_t C データ型 Notes
AAUDIO_FORMAT_PCM_I16 int16_t 一般的な 16 ビットサンプル、Q0.15 形式
AAUDIO_FORMAT_PCM_FLOAT float -1.0 ~ +1.0
AAUDIO_FORMAT_PCM_I24_PACKED 3 つのグループの uint8_t パックされた 24 ビットサンプル、Q0.23 形式
AAUDIO_FORMAT_PCM_I32 int32_t 一般的な 32 ビットサンプル、Q0.31 形式
AAUDIO_FORMAT_IEC61937 uint8_t HDMI または S/PDIF パススルー用に IEC61937 でラップされた圧縮オーディオ

特定のサンプル形式をリクエストすると、その形式がデバイスに最適な形式でなくても、ストリームで使用されます。サンプル形式を指定しない場合は、AAudio が最適な形式を選択します。ストリームが開いたら、サンプルデータ形式をクエリしてから、必要に応じて次の例に示すようにデータを変換します。

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

オーディオ ストリームの作成

AAudio ライブラリは、ビルダー デザイン パターンに従って AAudioStreamBuilder を提供します。

  1. AAudioStreamBuilder を作成します。

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. ストリーム パラメータに対応するビルダー関数を使用して、ビルダー内のオーディオ ストリーム設定を行います。以下のオプションの set 関数を使用できます。

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    これらのメソッドは、未定義の定数や範囲外の値などのエラーを報告しないことに注意してください。

    deviceId を指定しなかった場合のデフォルトは、メインの出力機器です。 ストリーム方向を指定しなかった場合のデフォルトは、出力ストリームです。 その他のパラメータについては、明示的に値を設定することも、システムに最適な値を割り当てさせることもできます(後者の場合、パラメータをまったく指定しないか、パラメータを AAUDIO_UNSPECIFIED に設定します)。

    念のため、作成したオーディオ ストリームの状態をチェックします(ステップ 4 を参照)。

  3. AAudioStreamBuilder が設定されている場合は、それを使用してストリームを作成します。

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

  4. ストリームを作成したら、その設定を確認します。サンプル形式、サンプルレート、フレームあたりのサンプル数を指定した場合、それらが変更されることはありません。 共有モードまたはバッファ容量を指定した場合は、ストリームのオーディオ機器とそれが動作している Android デバイスの性能に応じて、それらが変更されることがあります。優れた防御的プログラミングを行うためには、ストリームを使用する前にその設定をチェックする必要があります。以下の関数を使用して、各ビルダー設定に対応するストリーム設定を取得できます。

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()

  5. ビルダーを保存すると、後で新しいストリームを作成するために再利用できます。ただし、二度と使用しない場合は、削除する必要があります。

    AAudioStreamBuilder_delete(builder);
    

オーディオ ストリームの使用

状態遷移

AAudio ストリームは、通常、以下の 5 つの安定状態のいずれかになります(エラー状態の「切断済み(Disconnected)」については後述します)。

  • 開く
  • 開始済み(Started)
  • 一時停止済み(Paused)
  • フラッシュ済み(Flushed)
  • 停止済み(Stopped)

ストリームの状態が開始済みの場合、データはストリーム経由でしか送信されません。ストリームの状態を変更するには、状態遷移を要求する次の関数のいずれかを使用します。

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

出力ストリームに対しては一時停止かフラッシュしか要求できないことに注意してください。

これらの関数は非同期で、状態変更はすぐには起こりません。状態変更を要求すると、ストリームは以下の対応する過渡状態のいずれかに遷移します。

  • 開始中(Starting)
  • 一時停止中(Starting)
  • フラッシュ中(Flushing)
  • 停止中(Stopping)
  • クローズ中(Closing)

下の状態図では、安定状態を角の丸い四角形で、過渡状態を点線の四角形で示しています。 図にはありませんが、どの状態からでも close() を呼び出すことができます。

AAudio のライフサイクル

AAudio は、状態変更を警告するためのコールバックを提供していません。特殊な関数 AAudioStream_waitForStateChange(stream, inputState, nextState, timeout) を使用すると、状態変更を待機できます。

この関数は、自動的に状態変更を検出するわけでも、特定の状態を待機するわけでもありません。現在の状態が、指定した inputState「以外」になるまで待機します。

たとえば、一時停止を要求すると、ストリームは即座に過渡状態の一時停止中になり、しばらくして一時停止済み状態になります。ただし、この動作は保証されません。一時停止済み状態を待機することはできないため、waitForStateChange() を使用して「一時停止中以外の状態」を待機します。その方法を以下に示します。

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

ストリームの状態が一時停止中(inputState: 呼び出し時点で仮定した状態)でない場合、この関数はすぐに戻ります。そうでない場合は、状態が一時停止中でなくなるか、タイムアウトになるまでブロックされます。関数が戻ると、パラメータ nextState でストリームの現在の状態を確認できます。

開始、停止、フラッシュを要求した後にもこれと同じ手法を使用できます。その場合、対応する過渡状態を inputState として指定します。AAudioStream_close() を呼び出した後に waitForStateChange() を呼び出さないでください。ストリームはクローズするとすぐに削除されます。また、waitForStateChange() が別のスレッドで実行されているときに AAudioStream_close() を呼び出さないでください。

オーディオ ストリームに対する読み取りと書き込み

ストリーム内のデータを開始後に処理するには、次の 2 つの方法があります。

指定された数のフレームを転送するブロック読み取りまたはブロック書き込みの場合は、timeoutNanos を 0 より大きい値に設定します。 非ブロック呼び出しの場合は、timeoutNanos を 0 に設定します。この場合、実際に転送されたフレーム数が結果になります。

入力を読み取ったら、正しい数のフレームが読み取られたことを確認する必要があります。そうしないと、オーディオの不具合を引き起こす不明なデータがバッファに格納される可能性があります。バッファを 0 で埋めることによってサイレント ドロップアウトを作成できます。

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

ストリームを開始する前に、データまたは無音を書き込むことにより、ストリームのバッファを準備することができます。この作業は、timeoutNanos を 0 に設定した非ブロック呼び出しで行う必要があります。

バッファ内のデータは、AAudioStream_getDataFormat() から返されたデータ形式と一致する必要があります。

オーディオ ストリームのクローズ

ストリームの使用を終了する場合は、ストリームをクローズします。

AAudioStream_close(stream);

クローズされたストリームは AAudio ストリームベースの関数で使用できなくなります。

切断されたオーディオ ストリーム

オーディオ ストリームは、以下のイベントのいずれかが発生した場合、いつでも切断される可能性があります。

  • 関連付けられているオーディオ機器の接続が解除された(ヘッドフォンが引き抜かれた場合など)。
  • 内部エラーが発生した。
  • オーディオ機器がメインのオーディオ機器ではなくなった。

ストリームが切断されると「切断済み」(Disconnected)状態になり、AAudioStream_write() などの関数を実行しようとするとエラーが返されます。切断されたストリームは、エラーコードにかかわらず必ず停止し、終了する必要があります。

データ コールバックを使用している場合(直接読み取り / 書き込みのメソッドを使用するのではなく)にストリームが切断されると、戻りコードは送信されません。 こうした事態が発生したら、AAudioStream_errorCallback 関数を記述し、AAudioStreamBuilder_setErrorCallback() を使用してその関数を登録します。

エラーのコールバック スレッドで切断の通知を受け取った場合は、別のスレッドでストリームの停止と終了を行う必要があります。これを行わないと、デッドロックが発生します。

なお、新しいストリームをオープンすると、設定が元のストリームと異なる(framesPerBurst など)場合があります。

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error) {
    // Launch a new thread to handle the disconnect.
    std::thread myThread(my_error_thread_proc, stream, userData);
    myThread.detach(); // Don't wait for the thread to finish.
}

パフォーマンスの最適化

オーディオ アプリのパフォーマンスは、その内部バッファを調整し、特殊な高優先度スレッドを使用することによって最適化できます。

バッファの調整によるレイテンシの最小化

AAudio は、オーディオ機器ごとに 1 つずつある内部バッファとデータのやり取りをします。

バッファの「容量」は、バッファで保持できるデータの総量です。容量を設定するには、AAudioStreamBuilder_setBufferCapacityInFrames() を呼び出します。このメソッドでは、割り当て可能な容量がデバイスの最大許容値に制限されます。バッファの実際の容量を確認するには、AAudioStream_getBufferCapacityInFrames() を使用します。

アプリでバッファの全容量を使用する必要はありません。AAudio は、設定可能な「サイズ」までバッファを満たします。バッファのサイズはその容量までは設定できますが、たいていはそれより少なく設定します。バッファサイズを制御することにより、バッファを満たすのに必要なバースト数を特定し、レイテンシを制御します。バッファサイズを操作するには、AAudioStreamBuilder_setBufferSizeInFrames() メソッドと AAudioStreamBuilder_getBufferSizeInFrames() メソッドを使用します。

アプリはオーディオを再生するとき、バッファに対して書き込みを行い、書き込みが完了するまでブロックします。AAudio はバッファからの読み取りを個別のバーストで行います。各バーストは複数のオーディオ フレームで構成され、通常は読み取るバッファのサイズより小さくなります。 バーストのサイズとレートはシステムが制御します。これらのプロパティは通常、オーディオ機器の回路によって決定されます。 バーストサイズやバーストレートは変更できませんが、内部バッファのサイズはそれに含まれるバーストの数に応じて設定できます。 一般に、AAudioStream のバッファサイズが報告されたバーストサイズの倍数の場合にレイテンシが最小になります。

      AAudio のバッファリング

バッファサイズを最適化する方法の 1 つは、大きなバッファから始めて徐々に小さくしていき、不足し始めたところで少し戻す方法です。または、小さなバッファサイズから始めて、足りなければ大きくします。これを、出力がスムーズに送信されるようになるまで繰り返します。

このプロセスはすぐに実行できるので、できればユーザーが最初の音を鳴らす前に行ってください。ユーザーにオーディオの不具合を聞かせないように、最初は無音を使用して初期のバッファサイズの設定を実行することもできます。システム パフォーマンスは時間とともに変化します(ユーザーが機内モードをオフにした場合など)。バッファ調整によるオーバーヘッドの増加はほとんどないため、アプリはストリームに対してデータを読み取り / 書き込みながら繰り返しバッファ調整を行うことができます。

バッファ最適化ループの例を以下に示します。

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

この手法を入力ストリームのバッファサイズの最適化に使用してもメリットはありません。 入力ストリームはできるだけすばやく動作して、バッファデータの量を最小に保つようにします。その後、バッファがいっぱいになるとアプリがプリエンプトされます。

高優先度コールバックの使用

アプリが通常のスレッドとオーディオ データをやり取りする場合、プリエンプトされたり、タイミング ジッターが発生したりする可能性があります。これにより、オーディオの不具合が発生することがあります。 大きなバッファを使用すれば、このような不具合を防ぐことができますが、オーディオ レイテンシが高くなります。 低レイテンシが要求されるアプリの場合は、オーディオ ストリームで非同期コールバック関数を使用してアプリとデータをやり取りすることができます。AAudio は、パフォーマンスに優れた高優先度スレッドでコールバックを実行します。

コールバック関数のプロトタイプを以下に示します。

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

ストリーム構築を使用してコールバックを登録します。

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

最も単純なケースでは、ストリームが定期的にコールバック関数を実行して、その次のバーストのデータを取得します。

コールバック関数の呼び出し元のストリームでは読み取りや書き込みを行わないようにする必要があります。コールバックが入力ストリームに属している場合、コードは audioData バッファに入力されたデータ(3 つ目の引数として指定される)を処理する必要があります。コールバックが出力ストリームに属している場合は、コードでデータをバッファに格納する必要があります。

たとえば次のように、コールバックを使用して、正弦波出力を連続的に生成することができます。

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

AAudio では複数のストリームを処理することができます。1 つのストリームをマスターとして使用し、ユーザーデータで他のストリームへのポインタを渡すことができます。マスター ストリームのコールバックを登録し、その後で他のストリームに対して非ブロック I/O を使用します。出力ストリームに入力ストリームを渡すラウンドトリップ コールバックの例を以下に示します。 呼び出し元のマスター ストリームが出力ストリームです。入力ストリームはユーザーデータに含まれています。

コールバックは、入力ストリームからの非ブロック読み取りを実行し、出力ストリームのバッファにデータを格納します。

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

この例では、入力ストリームと出力ストリームのチャンネル数、形式、サンプルレートが同じであると仮定しています。コードで変換が適切に処理される限り、ストリームの形式は一致しなくてもかまいません。

パフォーマンス モードの設定

すべての AAudioStream に、アプリの動作に大きく影響する「パフォーマンス モード」があります。モードには次の 3 つがあります。

  • AAUDIO_PERFORMANCE_MODE_NONE はデフォルトのモードです。レイテンシと節電のバランスを取る基本ストリームが使用されます。
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY では、小さいサイズのバッファと、低レイテンシのために最適化されたデータパスを使用します。
  • AAUDIO_PERFORMANCE_MODE_POWER_SAVING では、大きいサイズの内部バッファと、レイテンシを犠牲にして節電を実現するデータパスを使用します。

パフォーマンス モードを選択するには setPerformanceMode() を呼び出し、現在のモードを検出するには getPerformanceMode() を呼び出します。

節電よりも低レイテンシの方が重要なアプリでは、AAUDIO_PERFORMANCE_MODE_LOW_LATENCY を使用します。 このモードは、ゲームやキーボード シンセサイザーなどのインタラクティブ性の高いアプリに有効です。

低レイテンシよりも節電の方が重要なアプリでは、AAUDIO_PERFORMANCE_MODE_POWER_SAVING を使用します。 このモードは主に、ストリーミング オーディオや MIDI ファイル プレーヤーなど、過去に制作された音楽を再生するアプリに適しています。

最新バージョンの AAudio では、できるだけレイテンシを低くするために、高優先度コールバックと一緒に AAUDIO_PERFORMANCE_MODE_LOW_LATENCY パフォーマンス モードを使用する必要があります。次の例に従ってください。

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

スレッドの安全性

AAudio API は完全なスレッドセーフではありません。 複数のスレッドから同時にいくつかの AAudio 関数を呼び出すことはできません。 これは、AAudio がスレッドのプリエンプションや不具合を引き起こす可能性のあるミューテックスの使用を回避するためです。

安全を確保するために、2 つの異なるスレッドから同じストリームに対して、AAudioStream_waitForStateChange() を呼び出したり、読み書きしたりしないでください。同様に、あるスレッドでストリームに対して読み書きしているときに、別のスレッドでそのストリームをクローズしないでください。

AAudioStream_getSampleRate()AAudioStream_getChannelCount() などのストリーム設定を返す呼び出しはスレッドセーフです。

以下の呼び出しもスレッドセーフです。

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*()AAudioStream_getTimestamp() 以外)

既知の問題

  • Android O DP2 リリースでは FAST トラックが使用されないため、オーディオ レイテンシが高くなり、write() がブロックされます。レイテンシを低くするにはコールバックを使用してください。

参考情報

詳細については、以下のリソースをご覧ください。

API リファレンス

Codelab

動画