車用相機 HAL

Android 包含汽車 HIDL 硬體抽象層 (HAL),可在 Android 開機程序的初期擷取及顯示影像,並在系統的整個生命週期中持續運作。HAL 包含外部影像系統 (EVS) 堆疊,通常用於支援搭載 Android 車用資訊娛樂系統 (IVI) 的車輛,提供後視攝影機和環景影像顯示功能。EVS 也能在使用者應用程式中實作進階功能。

Android 也包含 EVS 專屬的擷取和顯示驅動程式介面 (位於 /hardware/interfaces/automotive/evs/1.0 中)。雖然您可以在現有的 Android 相機和顯示服務上建構後視攝影機應用程式,但這類應用程式在 Android 啟動程序中執行的時間可能太晚。使用專門的 HAL 可實現簡化的介面,並明確告知原始設備製造商 (OEM) 需要實作哪些元件來支援 EVS 堆疊。

系統元件

EVS 包含下列系統元件:

EVS 系統元件圖
圖 1. EVS 系統元件總覽。

EVS 應用程式

一個 C++ EVS 應用程式 (/packages/services/Car/evs/app) 做為參考實作。這個應用程式負責向 EVS Manager 要求影片影格,並將已完成的影格傳回給 EVS Manager。預期在 EVS 和 Car Service 可用時,由 init 啟動,目標是在開機後的兩 (2) 秒內完成。OEM 可視需要修改或取代 EVS 應用程式。

EVS 經理

EVS Manager (/packages/services/Car/evs/manager) 提供 EVS 應用程式所需的建構區塊,可實作從簡單的後視攝影機顯示畫面到 6DOF 多攝影機算繪的任何內容。其介面會透過 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 硬體 (一或多部攝影機和單一顯示裝置)。

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 螢幕互動。一次只能有一個用戶端保留 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

這個物件代表單一攝影機,也是擷取圖片的主要介面。

getCameraInfo() generates (CameraDesc info);

傳回這部攝影機的 CameraDesc

setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);

指定相機要求支援的緩衝區鏈深度。IEvsCamera 的用戶端最多可同時保留此多個影格。如果這麼多影格已傳送至接收器,但未由 doneWithFrame 傳回,則串流會跳過影格,直到傳回可重複使用的緩衝區為止。這個呼叫可在任何時間發生,即使串流已在執行時也一樣,在這種情況下,應視情況在鏈結中新增或移除緩衝區。如果未對這個進入點發出呼叫,則 IEvsCamera 預設會支援至少一個影格,但可接受更多。

如果無法容納要求的 bufferCount,函式會傳回 BUFFER_NOT_AVAILABLE 或其他相關錯誤代碼。在這種情況下,系統會繼續使用先前設定的值運作。

startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);

要求從這個攝影機傳送 EVS 攝影機影格。IEvsCameraStream 會持續接收新的圖像影格呼叫,直到呼叫 stopVideoStream() 為止。影格必須在 startVideoStream 呼叫後的 500 毫秒內開始傳送,且開始後必須以至少 10 FPS 的速度產生。啟動影片串流所需的時間,實際上會計入後視攝影機的啟動時間要求。如未啟動串流,則會傳回錯誤代碼;否則會傳回 OK。

oneway doneWithFrame(BufferDesc buffer);

傳回由 IEvsCameraStream 傳送的框架。使用傳送至 IEvsCameraStream 介面的影格後,必須將影格傳回 IEvsCamera 以供重複使用。可用的緩衝區數量有限 (可能只有一個),如果供應量用盡,則在傳回緩衝區之前不會傳送其他影格,這可能會導致略過影格 (具有空值句柄的緩衝區表示串流結束,因此不必透過此函式傳回)。成功時會傳回「OK」,失敗時則傳回適當的錯誤代碼,可能包括 INVALID_ARGBUFFER_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 物件,因為您可能需要使用 EGL 和 eglCreateImageKHR() 擴充功能的圖片。

  • width:顯示圖片的寬度 (以像素為單位)。
  • height:顯示圖片的高度 (以像素為單位)。
  • stride. 每列在記憶體中實際佔用的像素數量,包括任何用於對齊資料列的邊框間距。以像素為單位表示,以便與 gralloc 採用的緩衝區說明慣例相符。
  • pixelSize:每個像素所占用的位元組數,可計算在圖片中各列之間移動所需的位元組數大小 (以位元組為單位的 stride = 以像素為單位的 stride * pixelSize)。
  • format:圖片使用的像素格式。提供的格式必須與平台的 OpenGL 實作相容。如要通過相容性測試,建議您在相機使用時使用 HAL_PIXEL_FORMAT_YCRCB_420_SP,在螢幕上則建議使用 RGBABGRA
  • usage。由 HAL 實作設定的用法標記。HAL 用戶端應傳遞未經修改的這些值 (詳情請參閱 Gralloc.h 相關標記)。
  • bufferId:HAL 實作項目指定的唯一值,可在透過 HAL API 來回傳輸後,識別緩衝區。HAL 實作可能會任意選擇這個欄位中儲存的值。
  • memHandle:包含圖片資料的基礎記憶體緩衝區的句柄。HAL 實作可能會選擇在此處儲存 Gralloc 緩衝區句柄。

IEvsCameraStream

用戶端會實作這個介面,以便接收非同步的影片影格提交內容。

deliverFrame(BufferDesc buffer);

每次影片影格準備好供檢查時,就會接收來自 HAL 的呼叫。此方法收到的緩衝區控制必須透過呼叫 IEvsCamera::doneWithFrame() 傳回。當影片串流透過呼叫 IEvsCamera::stopVideoStream() 而停止時,此回呼可能會在管道排空時繼續。每個影格仍須傳回;當串流中的最後一個影格已傳送後,系統會傳送空值 bufferHandle,表示串流結束,且不會再傳送任何影格。NULL bufferHandle 本身不需要與 doneWithFrame() 一併傳回,但必須傳回所有其他控制代碼

雖然就技術上可以取得專屬緩衝區格式,但要進行相容性測試時,必須採用以下四種支援的格式:NV21 (YCrCb 4:2:0 半平面)、YV12 (YCrCb 4:2:0 Planar)、YUYV (YCrCb 4:2:2 位元)、RGBA (32 位元:R:GRA: R:G)。所選格式必須是平台 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 半平面)、YV12 (YCrCb 4:2:0 平面)、YUYV (YCrCb 4:2:2 交錯)、RGBA (32 位元 R:G:B:x)、BGRA (32 位元 B:G:R:x)。所選格式必須是平台 GLES 實作項目中的有效 GL 轉譯目標。

發生錯誤時,系統會傳回具有空值句柄的緩衝區,但此類緩衝區不需要傳回至 returnTargetBufferForDisplay

returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);

告知顯示器緩衝區已準備好顯示。只有透過呼叫 getTargetBuffer() 擷取的緩衝區可用於此呼叫,且用戶端應用程式不得修改 BufferDesc 的內容。此呼叫之後,緩衝區就不再有效,無法供用戶端使用。成功時會傳回「OK」,否則會傳回適當的錯誤代碼,可能包括 INVALID_ARGBUFFER_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 經理

EVS Manager 會為 EVS 系統提供公開介面,用於收集及顯示外部攝影機畫面。如果硬體驅動程式只允許每個資源 (相機或螢幕) 有一個有效介面,EVS Manager 可協助共用相機存取權。單一主要 EVS 應用程式是 EVS Manager 的第一個用戶端,也是唯一可寫入顯示資料的用戶端 (其他用戶端可授予相機影像的唯讀存取權)。

EVS Manager 會實作與基礎 HAL 驅動程式相同的 API,並透過支援多個並行用戶端 (多個用戶端可透過 EVS Manager 開啟攝影機並接收影像串流) 提供擴充服務。

EVS Manager 和 EVS Hardware API 圖表。
圖 2. EVS Manager 會鏡像底層 EVS 硬體 API。

應用程式透過 EVS 硬體 HAL 實作或 EVS Manager API 運作時,不會有任何差異,唯一的差異是 EVS Manager API 允許同時存取相機串流。EVS Manager 本身是 EVS Hardware HAL 層允許的用戶端,也是 EVS Hardware HAL 的 Proxy。

以下各節僅說明在 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 實作在內部虛擬化,因此一個用戶端對攝影機的作業不會影響其他用戶端,後者可保留對攝影機的獨立存取權。

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 層級也是如此。Manager 不會新增任何功能,只會直接將 IEvsDisplay 介面傳遞至基礎 HAL 實作項目。

EVS 應用程式

Android 包含 EVS 應用程式的原生 C++ 參考實作項目,可與 EVS Manager 和 Vehicle HAL 通訊,提供基本的後視攝影機功能。應用程式預期會在系統啟動程序早期啟動,並根據可用攝影機和車輛狀態 (設備和轉彎訊號狀態) 顯示合適的影片。原始設備製造商 (OEM) 可以修改或取代 EVS 應用程式,並使用自己的車輛專屬邏輯和呈現方式。

圖 3. EVS 應用程式範例邏輯,取得攝影機清單。


圖 4. EVS 應用程式範例邏輯,接收影格回呼。

由於圖片資料會以標準圖形緩衝區的形式呈現給應用程式,因此應用程式會負責將圖片從來源緩衝區移至輸出緩衝區。雖然這會導致資料複製成本,但也讓應用程式有機會以任何方式將圖片算繪至顯示緩衝區。

舉例來說,應用程式可以選擇移動像素資料本身,可能會使用內嵌縮放或旋轉作業。應用程式也可以選擇使用來源圖片做為 OpenGL 紋理,並將複雜場景轉譯至輸出緩衝區,包括圖示、指南和動畫等虛擬元素。較複雜的應用程式也可能會選取多個並行的輸入鏡頭,並合併至單一輸出影格 (例如用於車輛周遭由上而下虛擬的檢視畫面)。

在 EVS 顯示 HAL 中使用 EGL/SurfaceFlinger

本節說明如何使用 EGL 在 Android 10 中算繪 EVS 顯示 HAL 實作。

EVS HAL 參考實作項目會使用 EGL 在螢幕上算繪相機預覽畫面,並使用 libgui 建立目標 EGL 算繪介面。在 Android 8 (以上版本) 中,libgui 被歸類為 VNDK-private,這是指一組可供供應商程序使用的 VNDK 程式庫。由於 HAL 實作項目必須位於供應商分區,因此供應商無法在 HAL 實作項目中使用 Surface。

為供應商程序建構 libgui

使用 libgui 是使用 EVS 顯示 HAL 實作中 EGL/SurfaceFlinger 的唯一選項。實作 libgui 最直接的方式,就是直接透過 frameworks/native/libs/gui 在建構指令碼中使用額外的建構目標。這個目標與 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 位元字詞。由於 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));

注意:供應商程序應在呼叫 ProcessIPCThreadState 之前,或在進行任何 Binder 呼叫之前,呼叫這個方法。

SELinux 政策

如果裝置實作是完整的 treble,SELinux 會防止供應商程序使用 /dev/binder。舉例來說,系統會將 EVS HAL 實作範例指派給 hal_evs_driver 網域,並需要 binder_device 網域的 R/w 權限。

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

不過,新增這些權限會導致建構失敗,因為其違反 system/sepolicy/domain.te 針對完整高解析裝置定義的下列不允許規則。

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;