車用相機 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 呈現,並且可接受多個並行用戶端。其他應用程式和服務 (特別是 Car Service) 可以查詢 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 應用程式,但不會進行解譯,因此應用程式可以自由忽略。

IEvs 相機

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

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 物件,因為根據 eglCreateImageKHR() 擴充功能可能需要透過 EGL 使用圖片。

  • 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 (YCbCr 4:2:0 半平面)、YV12 (YCbCr 4:2:0 平面)、YUYV (YCbCr 4:2:2 交錯)、RGBA (32 位元 R:G:B:x)、BGRA (32 位元 B:G:R:x)。所選格式必須是平台 GLES 實作項目中的有效 GL 紋理來源。

應用程式「不應」依賴 BufferDesc 結構中 bufferId 欄位和 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 Planar)、YUYV (YCrCb 4:2:0 Planar)、YUYV (YCrCb 4:2:2 交錯式)、RGBA (32 位元:R2:GRA: GRA: GRA: R2)。所選格式必須是平台 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:用於識別螢幕的字串。這可能是裝置的核心裝置名稱,或裝置名稱,例如「後視鏡」。這個字串的值是由 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 Hardware API。

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

以下各節僅說明在 EVS Manager 實作中具有不同 (擴充) 行為的呼叫;其餘呼叫與 EVS HAL 說明相同。

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

取得用於與特定相機互動的介面物件,該相機由專屬的 camera_id 字串識別。失敗時會傳回 NULL。在 EVS Manager 層級,只要有足夠的系統資源,已開啟的相機就可能由其他程序再次開啟,讓影片串流分流至多個消費者應用程式。EVS Manager 層的 camera_id 字串與回報至 EVS 硬體層的相同。

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 實作中使用 Binder

在 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「之前」呼叫這個方法,或是在進行任何繫結器呼叫之前。

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 中為全 Treble 裝置定義的以下 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;