车辆摄像头 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 System
components diagram
图 1. EVS 系统组件概览。

EVS 应用

示例 C++ EVS 应用 (/packages/services/Car/evs/app) 用作参考实现。此应用负责从 EVS 管理器请求视频帧,并将完成的帧发送回 EVS 管理器以进行显示。它希望在 EVS 和 Car Service 可用后立即由 init 启动,目标是在开机后两 (2) 秒内启动。OEM 可以根据需要修改或替换 EVS 应用。

EVS 管理器

EVS 管理器 (/packages/services/Car/evs/manager) 提供了 EVS 应用所需的构建块,以实现从简单的后视摄像头显示到 6DOF 多摄像头渲染的任何功能。其接口通过 HIDL 呈现,并且构建为接受多个并发客户端。其他应用和服务(特别是 Car Service)可以查询 EVS 管理器状态,以了解 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() 调用的反向操作)。必须先调用 stopVideoStream() 停止摄像头视频流,然后才能调用 closeCamera

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 应用,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 以供重用。可用的缓冲区数量有限(可能少至一个),如果供应耗尽,则在返回缓冲区之前不会传送更多帧,这可能会导致跳帧(缓冲区句柄为 null 表示流的结束,无需通过此函数返回)。成功时返回 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 纹理源。

应用不应依赖于 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 渲染目标。

在错误时,将返回句柄为 null 的缓冲区,但此类缓冲区不需要传递回 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 应用,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 管理器为 EVS 系统提供公共接口,用于采集和呈现外部摄像头视图。如果硬件驱动程序每个资源(摄像头或显示屏)只允许一个活动接口,则 EVS 管理器将促进对摄像头的共享访问。单个主 EVS 应用是 EVS 管理器的第一个客户端,也是唯一允许写入显示数据的客户端(可以授予其他客户端对摄像头图像的只读访问权限)。

EVS 管理器实现的 API 与底层 HAL 驱动程序相同,并通过支持多个并发客户端来提供扩展服务(多个客户端可以通过 EVS 管理器打开摄像头并接收视频流)。

EVS Manager and
EVS Hardware API diagram.
图 2. EVS 管理器镜像底层 EVS 硬件 API。

应用程序在使用 EVS 硬件 HAL 实现或 EVS 管理器 API 进行操作时看不到任何差异,除了 EVS 管理器 API 允许并发摄像头流访问。EVS 管理器本身是 EVS 硬件 HAL 层的唯一允许客户端,并充当 EVS 硬件 HAL 的代理。

以下章节仅描述在 EVS 管理器实现中具有不同(扩展)行为的调用;其余调用与 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 管理器和 Vehicle HAL 通信,以提供基本的后视摄像头功能。该应用预计在系统启动过程的早期启动,并根据可用的摄像头和汽车状态(档位和转向信号状态)显示合适的视频。OEM 可以使用他们自己的特定于车辆的逻辑和演示文稿来修改或替换 EVS 应用。

图 3. EVS 应用示例逻辑,获取摄像头列表。


图 4. EVS 应用示例逻辑,接收帧回调。

由于图像数据以标准图形缓冲区形式呈现给应用,因此应用负责将图像从源缓冲区移动到输出缓冲区。虽然这引入了数据复制的成本,但也为应用提供了以其所需的任何方式将图像渲染到显示缓冲区的机会。

例如,应用可以选择移动像素数据本身,可能使用内联缩放或旋转操作。应用还可以选择将源图像用作 OpenGL 纹理,并将复杂场景渲染到输出缓冲区,包括虚拟元素,例如图标、准则和动画。更复杂的应用还可以选择多个并发输入摄像头,并将它们合并到单个输出帧中(例如,用于车辆周围环境的自上而下的虚拟视图)。

在 EVS Display HAL 中使用 EGL/SurfaceFlinger

本节介绍如何在 Android 10 中使用 EGL 渲染 EVS Display HAL 实现。

EVS HAL 参考实现使用 EGL 在屏幕上渲染摄像头预览,并使用 libgui 创建目标 EGL 渲染表面。在 Android 8(及更高版本)中,libgui 被归类为 VNDK-private,它指的是一组可供 VNDK 库使用的库,但供应商进程无法使用。由于 HAL 实现必须驻留在供应商分区中,因此供应商无法在 HAL 实现中使用 Surface。

为供应商进程构建 libgui

使用 libgui 是在 EVS Display HAL 实现中使用 EGL/SurfaceFlinger 的唯一选择。实现 libgui 最直接的方法是通过在构建脚本中使用额外的构建目标,直接通过 frameworks/native/libs/gui 实现。此目标与 libgui 目标完全相同,只是增加了两个字段

  • 名称
  • 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 宏构建的,该宏从 parcel 数据中删除一个 32 位字。由于 SurfaceFlinger 期望此已删除的字段,因此 SurfaceFlinger 无法解析 parcel。这被观察为 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 接口的人,请使用 binder 域 /dev/vndbinder

IPC 域 描述
/dev/binder 框架/应用进程之间使用 AIDL 接口的 IPC
/dev/hwbinder 框架/供应商进程之间使用 HIDL 接口的 IPC
供应商进程之间使用 HIDL 接口的 IPC
/dev/vndbinder 供应商/供应商进程之间使用 AIDL 接口的 IPC

虽然 SurfaceFlinger 定义了 AIDL 接口,但供应商进程只能使用 HIDL 接口与框架进程通信。将现有的 AIDL 接口转换为 HIDL 需要大量工作。幸运的是,Android 提供了一种方法,可以使用该方法为 libbinder 选择 binder 驱动程序,用户空间库进程链接到该驱动程序。

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 策略

如果设备实现是 full 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 中为 full-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;