車用相機 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 管理工具要求影片影格,並將完成的影格傳送回 EVS 管理工具以供顯示。系統預期在 EVS 和車輛服務可用時,由 init 啟動,目標是在開機後兩 (2) 秒內啟動。原始設備製造商可以視需要修改或更換 EVS 應用程式。

EVS Manager

EVS 管理工具 (/packages/services/Car/evs/manager) 提供 EVS 應用程式所需的建構區塊,可實作從簡單的後視攝影機畫面到 6DOF 多攝影機算繪的任何功能。介面透過 HIDL 呈現,可接受多個並行用戶端。其他應用程式和服務 (特別是車輛服務) 可以查詢 EVS 管理工具狀態,瞭解 EVS 系統何時處於啟用狀態。

EVS HIDL 介面

攝影機和螢幕元素都定義在 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:用於識別特定攝影機的專屬字串。可以是裝置的 Kernel 裝置名稱,也可以是裝置名稱,例如「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 物件,以便搭配 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,表示串流結束,不會再傳送任何影格。NULL bufferHandle 本身不需要透過 doneWithFrame() 傳回,但所有其他控制代碼都必須傳回

雖然技術上可以採用專有緩衝區格式,但相容性測試需要緩衝區採用四種支援格式之一: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 紋理來源。

應用程式不應依賴 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。這是用於識別螢幕的專屬字串。這可能是裝置的 Kernel 裝置名稱,或是裝置的名稱,例如「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 管理工具提供 EVS 系統的公開介面,用於收集及呈現外部攝影機畫面。如果硬體驅動程式只允許每個資源 (攝影機或螢幕) 有一個有效介面,EVS 管理員會協助共用攝影機存取權。單一主要 EVS 應用程式是 EVS 管理員的第一個用戶端,也是唯一允許寫入螢幕資料的用戶端 (可授予其他用戶端攝影機影像的唯讀存取權)。

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

EVS 管理員和 EVS 硬體 API 圖表。
圖 2. EVS Manager 會反映基礎 EVS 硬體 API。

透過 EVS 硬體 HAL 實作或 EVS 管理員 API 運作時,應用程式不會看到任何差異,但 EVS 管理員 API 允許並行存取攝影機串流。EVS 管理員本身是 EVS 硬體 HAL 層的允許用戶端,並做為 EVS 硬體 HAL 的 Proxy。

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

IEvsEnumerator

openCamera(string camera_id) generates (IEvsCamera camera);

取得用於與特定攝影機互動的介面物件,該攝影機由專屬的 camera_id 字串識別。如果失敗,則傳回 NULL。 在 EVS 管理工具層級,只要有足夠的系統資源,另一個程序就能再次開啟已開啟的攝影機,將影片串流傳送至多個消費者應用程式。EVS 管理員層的 camera_id 字串與回報給 EVS 硬體層的字串相同。

IEvsCamera

EVS 管理員提供的 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 管理員無法瞭解供應商定義的控制字詞所造成的影響,因此這些字詞不會虛擬化,且任何副作用都會套用至特定攝影機的所有用戶端。舉例來說,如果供應商使用這個呼叫變更影格速率,受影響硬體層攝影機的所有用戶端都會以新速率接收影格。

IEvsDisplay

即使在 EVS 管理員層級,螢幕也只能有一位擁有者。管理員不會新增任何功能,只會將 IEvsDisplay 介面直接傳遞至基礎 HAL 實作。

EVS 應用程式

Android 內建 EVS 應用程式的 C++ 參考實作,可與 EVS 管理工具和車輛 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 私有,也就是指 VNDK 程式庫可用的程式庫群組,但供應商程序無法使用。由於 HAL 實作項目必須位於供應商分區,供應商無法在 HAL 實作項目中使用 Surface。

為供應商程序建構 libgui

在 EVS 螢幕 HAL 實作中,使用 libgui 是使用 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 架構/應用程式程序間的 IPC (使用 AIDL 介面)
/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 網域的讀取/寫入權限。

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;