Android 包含車用 HIDL 硬體抽象層 (HAL),可在 Android 開機程序初期提供影像擷取和顯示功能,並在系統生命週期內持續運作。HAL 包含外部檢視系統 (EVS) 堆疊,通常用於支援後視攝影機,以及搭載 Android 車載資訊娛樂 (IVI) 系統的車輛環景顯示器。EVS 也可讓使用者應用程式實作進階功能。
Android 也包含 EVS 專用的擷取和顯示驅動程式介面 (位於 /hardware/interfaces/automotive/evs/1.0)。雖然可以透過現有的 Android 相機和顯示服務建構後視攝影機應用程式,但這類應用程式可能會在 Android 啟動程序中太晚執行。專屬 HAL 可簡化介面,並清楚說明 OEM 支援 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_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 實作指定的專屬值,可讓緩衝區在透過 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_ARG 或 BUFFER_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 管理工具開啟攝影機並接收影片串流),提供擴充服務。
圖 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 目標完全相同,只是多了兩個欄位:
namevendor_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));
注意:供應商程序應先呼叫此函式,再呼叫 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
不過,新增這些權限會導致建構失敗,因為這違反了 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;