AAudio

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

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

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

オーディオ ストリーム

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 データ型
AAUDIO_FORMAT_PCM_I16 int16_t 一般的な 16 ビットサンプル、Q0.15 形式
AAUDIO_FORMAT_PCM_FLOAT float -1.0 ~ +1.0

AAudio がそれ自体でサンプル変換を実行することがあります。 たとえば、アプリは FLOAT データを書き込んでいて、HAL が PCM_I16 を使用している場合、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 つの安定状態のいずれかにあります(エラー状態の「切断済み」については後述します)。

  • オープン
  • 開始済み
  • 一時停止済み
  • フラッシュ済み
  • 停止済み

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

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

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

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

  • 開始中
  • 一時停止中
  • フラッシュ中
  • 停止中
  • クローズ中

下の状態図では、安定状態を角の丸い四角形で、過渡状態を点線の四角形で表しています。 図にはありませんが、どの状態からでも 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() を呼び出さないでください。

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

ストリームが開始されたら、関数の AAudioStream_read(stream, buffer, numFrames, timeoutNanos)AAudioStream_write(stream, buffer, numFrames, timeoutNanos) を使用して、そのストリームに対して読み取りまたは書き込むを行うことができます。

指定された数のフレームを転送するブロック読み取りまたは書き込みの場合は、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 ストリームベースの関数で使用できなくなります。

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

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

  • 関連するオーディオ端末の接続が解除された(ヘッドフォンが引き抜かれた場合など)。
  • 内部エラーが発生した。
  • オーディオ端末がプライマリ オーディオ端末ではなくなった。

ストリームが切断されると、その状態は「切断済み」になり、write() などの関数を実行しようとすると AAUDIO_ERROR_DISCONNECTED が返されます。 ストリームが切断されたら、やれることはそれを閉じることだけです。

オーディオ端末が切断された時点で通知を受け取りたい場合は、AAudioStream_errorCallback 関数を書き込んで、それを AAudioStreamBuilder_setErrorCallback() を使用して登録します。

下の例に示すように、コールバックでストリームの状態をチェックする必要があります。 コールバックからストリームを閉じたり、開き直したりせずに、別のスレッドを使用する必要があります。 新しいストリームを開いた場合は、元のストリームとは異なる特性(framesPerBurst など)を持っている可能性があることに注意してください。

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error){

  aaudio_stream_state_t streamState = AAudioStream_getState(stream);
  if (streamState == AAUDIO_STREAM_STATE_DISCONNECTED){
    // Handle stream disconnect on a separate thread
    ...
  }
}

パフォーマンスの最適化

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

レイテンシを最小化するためのバッファの調整

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

注: AAudio の内部バッファと AAudio ストリーム読み取り / 書き込み関数のバッファ パラメータを混同しないでください。

バッファの「容量」は、バッファで保持できるデータの総量です。 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() 以外)

注: ストリームでコールバック関数を使用している場合は、コールバック スレッドからの読み取り / 書き込みを実行中のスレッドからストリームを閉じても安全です。

コードサンプル

2 つの小型の AAudio デモアプリを GitHub ページから入手できます。

  • Hello-Audio は、正弦波を生成して、オーディオを再生します。
  • Echo は、入出力ラウンドトリップ オーディオ ループの実装方法を示します。 2 つのストリームを同期させるコールバック関数を使用し、連続バッファ調整を実行します。

OpenSL ES を使用した最適な出力レイテンシの実現とオーディオの不具合の回避の詳細については、簡易同期をご覧ください。

既知の問題点

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