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 インターフェースを示しています。
図 1. Android 9 以前のカメラ HAL インターフェース
図 2. バッファ管理 API を使用している Android 10 のカメラ HAL インターフェース
バッファ管理 API の実装
バッファ管理 API を実装するには、カメラ HAL に以下が必要です。
- HIDL
ICameraDevice@3.5
を実装する。 - カメラ特性キーの
android.info.supportedBufferManagementVersion
をHIDL_DEVICE_3_5
に設定する。
カメラ HAL では、ICameraDeviceCallback.hal
で requestStreamBuffers
メソッドと returnStreamBuffers
メソッドを使用し、バッファをリクエストして返します。また、HAL では ICameraDeviceSession.hal
で signalStreamFlush
メソッドを実装し、カメラ HAL にバッファを返すように通知する必要があります。
requestStreamBuffers
requestStreamBuffers
メソッドを使用して、カメラ フレームワークからバッファをリクエストします。カメラ HAL3 バッファ管理 API を使用する場合、カメラ フレームワークからのキャプチャ リクエストには出力バッファが含まれないため、StreamBuffer
の bufferId
フィールドが 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++ コードで実装しています。