車載カメラ HAL

Android には、自動車用 HIDL Hardware Abstraction Layer(HAL)が含まれています。この HAL は Android の起動プロセスの初期段階で画像のキャプチャと表示を行い、システムが稼働している限り機能し続けます。HAL には外部ビューシステム(EVS)スタックが含まれ、通常は Android ベースの車載インフォテインメント(IVI)システムを搭載した車両のリアビュー カメラとサラウンド ビュー ディスプレイをサポートするために使用されます。EVS により、高度な機能をユーザーアプリに実装することもできます。

Android には、EVS 固有のキャプチャとディスプレイのドライバ インターフェースも含まれています(/hardware/interfaces/automotive/evs/1.0 内)。既存の Android カメラや表示サービスの上にリアビュー カメラ アプリを構築することは可能ですが、そのようなアプリは Android の起動プロセスでの実行が非常に遅くなる可能性があります。専用の HAL を使用することでインターフェースを簡素化し、EVS スタックをサポートするために OEM が実装する必要があるものを明確にできます。

システム コンポーネント

EVS には次のシステム コンポーネントが含まれます。

EVS システム コンポーネントの図。
図 1: EVS システム コンポーネントの概要

EVS アプリ

サンプルの C++ EVS アプリ(/packages/services/Car/evs/app)はリファレンス実装として機能します。このアプリは EVS Manager に動画フレームをリクエストし、表示用の完成フレームを EVS Manager に返送します。EVS と車のサービスが利用可能になるとすぐに、電源投入後 2 秒以内を目標として、init により開始されます。OEM は必要に応じて EVS アプリを変更または交換できます。

EVS Manager

EVS Manager(/packages/services/Car/evs/manager)は、簡単なリアビュー カメラのディスプレイから 6DOF のマルチカメラ レンダリングまで、あらゆる実装に EVS アプリが必要とする構成要素を提供します。インターフェースは HIDL を介して提供され、複数のクライアントを同時に受け入れるように構築されています。他のアプリやサービス(特に車のサービス)は、EVS Manager の状態をクエリして、EVS システムがいつアクティブであるかを調べることができます。

EVS HIDL インターフェース

カメラとディスプレイの両方の要素である EVS システムは、android.hardware.automotive.evs パッケージで定義されています。インターフェースを使用する(合成テスト画像を生成し、画像の往復を検証する)サンプル実装は、/hardware/interfaces/automotive/evs/1.0/default で提供されています。

OEM は、/hardware/interfaces/automotive/evs 内の .hal ファイルで指定されている API を実装します。これらの実装により、物理カメラのデータを構成および収集し、Gralloc が認識できる共有メモリバッファを介してデータを配信します。実装の表示側は、(通常は EGL レンダリングを介して)アプリによる入力が可能な共有メモリバッファを提供し、物理ディスプレイに表示されることを求める他のフレームに優先して、完成フレームを表示します。EVS インターフェースのベンダー実装は、/vendor/… /device/… または hardware/… 配下に保存できます(/hardware/[vendor]/[platform]/evs など)。

カーネル ドライバ

EVS スタックをサポートするデバイスにはカーネル ドライバが必要です。新しいドライバを作成する代わりに、OEM は既存のカメラやディスプレイ ハードウェア ドライバで EVS の必須機能をサポートすることもできます。ドライバを再利用することは、画像を表示する際に他のアクティブなスレッドとの調整が必要なディスプレイ ドライバにとって特に有利です。Android 8.0 には、v4l2 ベースのサンプル ドライバ(packages/services/Car/evs/sampleDriver 内)が含まれています。このサンプル ドライバは、v4l2 のサポートについてはカーネルに、出力画像の表示については SurfaceFlinger に依存しています。

EVS ハードウェア インターフェースの説明

このセクションでは、HAL について説明します。ベンダーは、ハードウェアに合わせてこの API の実装を提供することが求められます。

IEvsEnumerator

このオブジェクトは、システム内の使用可能な EVS ハードウェアを列挙します(1 つ以上のカメラと単一のディスプレイ デバイス)。

getCameraList() generates (vec<CameraDesc> cameras);

システム内のあらゆるカメラの説明を含むベクターを返します。カメラのセットは起動時に固定されていて認識できるものとします。カメラの説明の詳細については、CameraDesc をご覧ください。

openCamera(string camera_id) generates (IEvsCamera camera);

固有の camera_id 文字列で識別される特定のカメラとのやり取りに使用されるインターフェース オブジェクトを取得します。失敗した場合は NULL を返します。 すでに起動しているカメラを再起動しようとしても失敗することはありません。アプリの起動やシャットダウンに伴う競合状態を回避するには、カメラの再起動により前のインスタンスをシャットダウンして、新しいリクエストを実行できるようにする必要があります。この方法でプリエンプトされたカメラ インスタンスは非アクティブ状態になり、最終的な破棄を待って、カメラの状態に影響を与えるすべてのリクエストに対して、OWNERSHIP_LOST のリターンコードで応答する必要があります。

closeCamera(IEvsCamera camera);

IEvsCamera インターフェースを解放します(openCamera() 呼び出しの逆の処理)。closeCamera を呼び出す前に、stopVideoStream() を呼び出してカメラの動画ストリームを停止する必要があります。

openDisplay() generates (IEvsDisplay display);

システムの EVS ディスプレイとの排他的なやり取りに使用されるインターフェース オブジェクトを取得します。一度に 1 つのクライアントだけが IEvsDisplay の関数インスタンスを保持できます。openCamera で説明した積極的な起動動作と同様に、新しい IEvsDisplay オブジェクトはいつでも作成でき、それによって以前のインスタンスは無効になります。無効化されたインスタンスは引き続き存在し、所有者からの関数呼び出しに応答しますが、デッドとなった場合は変更オペレーションを実行してはいけません。最終的に、クライアント アプリは、OWNERSHIP_LOST エラーのリターンコードを検知し、無効なインターフェースを閉じて解放します。

closeDisplay(IEvsDisplay display);

IEvsDisplay インターフェースを解放します(openDisplay() 呼び出しの逆の処理)。getTargetBuffer() 呼び出し経由で受け取った未処理のバッファは、ディスプレイを閉じる前にディスプレイに返す必要があります。

getDisplayState() generates (DisplayState state);

現在のディスプレイの状態を取得します。HAL 実装では、実際の現在の状態を報告する必要があります。これは直近でリクエストされた状態とは異なる場合があります。ディスプレイの状態を変更するロジックは、デバイスレイヤの上に存在する必要があります。そのため、HAL 実装でディスプレイの状態を自動的に変更することは望ましくありません。現在どのクライアントによっても(openDisplay の呼び出しによって)ディスプレイが保持されていない場合、この関数は NOT_OPEN を返します。それ以外の場合は、EVS ディスプレイの現在の状態を報告します(IEvsDisplay API をご覧ください)。

struct CameraDesc {
    string      camera_id;
    int32       vendor_flags;       // Opaque value
}
  • camera_id。特定のカメラを一意に識別する文字列。 デバイスのカーネル デバイス名や、rearview などのデバイス名を指定できます。この文字列の値は、HAL 実装によって選択され、上記のスタックで不透明に使用されます。
  • vendor_flags。特殊なカメラ情報を、ドライバからカスタム EVS アプリに不透明に渡すメソッド。ドライバから EVS アプリに解釈されずに渡されますが、無視することもできます。

IEvsCamera

このオブジェクトは、1 つのカメラを表し、画像をキャプチャするための主要なインターフェースとなります。

getCameraInfo() generates (CameraDesc info);

このカメラの CameraDesc を返します。

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

カメラがサポートする必要のあるバッファ チェーンの深度を指定します。ここで指定した深度まで、IEvsCamera のクライアントは多数のフレームを同時に保持できます。この多数のフレームが doneWithFrame によって返されることなく受信側に配信された場合、再利用のためにバッファが返されるまで、ストリームはフレームをスキップします。この呼び出しはいつでも、ストリームがすでに実行中であっても実行できます。この場合、必要に応じてチェーンからバッファを追加または削除する必要があります。このエントリ ポイントが呼び出されない場合、IEvsCamera は少なくとも 1 つのフレームをデフォルトでサポートしますが、それ以上のフレームでも受け入れ可能です。

要求された bufferCount に対応できない場合、関数は BUFFER_NOT_AVAILABLE またはその他の関連するエラーコードを返します。この場合、システムは以前に設定された値で動作し続けます。

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

このカメラからの EVS カメラフレームの配信をリクエストします。IEvsCameraStream は stopVideoStream() が呼び出されるまで、新しい画像フレームを含む定期的な呼び出しの受信を開始します。フレームは startVideoStream 呼び出しから 500 ミリ秒以内に配信開始される必要があり、配信開始後は少なくとも 10 FPS で生成される必要があります。動画ストリームを開始するのに必要な時間は、事実上すべてのリアビュー カメラの起動時間要件にとって不利に働きます。ストリームが開始されない場合はエラーコードが返されます。それ以外の場合は OK が返されます。

oneway doneWithFrame(BufferDesc buffer);

IEvsCameraStream から配信されたフレームを、IEvsCameraStream に返します。IEvsCameraStream インターフェースに配信されたフレームを消費したら、再利用のためにフレームを IEvsCamera に返す必要があります。使用可能なバッファは少数で、数が限られています(おそらく 1 つ)。バッファの供給が尽きると、バッファが返されるまでフレームが配信されず、フレームのスキップが発生する可能性があります(ハンドルが null のバッファはストリームの最後であることを示し、この関数で返される必要はありません)。成功の場合は OK を返します。それ以外の場合は、INVALID_ARG または BUFFER_NOT_AVAILABLE を含む適切なエラーコードを返します。

stopVideoStream();

EVS カメラフレームの配信を停止します。配信は非同期であるため、この呼び出しが返された後、しばらくの間フレームが到着することがあります。ストリームの終了が IEvsCameraStream に通知されるまで、各フレームを返す必要があります。すでに停止しているストリームや開始していないストリームで stopVideoStream を呼び出すことはできますが、その場合は無視されます。

getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);

HAL 実装からドライバ固有の情報をリクエストします。opaqueIdentifier に許可される値はドライバ固有ですが、渡された値によりドライバがクラッシュすることはありません。ドライバは認識されない opaqueIdentifier に対して 0 を返します。

setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);

ドライバ固有の値を HAL 実装に送信します。この拡張は車両固有の拡張を容易にするためにのみ提供され、HAL の実装ではこの呼び出しがデフォルトの状態で機能する必要はありません。ドライバが値を認識して受け入れる場合は、OK が返されます。それ以外の場合は、INVALID_ARG または他の代表的なエラーコードが返されます。

struct BufferDesc {
    uint32  width;      // Units of pixels
    uint32  height;     // Units of pixels
    uint32  stride;     // Units of pixels
    uint32  pixelSize;  // Size of single pixel in bytes
    uint32  format;     // May contain values from android_pixel_format_t
    uint32  usage;      // May contain values from Gralloc.h
    uint32  bufferId;   // Opaque value
    handle  memHandle;  // gralloc memory buffer handle
}

API を通過した画像を記述します。HAL ドライブはこの構造体に入力して画像バッファを記述し、HAL クライアントはこの構造体を読み取り専用として扱います。フィールドには、クライアントが ANativeWindowBuffer オブジェクトを再構成するのに十分な情報が含まれています。この情報は eglCreateImageKHR() 拡張経由の EGL を用いた画像を使用するために必要になる場合があります。

  • width。表示された画像のピクセル単位の幅。
  • height。表示された画像のピクセル単位の高さ。
  • stride。メモリ内で各行が実際に占めるピクセル数。行の配置のパディングを表します。Gralloc がバッファの記述に採用した規則に合わせてピクセル単位で表現されます。
  • pixelSize。各ピクセルが占めるバイト数。画像内の行間を移動するのに必要なサイズをバイト単位で計算できます(バイト単位の stride = ピクセル単位の stride * pixelSize)。
  • format。画像が使用するピクセル形式。指定された形式は、プラットフォームの OpenGL 実装に対応している必要があります。互換性テストをパスするには、HAL_PIXEL_FORMAT_YCRCB_420_SP をカメラに使用し、RGBA または BGRA をディスプレイに使用することをおすすめします。
  • usage。HAL 実装によって設定される使用フラグ。HAL クライアントは、これらのフラグを変更せずに渡すことが求められます(詳細については、Gralloc.h 関連フラグをご覧ください)。
  • bufferId。HAL API を往復した後にバッファを認識できるように、HAL 実装で指定される一意の値。このフィールドに格納される値は、HAL 実装によって任意に選択できます。
  • memHandle。画像データを格納する、基盤となるメモリバッファのハンドル。HAL 実装では、Gralloc のバッファ ハンドルをここに格納することもあります。

IEvsCameraStream

クライアントはこのインターフェースを実装して、非同期の動画フレームの配信を受け取ります。

deliverFrame(BufferDesc buffer);

動画フレームの検査準備が完了するたびに、HAL からの呼び出しを受け取ります。 このメソッドが受け取るバッファ ハンドルは、IEvsCamera::doneWithFrame() の呼び出しによって返される必要があります。動画ストリームが IEvsCamera::stopVideoStream() の呼び出しによって停止した場合、このコールバックはパイプラインの枯渇に伴い続行される場合があります。各フレームは引き続き返される必要があります。ストリームの最後のフレームが配信されると、NULL bufferHandle が配信されます。これはストリームの最後を示し、それ以降のフレーム配信は行われません。doneWithFrame() によって NULL bufferHandle 自体を返す必要はありませんが、他のハンドルはすべて返す必要があります

独自のバッファ形式は技術的に可能ですが、互換性テストでは NV21(YCrCb 4:2:0 Semi-Planar)、YV12(YCrCb 4:2:0 Planar)、YUYV(YCrCb 4:2:2 インターリーブ)、RGBA(32 bit R:G:B:x)、BGRA(32 bit B:G:R:x)の 4 つのサポートされているバッファ形式のいずれかである必要があります。選択した形式は、プラットフォームの GLES 実装で有効な GL テクスチャ ソースである必要があります。

アプリは、bufferId フィールドと BufferDesc 構造内の memHandle の間の対応に依存してはなりませんbufferId 値は基本的に HAL ドライバの実装での使用に限定されるため、自由に使用(再使用)できます。

IEvsDisplay

このオブジェクトは EVS ディスプレイを表しており、ディスプレイの状態を制御し、実際の画像の表示を処理します。

getDisplayInfo() generates (DisplayDesc info);

システムが提供する EVS ディスプレイに関する基本情報を返します(DisplayDesc をご覧ください)。

setDisplayState(DisplayState state) generates (EvsResult result);

ディスプレイの状態を設定します。クライアントは、目的の状態を表すためにディスプレイの状態を設定できます。HAL 実装は他の状態のリクエストを適切に受け入れる必要がありますが、リクエストを無視することもできます。

初期化の際、ディスプレイは NOT_VISIBLE 状態で開始するよう定義されます。その後、クライアントは VISIBLE_ON_NEXT_FRAME 状態をリクエストし、動画の提供を開始します。ディスプレイが不要になると、クライアントは最後の動画フレームを渡した後に NOT_VISIBLE 状態をリクエストします。

どの状態も、いつでもリクエストできます。ディスプレイがすでに表示されている場合は、状態が VISIBLE_ON_NEXT_FRAME に設定されていれば引き続き表示されたままになります。リクエストされた状態が認識できない列挙値でない限り、常に OK を返します。認識できない場合、INVALID_ARG が返されます。

getDisplayState() generates (DisplayState state);

ディスプレイの状態を取得します。HAL 実装では、実際の現在の状態を報告する必要があります。これは最新のリクエストされた状態とは異なる場合があります。ディスプレイの状態を変更するロジックは、デバイスレイヤの上に存在する必要があります。そのため、HAL 実装でディスプレイの状態を自動的に変更することは望ましくありません。

getTargetBuffer() generates (handle bufferHandle);

ディスプレイに関連付けられたフレーム バッファにハンドルを返します。このバッファはロックして、ソフトウェアや GL によって書き込むことができます。また、ディスプレイがすでに非表示になっていても、returnTargetBufferForDisplay() の呼び出しを介して返される必要があります。

独自のバッファ形式は技術的に可能ですが、互換性テストでは NV21(YCrCb 4:2:0 Semi-Planar)、YV12(YCrCb 4:2:0 Planar)、YUYV(YCrCb 4:2:2 インターリーブ)、RGBA(32 bit R:G:B:x)、BGRA(32 bit B:G:R:x)の 4 つのサポートされているバッファ形式のいずれかである必要があります。選択した形式は、プラットフォームの GLES 実装で有効な GL レンダリング ターゲットである必要があります。

エラーの場合、ハンドルが null のバッファが返されますが、このようなバッファは returnTargetBufferForDisplay に返す必要はありません。

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

ディスプレイ用のバッファの準備ができたことをディスプレイに通知します。getTargetBuffer() の呼び出しによって取得されたバッファのみを、この呼び出しで使用できます。BufferDesc のコンテンツがクライアント アプリによって変更されることはありません。この呼び出しの後、バッファはクライアントで使用できなくなります。成功の場合は OK を返します。それ以外の場合は、INVALID_ARG または BUFFER_NOT_AVAILABLE を含む適切なエラーコードを返します。

struct DisplayDesc {
     string  display_id;
     int32   vendor_flags;  // Opaque value
}

EVS の実装で必要とされる、EVS ディスプレイの基本プロパティを記述します。HAL はこの構造体に入力して EVS ディスプレイを記述します。他の表示デバイスと重なった、または組み合わさった物理ディスプレイや仮想ディスプレイにすることもできます。

  • display_id。ディスプレイを一意に識別する文字列。 デバイスのカーネル デバイス名や、「rearview」などのデバイス名を指定できます。この文字列の値は、HAL 実装によって選択され、上記のスタックで不透明に使用されます。
  • vendor_flags。特殊なカメラ情報を、ドライバからカスタム EVS アプリに不透明に渡すメソッド。ドライバから EVS アプリに解釈されずに渡されますが、無視することもできます。
enum DisplayState : uint32 {
    NOT_OPEN,               // Display has not been “opened” yet
    NOT_VISIBLE,            // Display is inhibited
    VISIBLE_ON_NEXT_FRAME,  // Will become visible with next frame
    VISIBLE,                // Display is currently active
    DEAD,                   // Display is not available. Interface should be closed
}

EVS ディスプレイの状態を記述します。無効(ドライバには非表示)または有効(ドライバに画像を表示)にできます。ディスプレイがまだ表示されていない一時的な状態が含まれますが、returnTargetBufferForDisplay() の呼び出しによる次の画像フレームの配信で表示可能になる準備ができています。

EVS Manager

EVS Manager は、外部カメラビューを収集して表示するための公開インターフェースを EVS システムに提供します。ハードウェア ドライバが 1 つのリソース(カメラまたはディスプレイ)あたり 1 つのアクティブなインターフェースのみを許可する場合、EVS Manager によりカメラへの共有アクセスが容易になります。単一のプライマリ EVS アプリは EVS Manager の最初のクライアントで、表示データの書き込みを許可された唯一のクライアントです(追加のクライアントにはカメラ画像への読み取り専用権限を付与できます)。

EVS Manager は、基盤となる HAL ドライバと同じ API を実装し、複数の同時クライアントをサポートすることで、幅広いサービスを提供します(複数のクライアントが EVS Manager を介してカメラを起動し、動画ストリームを受信できます)。

EVS Manager と EVS Hardware API の図。
図 2. EVS Manager による、基盤となる EVS Hardware API のミラーリング

EVS Hardware HAL 実装または EVS Manager API を使用して操作する場合、EVS Manager API がカメラ ストリームの同時アクセスを許可する点を除き、アプリにとって違いはありません。EVS Manager 自体が EVS Hardware HAL レイヤのクライアントとして許可され、EVS Hardware HAL のプロキシとして機能します。

以下のセクションでは、EVS Manager の実装で異なる(拡張された)動作を持つ呼び出しについてのみ説明します。残りの呼び出しについては EVS HAL の説明と同じです。

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

固有の camera_id 文字列で識別される特定のカメラとのやり取りに使用されるインターフェース オブジェクトを取得します。失敗した場合は NULL を返します。 EVS Manager レイヤでは、十分なシステム リソースがある限り、すでに開いているカメラを別のプロセスで再び開いて、複数のコンシューマ アプリに動画ストリームを転送できます。EVS Manager レイヤの camera_id 文字列は、EVS Hardware レイヤに報告される文字列と同じです。

IEvsCamera

EVS Manager が提供する IEVsCamera の実装は内部で仮想化されているため、1 つのクライアントによるカメラの操作は、カメラへの独立したアクセスを保持する他のクライアントに影響しません。

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

動画ストリームを開始します。クライアントは、同一の基盤となるカメラで個別に動画ストリームを開始、停止できます。最初のクライアントが開始すると、基盤となるカメラが起動します。

doneWithFrame(uint32 frameId, handle bufferHandle) generates (EvsResult result);

フレームを返します。各クライアントは、完了したフレームを返す必要がありますが、必要な間はフレームを保持できます。クライアントが保持するフレーム数が設定された上限に達すると、フレームを返すまでそれ以上のフレームを受信しません。このフレーム スキップは他のクライアントには影響しないため、すべてのフレームを期待どおりに受信し続けます。

stopVideoStream();

動画ストリームを停止します。クライアントは、他のクライアントに影響を与えることなく、いつでも動画ストリームを停止できます。特定のカメラの最後のクライアントがストリームを停止すると、ハードウェア レイヤの基盤となるカメラのストリームが停止します。

setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);

ドライバ固有の値を送信します。これによって、あるクライアントが別のクライアントに影響を与える可能性があります。EVS Manager はベンダー定義の制御用語の意味を理解できないため、仮想化されておらず、あらゆる副作用が特定のカメラのすべてのクライアントに適用されます。たとえば、ベンダーがこの呼び出しを使用してフレームレートを変更した場合、影響を受けるハードウェア レイヤ カメラのクライアントはすべて、新しいレートでフレームを受信します。

IEvsDisplay

EVS Manager レベルでも、許可されるディスプレイ所有者は 1 人だけです。EVS Manager は機能を追加せず、IEvsDisplay インターフェースを基盤となる HAL 実装にそのまま渡します。

EVS アプリ

Android には、EVS Manager および車両 HAL と通信して基本的なリアビュー カメラ機能を提供する、EVS アプリのネイティブ C++ リファレンス実装が含まれています。アプリは、システムの起動プロセスの早い段階から開始され、使用可能なカメラと車の状態(ギアとウィンカーの状態)に応じた適切な動画が表示されます。OEM は EVS アプリを、車両固有のロジックや表示に変更または置換できます。

図 3. EVS アプリのサンプル ロジック、カメラリストの取得


図 4. EVS アプリのサンプル ロジック、フレーム コールバックの受信

画像データは標準のグラフィック バッファでアプリに渡されるため、アプリはソースバッファから出力バッファに画像を移動します。これにより、データコピーのコストが発生しますが、アプリは任意の形式で画像をディスプレイ バッファにレンダリングすることもできます。

たとえば、アプリはピクセルデータ自体をインライン スケーリングや回転操作で移動することもできます。また、ソース画像を OpenGL テクスチャとして使用し、アイコン、ガイドライン、アニメーションなどの仮想要素を含む複雑なシーンを出力バッファにレンダリングすることも可能です。より高度なアプリでは、複数の同時入力カメラを選択して単一の出力フレームに統合することもできます(上から見下ろす形の、車両環境の仮想ビューでの使用など)。

EVS ディスプレイ HAL で EGL / SurfaceFlinger を使用する

このセクションでは、EGL を使用して Android 10 で EVS ディスプレイ HAL 実装をレンダリングする方法について説明します。

EVS HAL のリファレンス実装では、EGL を使用してカメラのプレビューを画面にレンダリングし、libgui を使用してターゲット EGL レンダリング サーフェスを作成します。Android 8 以降では libguiVNDK-private に分類されます。これは、ベンダー プロセスが使用できない VNDK ライブラリで使用可能なライブラリのグループを指します。HAL 実装はベンダー パーティションに存在する必要があるため、ベンダーは HAL 実装で Surface を使用できません。

ベンダー プロセス用の libgui のビルド

libgui の使用は、EVS ディスプレイ HAL 実装で EGL / SurfaceFlinger を使用するための唯一の選択肢となります。libgui を実装する最も簡単な方法は、ビルド スクリプトで追加のビルド ターゲットを使用して、frameworks/native/libs/gui を直接使用することです。このターゲットは、次の 2 つのフィールドが追加されていることを除けば、libgui ターゲットとまったく同じです。

  • name
  • vendor_available
cc_library_shared {
    name: "libgui_vendor",
    vendor_available: true,
    vndk: {
        enabled: false,
    },
    double_loadable: true,

defaults: ["libgui_bufferqueue-defaults"],
srcs: [ … // bufferhub is not used when building libgui for vendors target: { vendor: { cflags: [ "-DNO_BUFFERHUB", "-DNO_INPUT", ], …

注: ベンダー ターゲットは、NO_INPUT マクロ(パーセルデータから 32 ビットワードを 1 つ削除する)で作成されます。SurfaceFlinger はこのフィールドが削除されていると想定しているため、パーセルの解析に失敗します。これは fcntl エラーとして記録されます。

W Parcel  : Attempt to read object from Parcel 0x78d9cffad8 at offset 428 that is not in the object list
E Parcel  : fcntl(F_DUPFD_CLOEXEC) failed in Parcel::read, i is 0, fds[i] is 0, fd_count is 20, error: Unknown error 2147483647
W Parcel  : Attempt to read object from Parcel 0x78d9cffad8 at offset 544 that is not in the object list

この状態を解決するには:

diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 6066421fa..25cf5f0ce 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -54,6 +54,9 @@ status_t layer_state_t::write(Parcel& output) const
     output.writeFloat(color.b);
 #ifndef NO_INPUT
     inputInfo.write(output);
+#else
+    // Write a dummy 32-bit word.
+    output.writeInt32(0);
 #endif
     output.write(transparentRegion);
     output.writeUint32(transform);

以下のビルド手順をご覧ください。$(ANDROID_PRODUCT_OUT)/system/lib64/libgui_vendor.so が作成されるはずです。

$ cd <your_android_source_tree_top>
$ . ./build/envsetup.
$ lunch <product_name>-<build_variant>
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=10
TARGET_PRODUCT=<product_name>
TARGET_BUILD_VARIANT=<build_variant>
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a9
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=<host_linux_version>
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=QT
OUT_DIR=out
============================================

$ m -j libgui_vendor … $ find $ANDROID_PRODUCT_OUT/system -name "libgui_vendor*" .../out/target/product/hawk/system/lib64/libgui_vendor.so .../out/target/product/hawk/system/lib/libgui_vendor.so

EVS HAL 実装でのバインダーの使用

Android 8 以降では、/dev/binder デバイスノードがフレームワーク プロセス専用になり、ベンダー プロセスからアクセスできなくなりました。代わりに、ベンダー プロセスは /dev/hwbinder を使用し、AIDL インターフェースを HIDL に変換する必要があります。ベンダー プロセス間で AIDL インターフェースを引き続き使用する場合は、バインダー ドメイン /dev/vndbinder を使用します。

IPC ドメイン 説明
/dev/binder AIDL インターフェースを使用した、フレームワーク プロセスとアプリプロセス間の IPC
/dev/hwbinder HIDL インターフェースを使用した、フレームワーク プロセスとベンダー プロセス間の IPC
HIDL インターフェースを使用した、ベンダー プロセス間の IPC
/dev/vndbinder AIDL インターフェースを使用した、ベンダー プロセス間の IPC

SurfaceFlinger は AIDL インターフェースを定義しますが、ベンダー プロセスでは HIDL インターフェースのみを使用してフレームワーク プロセスと通信できます。既存の AIDL インターフェースを HIDL に変換するには、非常に多くの作業が必要です。幸いなことに、Android には、ユーザー空間ライブラリのプロセスがリンクされている libbinder のバインダー ドライバを選択するメソッドが用意されています。

diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp
index d8fb3166..5fd02935 100644
--- a/evs/sampleDriver/service.cpp
+++ b/evs/sampleDriver/service.cpp
@@ -21,6 +21,7 @@
 #include <utils/Errors.h>
 #include <utils/StrongPointer.h>
 #include <utils/Log.h>
+#include <binder/ProcessState.h>

 #include "ServiceNames.h"
 #include "EvsEnumerator.h"
@@ -43,6 +44,9 @@ using namespace android;
 int main() {
     ALOGI("EVS Hardware Enumerator service is starting");


+    // Use /dev/binder for SurfaceFlinger
+    ProcessState::initWithDriver("/dev/binder");
+


     // Start a thread to listen to video device addition events.
     std::atomic<bool> running { true };
     std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));

注: ベンダー プロセスは、Process または IPCThreadState を呼び出すに、またはバインダー呼び出しの前に、このメソッドを呼び出す必要があります。

SELinux ポリシー

デバイスの実装が Treble に完全に対応している場合、SELinux はベンダー プロセスが /dev/binder を使用できないようにします。たとえば、EVS HAL のサンプル実装は hal_evs_driver ドメインに割り当てられており、binder_device ドメインへの読み書き権限が必要です。

W ProcessState: Opening '/dev/binder' failed: Permission denied
F ProcessState: Binder driver could not be opened. Terminating.
F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 9145 (android.hardwar), pid 9145 (android.hardwar)
W android.hardwar: type=1400 audit(0.0:974): avc: denied { read write } for name="binder" dev="tmpfs" ino=2208 scontext=u:r:hal_evs_driver:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=0

ただし、これらの権限を追加するとビルドが失敗します。これは、Treble 完全対応デバイス用に system/sepolicy/domain.te で定義された次の neverallow ルールに違反するためです。

libsepol.report_failure: neverallow on line 631 of system/sepolicy/public/domain.te (or line 12436 of policy.conf) violated by allow hal_evs_driver binder_device:chr_file { read write };
libsepol.check_assertions: 1 neverallow failures occurred
full_treble_only(`
  neverallow {
    domain
    -coredomain
    -appdomain
    -binder_in_vendor_violators
  } binder_device:chr_file rw_file_perms;
')

binder_in_vendor_violators は、バグやガイド開発を検出するために提供される属性です。また、上記の Android 10 違反の解決にも使用されることがあります。

diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te
index f1f31e9fc..6ee67d88e 100644
--- a/evs/sepolicy/evs_driver.te
+++ b/evs/sepolicy/evs_driver.te
@@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain;
 hal_server_domain(hal_evs_driver, hal_evs)
 hal_client_domain(hal_evs_driver, hal_evs)

+# Allow to use /dev/binder
+typeattribute hal_evs_driver binder_in_vendor_violators;
+
 # allow init to launch processes in this context
 type hal_evs_driver_exec, exec_type, file_type, system_file_type;
 init_daemon_domain(hal_evs_driver)

ベンダー プロセスとしての EVS HAL リファレンス実装の作成

参考までに、packages/services/Car/evs/Android.mk に次の変更を適用できます。記述されたすべての変更が実装で機能することを確認してください。

diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk
index 734feea7d..0d257214d 100644
--- a/evs/sampleDriver/Android.mk
+++ b/evs/sampleDriver/Android.mk
@@ -16,7 +16,7 @@ LOCAL_SRC_FILES := \
 LOCAL_SHARED_LIBRARIES := \
     android.hardware.automotive.evs@1.0 \
     libui \
-    libgui \
+    libgui_vendor \
     libEGL \
     libGLESv2 \
     libbase \
@@ -33,6 +33,7 @@ LOCAL_SHARED_LIBRARIES := \
 LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc

 LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample
+LOCAL_PROPRIETARY_MODULE := true

 LOCAL_MODULE_TAGS := optional
 LOCAL_STRIP_MODULE := keep_symbols
@@ -40,6 +41,7 @@ LOCAL_STRIP_MODULE := keep_symbols
 LOCAL_CFLAGS += -DLOG_TAG=\"EvsSampleDriver\"
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+LOCAL_CFLAGS += -Iframeworks/native/include

 # NOTE:  It can be helpful, while debugging, to disable optimizations
 #LOCAL_CFLAGS += -O0 -g
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp
index d8fb31669..5fd029358 100644
--- a/evs/sampleDriver/service.cpp
+++ b/evs/sampleDriver/service.cpp
@@ -21,6 +21,7 @@
 #include <utils/Errors.h>
 #include <utils/StrongPointer.h>
 #include <utils/Log.h>
+#include <binder/ProcessState.h>

 #include "ServiceNames.h"
 #include "EvsEnumerator.h"
@@ -43,6 +44,9 @@ using namespace android;
 int main() {
     ALOGI("EVS Hardware Enumerator service is starting");
+    // Use /dev/binder for SurfaceFlinger
+    ProcessState::initWithDriver("/dev/binder");
+
     // Start a thread to listen video device addition events.
     std::atomic<bool> running { true };
     std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te
index f1f31e9fc..632fc7337 100644
--- a/evs/sepolicy/evs_driver.te
+++ b/evs/sepolicy/evs_driver.te
@@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain;
 hal_server_domain(hal_evs_driver, hal_evs)
 hal_client_domain(hal_evs_driver, hal_evs)

+# allow to use /dev/binder
+typeattribute hal_evs_driver binder_in_vendor_violators;
+
 # allow init to launch processes in this context
 type hal_evs_driver_exec, exec_type, file_type, system_file_type;
 init_daemon_domain(hal_evs_driver)
@@ -22,3 +25,7 @@ allow hal_evs_driver ion_device:chr_file r_file_perms;

 # Allow the driver to access kobject uevents
 allow hal_evs_driver self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;
+
+# Allow the driver to use the binder device
+allow hal_evs_driver binder_device:chr_file rw_file_perms;