カメラ HAL3 バッファ管理 API

Android 10 では、バッファ管理ロジックを実装できる、オプションのカメラ HAL3 バッファ管理 API が導入されています。これにより、異なるメモリとキャプチャ レイテンシのトレードオフがカメラの HAL 実装で可能になります。

カメラ HAL では、パイプラインで N 個のリクエスト(N はパイプラインの深さに等しい)がキューイングされている必要があります。ただし、N セットの出力バッファのすべてが同時には必要でないこともあります。

たとえば、HAL で 8 つのリクエストがパイプラインにキューイングされていても、パイプラインの最後のステージで必要なのは 2 つのリクエスト用の出力バッファのみである場合があります。Android 9 以前を搭載したデバイスでは、カメラ フレームワークによりリクエストが HAL にキューイングされた時点でバッファが割り当てられるため、HAL には使用されていないバッファが 6 セットできることになります。Android 10 であれば、カメラ HAL3 バッファ管理 API を使用することで、出力バッファを分割して 6 セットのバッファを解放できます。これにより、高性能デバイスでは数百メガバイトのメモリが節約できます。また、メモリの少ないデバイスにもメリットがあります。

図 1 は、Android 9 以前を搭載したデバイスのカメラ HAL インターフェースの図を示しています。図 2 は、カメラ HAL3 バッファ管理 API が実装された Android 10 のカメラ HAL インターフェースを示しています。

9 以前のバッファ管理

図 1. Android 9 以前のカメラ HAL インターフェース

Android 10 のバッファ管理

図 2. バッファ管理 API を使用している Android 10 のカメラ HAL インターフェース

バッファ管理 API の実装

バッファ管理 API を実装するには、カメラ HAL に以下が必要です。

カメラ HAL では、ICameraDeviceCallback.halrequestStreamBuffers メソッドと returnStreamBuffers メソッドを使用し、バッファをリクエストして返します。また、HAL では ICameraDeviceSession.halsignalStreamFlush メソッドを実装し、カメラ HAL にバッファを返すように通知する必要があります。

requestStreamBuffers

requestStreamBuffers メソッドを使用して、カメラ フレームワークからバッファをリクエストします。カメラ HAL3 バッファ管理 API を使用する場合、カメラ フレームワークからのキャプチャ リクエストには出力バッファが含まれないため、StreamBufferbufferId フィールドが 0 になります。したがって、カメラ HAL は requestStreamBuffers を使用して、カメラ フレームワークからバッファをリクエストする必要があります。

requestStreamBuffers メソッドを使用すると、呼び出し元は 1 回の呼び出しで複数の出力ストリームから複数のバッファをリクエストできるため、HIDL IPC 呼び出しの回数を減らすことができます。ただし、複数のバッファが同時にリクエストされると呼び出しに時間がかかり、リクエストしてから結果が出るまでの全体のレイテンシに悪影響が出る恐れがあります。また、requestStreamBuffers への呼び出しはカメラサービスでシリアル化されるため、カメラ HAL で専用の高優先度スレッドを使用してバッファをリクエストすることをおすすめします。

バッファ リクエストが失敗した場合、カメラ HAL で致命的でないエラーを適切に処理できる必要があります。バッファのリクエストが失敗する一般的な理由と、カメラ HAL での処理方法を次のリストに示します。

  • アプリから出力ストリームへの接続の切断: これは致命的でないエラーです。カメラ HAL では、切断されたストリームをターゲットとするキャプチャ リクエストに対して ERROR_REQUEST を送信し、後続のリクエストを通常どおり処理できるようにする必要があります。
  • タイムアウト: これは、アプリがバッファを保持しながら集中的に処理を実行してビジー状態となる場合に発生します。カメラ HAL では、タイムアウト エラーにより実行できないキャプチャ リクエストに対して ERROR_REQUEST を送信し、後続のリクエストを通常どおり処理できるようにする必要があります。
  • カメラ フレームワークで新しいストリーム構成を準備: カメラ HAL では、次の configureStreams 呼び出しが完了するまで待機したうえで、もう一度 requestStreamBuffers を呼び出す必要があります。
  • カメラ HAL でバッファ制限maxBuffers フィールド)に達している: カメラ HAL では、ストリームのバッファを少なくとも 1 つ返すまで待機したうえで、もう一度 requestStreamBuffers を呼び出す必要があります。

returnStreamBuffers

returnStreamBuffers メソッドを使用して、余分なバッファをカメラ フレームワークに返します。カメラ HAL では通常、processCaptureResult メソッドを使用してバッファをカメラ フレームワークに返しますが、このメソッドが使用できるのはカメラ HAL に送信されたキャプチャ リクエストに対してのみです。requestStreamBuffers メソッドを使用すると、カメラ フレームワークがリクエストしたバッファ以上のバッファを保持できるようなカメラ HAL の実装が可能です。returnStreamBuffers メソッドを使用するのは、このような場合ですが、HAL でリクエストされたバッファより多くのバッファを保持しない場合には、カメラ HAL 実装で returnStreamBuffers メソッドを呼び出す必要はありません。

signalStreamFlush

signalStreamFlush メソッドはカメラ フレームワークによって呼び出され、HAL で保持されているすべてのバッファを返すように通知します。このメソッドは通常、カメラ フレームワークで configureStreams を呼び出して、カメラ キャプチャ パイプラインを空にする必要があるときに呼び出されます。returnStreamBuffers メソッドと同様に、カメラ HAL でリクエストされたバッファより多くのバッファを保持しない場合には、このメソッドを指定せずに実装することが可能です。

カメラ フレームワークで signalStreamFlush が呼び出されると、フレームワークはすべてのバッファがカメラ フレームワークに返されるまで、カメラ HAL への新しいキャプチャ リクエストの送信を停止します。すべてのバッファが返されると、requestStreamBuffers メソッドの呼び出しが失敗し、カメラ フレームワークでクリーンな状態での処理が続行できます。その後、カメラ フレームワークでは configureStreams メソッドまたは processCaptureRequest メソッドが呼び出されます。カメラ フレームワークで configureStreams メソッドが呼び出された場合、configureStreams 呼び出しが正常に返された時点で、カメラ HAL でのバッファのリクエストが再開できるようになります。カメラ フレームワークで processCaptureRequest メソッドが呼び出された場合は、processCaptureRequest の呼び出し中に、カメラ HAL でのバッファのリクエストが開始できます。

signalStreamFlush メソッドと flush メソッドでは、セマンティクスが異なります。flush メソッドが呼び出されると、できるだけ早くパイプラインを空にするために、HAL ではERROR_REQUEST で保留中のキャプチャ リクエストが中止されます。signalStreamFlush メソッドが呼び出された場合には、HAL で保留中のキャプチャ リクエストをすべて正常に終了させ、カメラ フレームワークにすべてのバッファを返す必要があります。

signalStreamFlush メソッドとその他のメソッドのもう 1 つの違いは、signalStreamFlush が単方向の HIDL メソッドである点です。これは、HAL が signalStreamFlush 呼び出しを受け取る前に、カメラ フレームワークが他のブロッキング API を呼び出す可能性があることを意味します。つまり、signalStreamFlush メソッドとその他のメソッド(具体的には configureStreams メソッド)が、カメラ フレームワークで呼び出された順序とは異なる順序でカメラ HAL に到着する可能性があります。この非同期の問題に対処するために、streamConfigCounter フィールドが StreamConfiguration に追加され、引数として signalStreamFlush メソッドに追加されました。カメラ HAL を実装する際には、signalStreamFlush 呼び出しの到着を対応する configureStreams 呼び出しの後にするかどうかを、streamConfigCounter 引数を使用して定めておく必要があります。図 3 の例をご覧ください。

遅れて到着する呼び出しの処理

図 3. カメラ HAL が、遅れて到着する signalStreamFlush 呼び出しを検出して処理する方法

バッファ管理 API の実装時の動作変更

バッファ管理 API を使用してバッファ管理ロジックを実装する場合、カメラとカメラ HAL の実装に際して、次のような動作変更の生じる可能性があります。

  • カメラ HAL へのキャプチャ リクエストの送信速度、回数が増加する: バッファ管理 API を使用しない場合、カメラ フレームワークは各キャプチャ リクエストの出力バッファをリクエストしてから、カメラ HAL にキャプチャ リクエストを送信します。バッファ管理 API を使用すると、カメラ フレームワークでバッファを待つ必要がなく、キャプチャ リクエストをカメラ HAL に先に送信できます。

    また、バッファ管理 API を使用しない場合は、キャプチャ リクエストの出力ストリームのうちのいずれかが HAL で一度に保持できる最大バッファ数(この値は、configureStreams 呼び出しの戻り値の HalStream::maxBuffers フィールドでカメラ HAL によって指定されています)に達すると、カメラ フレームワークでのキャプチャ リクエストの送信が停止されます。バッファ管理 API を使用する場合にはこのような制限動作がないため、キューに登録されたキャプチャ リクエストの数が多すぎるときには、カメラ HAL で processCaptureRequest 呼び出しを受け付けないように実装する必要があります。

  • requestStreamBuffers 呼び出しのレイテンシが大きく異なる: さまざまな理由により、requestStreamBuffers 呼び出しに平均よりも時間が長くかかることがあります。次に例を示します。

    • 新たに作成されたストリームの最初の数バッファでは、デバイスでメモリの割り当てが必要になるため、呼び出しに時間がかかります。
    • 予想されるレイテンシは、各呼び出しでリクエストされたバッファの数に比例して増加します。
    • アプリがバッファを保持し、処理がビジー状態となると、バッファの不足や CPU の動作過多が原因で、バッファ リクエストが遅くなったりタイムアウトになったりすることがあります。

バッファ管理戦略

バッファ管理 API では、さまざまな種類のバッファ管理戦略を実装できます。以下にいくつか例を示します。

  • 下位互換性: HAL で、processCaptureRequest 呼び出しの間にキャプチャ リクエストのバッファをリクエストします。この方法ではメモリを節約することはできませんが、既存のカメラ HAL へのコード変更がほとんど必要なく、バッファ管理 API の最初の実装に適しています。
  • 最大限のメモリ節約: カメラ HAL で、入力が必要になる直前にだけ出力バッファをリクエストします。この戦略により、最大限のメモリ節約が可能になります。バッファ リクエストが完了するのに非常に長い時間がかかると、カメラ パイプラインのジャンクが増える可能性があります。
  • キャッシュ: バッファ リクエストに遅延が生じた場合の影響を受けにくくするために、カメラ HAL でバッファを数回キャッシュします。

カメラ HAL では、大量のメモリを使用するユースケースに対して最大限のメモリ節約戦略を使用したり、他のユースケースに対して下位互換性戦略を使用したりするなどして、個々のユースケースに応じた戦略をとることができます。

外部カメラ HAL の実装例

外部カメラ HAL は Android 9 で導入されました。hardware/interfaces/camera/device/3.5/ のソースツリーで確認できます。これは Android 10 で更新され、バッファ管理 API の実装である ExternalCameraDeviceSession.cpp が含まれるようになりました。この外部カメラ HAL では、バッファ管理戦略で説明した最大限のメモリ節約戦略を数百行の C++ コードで実装しています。