车载相机 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) 秒内启动)。原始设备制造商 (OEM) 可视需要修改或替换 EVS 应用。

EVS 管理器

EVS 管理器 (/packages/services/Car/evs/manager) 可提供 EVS 应用所需的构建块,以实现从简单的后视摄像头显示到 6DOF 多摄像头渲染的任何功能。它的接口通过 HIDL 呈现,并且能够接受多个并发客户端。其他应用和服务(特别是汽车服务)可以查询 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 应用,可直接将其忽略。

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 Semi-Planar)、YV12 (YCrCb 4:2:0 Planar)、YUYV (YCrCb 4:2:2 Interleaved)、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 Semi-Planar)、YV12 (YCrCb 4:2:0 Planar)、YUYV (YCrCb 4:2:2 Interleaved)、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 应用,可直接将其忽略。
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 管理器实现与底层 HAL 驱动程序相同的 API,并通过支持多个并发客户端来提供扩展服务(多个客户端可以通过 EVS 管理器打开相机并接收视频流)。

EVS 管理器和 EVS Hardware API 示意图。
图 2. EVS 管理器镜像底层 EVS Hardware API

除非 EVS Manager API 允许对相机数据流进行并发访问,否则是通过 EVS 硬件 HAL 实现操作还是通过 EVS Manager 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 管理器和车载 HAL 通信的 EVS 应用的原生 C++ 参考实现,以提供基本的后视摄像头功能。该应用应在系统启动过程的早期启动,根据可用的相机和汽车状态(车轮和转向灯状态)显示合适的视频。原始设备制造商 (OEM) 可以使用自己的汽车专用逻辑和呈现来修改或替换 EVS 应用。

图 3. 用于获取相机列表的 EVS 应用逻辑示例。


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

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

例如,应用可以选择移动像素数据本身,从而可以进行内嵌缩放或旋转操作。应用还可以选择使用源图像作为 OpenGL 纹理,并将复杂场景呈现给输出缓冲区,包括虚拟元素(如图标、准则和动画)。更复杂的应用还可以选择多个并发输入相机并将它们合并到单个输出帧中(例如用于从上到下地显示汽车周围环境的虚拟视图)。

在 EVS 显示 HAL 中使用 EGL/SurfaceFlinger

本部分介绍了如何在 Android 10 中使用 EGL 呈现 EVS 显示 HAL 实现。

EVS HAL 参考实现使用 EGL 在屏幕上呈现相机预览,并使用 libgui 创建目标 EGL 呈现表面。在 Android 8(及更高版本)中,libguiVNDK 专用库,它是一组可供 VNDK 库使用但供应商进程无法使用的库。由于必须在供应商分区实现 HAL,因此供应商无法在 HAL 实现中使用 Surface。

构建适用于供应商进程的 libgui

如需在 EVS 显示 HAL 实现中使用 EGL/SurfaceFlinger,libgui 是唯一之选。实现 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 宏构建的,该宏会从 Parcel 数据中移除一个 32 位字。由于 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 框架/应用进程之间的 IPC,使用 AIDL 接口
/dev/hwbinder 框架/供应商进程之间的 IPC,使用 HIDL 接口
供应商进程之间的 IPC,使用 HIDL 接口
/dev/vndbinder 供应商/供应商进程之间的 IPC,使用 AIDL 接口

虽然 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 政策

如果设备实现完全符合 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 是一种用于发现 bug 和提供开发指导的属性,也可用于解决上述 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;