車用相機 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:用於唯一識別特定相機的字串。可以是裝置的核心裝置名稱,或是裝置名稱,例如「後視鏡」。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 物件,因為透過 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() 而停止時,這個回呼可能會在管道排除時繼續。您仍須傳回每個影格;提交串流的最後一個影格時,系統會傳送 NULL bufferHandle,代表串流的結尾,且不會再提交影格。空值 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 平面)、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 管理員本身就是 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 硬體層的相同。

IEvs 相機

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 不會新增任何功能,只會直接將 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 中為全 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;