Android12 EVS架构全解之软件架构

Android for automotive中EVS架构的整体介绍。
Views: 976
5 1
Read Time:5 Minute, 59 Second

在安卓系统中,针对Camera的应用存在两套架构,经常做手机应用的朋友应该知道Camera2与最新推出的CameraX。无论是Camera2还是CameraX,都依赖于Native的CameraService。但是除了这套架构外,安卓针对Automotive的版本还存在另外一套架构——EVS(Exterior View System)。

从EVS的英文全称我们自己可以理解EVS架构的使用场景,即针对汽车外景系统。再简单直接一点,也就是我们平常所说的倒车影像,360全景这一类影像系统。关于EVS与Camera2这类架构的区别,主要包括如下几点:

1.EVS架构一般针对车外摄像头。车外摄像头一般用于倒车影像,360全景环视或者自动驾驶等场景。为了获取更宽阔的视野,更稳定的成像效果。一般而言,车外摄像头的位置都是固定的,且都属于鱼眼视角,其他参数上也较为固定。这与我们手机或者车内摄像头都是有差异的,因此也注定了在EVS架构中,系统对摄像头的控制是比较少的。与此相反,Camera2经常用于手机相机应用或者车内如IMS,DMS等系统,摄像头拥有比较灵活的调节空间与参数设定。故而Camera2提供了丰富的API可以实现对摄像头的多项控制,如闪光,3A控制等。

2.EVS架构在软件层次上,主体在HAL与Native Framework,而Camera2在HAL,Native Framework与App均有涉及。这也注定了EVS具有更快的响应、更快的启动,更低的时延,这是EVS架构最重要的特性之一。考虑到在汽车冷启动时,用户存在倒车的可能,此时需要安卓系统快速响应,而与此同时JVM可能还未启动,是无法运行Application的。使用Native来实现由此应运而生。

3.在开发难度上,由于Camera2在Java Framework层提供了丰富的API支持(CameraManager),使得开发者可以快速开发自己的应用,而EVS则需要开发者自己去构建诸如Input管理,View子系统等,也需要使用OpenGL ES的API去进行图像绘制,开发难度相对较大。涉及到的核心模块包括Camera子系统(内核v4l2、EVS HAL),Input子系统(内核Input_event、Eventhub)、Graphics子系统(OpenGL ES、Graphicbuffer、BufferQueue、SurfaceFlinger),View子系统(layout、touch handle),其他的还包括Vehicle HAL、Audio HAL等。

关于Camera2架构的分析已经比较多了,但关于EVS架构的分析是比较少的,这篇文章将尽可能地对EVS架构做一个完整的分析。

整体架构

EVS的具体架构介绍可以参考官方官方文档,这里借鉴官方文档上的架构图。借着这张框架图,我们展开讲讲。

EVS架构的主体分为四部分:CarEvsManagerEVS APP、EVS ManagerEVS HAL.

CarEvsManager是CarService中提供的子服务,用于向Android应用提供访问EVS框架,获取摄像头数据并进行绘制显示的能力,CarEvsManager会连接到CarEvsService,由CarEvsService连接EVS Manager,从而获取HAL服务。

EVS APP将作为Native EVS应用的具体实现方,在EVS APP中,最重要的任务就是通过EVS Manager拿到底层HAL传递的Camera数据,利用OpenGL ES实现绘制渲染,除此之外,EVS APP内还需要实现对CAN信号的处理,对Input输入的处理等等,在安卓系统未启动需要快速响应时,EVS APP将作为具体的业务承载方。

EVS Manager作为中间嵌套的一层,为APP提供接入EVS HAL的接口,同时实现对两个重要抽象对象(EVS Camera与EVS Display)的集中管理,此外还提供了额外的诸如权限管理,诊断功能等。

EVS HAL作为硬件抽象层的具体实现,将与内核驱动进行交互,获取具体摄像头数据。在这一层将会实现两个重要的抽象对象:EVS Camera与EVS Display。

CarEvsManager核心是作为下层服务的warper,核心内容并不多,在这里我们不做过多分析;接下来我们将会针对EVS架构中的其余三部分进行具体的讲解。

EVS APP

作为原生Native应用,EVS APP最重要的还是会回归到作为一个APP所应具备的东西。不过原生的EVS APP实现其实是相当不完善的,在Android12中仅支持简单的图像预览。

源码路径位于/packages/services/Car/cpp/evs/apps/default,程序入口位于evs_app.cpp内,代码如下:

int main(int argc, char** argv)
{
    LOG(INFO) << "EVS app starting";
    // Register a signal handler
    registerSigHandler();
    // Set up default behavior, then check for command line options
    bool useVehicleHal = true;
    bool printHelp = false;
    const char* evsServiceName = "default";
    int displayId = -1;
    .....
    android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
    int32_t mockGearSignal = static_cast<int32_t>(VehicleGear::GEAR_REVERSE);
    // Run forever, reacting to events as necessary

    .....
    LOG(INFO) << "Entering running state";
    pEvsListener->run(pStateController);
    LOG(ERROR) << "EVS Listener stopped.  Exiting.";
    return EXIT_SUCCESS;
}

在EVS APP启动时,主要做了以下几件事:

1.解析传入参数实现差异化启动,具体可配置项可以通过运行时加入–help参数进行查看。

2.读取配置文件,通过解析配置文件具体内容获取硬件信息.在启动时EVS会优先读取/system/etc/automotive/evs/config_override.json,该配置文件一般是由OEM产商自行配置的。如果该配置文件读取存在问题,则会以解析配置文件/system/etc/automotive/evs/config.json。

3.获取底层Evs Display操作对象,Display将指定在多个屏幕之间的具体显示位置。

4.在需要使用Vehicle HAL的前提下,连接Vehicle HAL,订阅车辆挡位信息与转向灯信息。当数据变化时通过listener回调处理。

5.开启EvsStateControl状态更新线程,在该线程内根据外部输入调整EVS APP的运行状态以及实现具体的图像绘制。

在原生EVS APP的实现中,重点就在这第5件事上。这里贴上EvsStateControl中状态更新处理线程的部分代码:

void EvsStateControl::updateLoop()
{
    LOG(DEBUG) << "Starting EvsStateControl update loop";
    bool run = true;
    while (run)
    {
        // Process incoming commands
        sp<IEvsDisplay> displayHandle;
        {
            std::lock_guard<std::mutex> lock(mLock);
            while (!mCommandQueue.empty())
            {
                const Command& cmd = mCommandQueue.front();
                switch (cmd.operation)
                {
                case Op::EXIT:
                    run = false;
                    break;
                case Op::CHECK_VEHICLE_STATE:
                .....
            }
            displayHandle = mDisplay.promote();
        }
        // Review vehicle state and choose an appropriate renderer
        if (!selectStateForCurrentConditions())
        {
            break;
        }
        // If we have an active renderer, give it a chance to draw
        if (mCurrentRenderer)
        {
          .....
        }
    }
    LOG(WARNING) << "EvsStateControl update loop ending";
    if (mCurrentRenderer)
    {
        // Deactive the renderer
        mCurrentRenderer->deactivate();
    }
    mEvsStats.sendCollectedDataBlocking();
    LOG(ERROR) << "Shutting down app due to state control loop ending";
}

在该线程中,一直周期性循环做三件事:

1.获取外部输入的command信息,包含EXIT、CHECK_VEHICLE_STATE与TOUCH_EVENT三类。原生APP中目前仅处理了 EXIT command,其余均未处理。

2.通过对selectStateForCurrentConditions的调用,对车身信号——挡位信息与转向的信息做判断,结合配置用于裁定当前的绘制模式。如当前处于倒挡,且在该模式下只有一个摄像头,则会选择RenderDirectView模式,如果在该模式下存在多个摄像头,则选择RenderTopView模式。

3.在绘制模式确定的情况下,调用drawFrame接口进行图像绘制。

这就是原生EVS APP主体上所实现的功能,当然这里面其实有很多细节,具体包括:

  • 配置文件解析
  • 摄像头数据流分发
  • 数据格式转换
  • 2D纹理转换
  • EGL环境配置与OpenGL ES绘制

但原生的EVS APP仍旧缺少很多东西,个人认为极其重要的两部分为:Input输入管理View管理。正是由于这两部分的缺失,导致在原生的EVS APP实现中是无法实现用户交互的。

关于这两部分如何实现,这里简单讲讲我的思路。

针对Input的管理,首先我们需要先将Input输入从EventHub中掐断,这样可以避免我们在EVS APP界面操作时影响到下层的应用。其次需要通过读取/dev/input目录下的输入设备,获取具体的Input事件。

针对View的管理,我们可以通过贴图转换为纹理作为View绘制的原材料,通过设定Window的层级与View的坐标,同时将具体的Input绑定到对应View上来实现。

EVS Manager

安卓12中EVS Manager的源码位于packages/services/Car/cpp/evs/manager目录,这里存在于两个版本,对应于EVS HAL版本1.0与1.1。EVS Manager 1.1是向下兼容的,也就是是说即使HAL为1.0版本,EVS Manager 1.1也是可以搭配使用的。

EVS Manager本质上是对HAL接口的一层封装,只不过在此基础上增加了一些其他东西。比如数据统计、诊断相关以及对虚拟Camera设备的支持等。

这里我们先看一下EVS Manager的启动,EVS Manager的启动入口位于service.cpp文件的main函数内,与常规的native service启动类似,这部分其实没什么好讲的。真正启动服务的主体部分其实在startService函数内,代码如下:

static void startService(const char* hardwareServiceName, const char* managerServiceName)
{

    android::sp<Enumerator> service = new Enumerator();
    if (!service->init(hardwareServiceName))
    {

        .....
        exit(1);
    }

    LOG(INFO) << "EVS managed service is starting as " << managerServiceName;
    status_t status = service->registerAsService(managerServiceName);
    if (status != OK)
    {
        .....
        exit(2);
    }
    LOG(INFO) << "Registration complete";
}

从代码中我们可以看到,EVS Manager的启动最终会进入到Enumerator类的init函数内,在该函数内,将会与EVS HAL建立Binder连接,之后获取Display信息,同时开启StatsColletcor进行数据统计。后续的事就是调用时的接口传递啦,没有过多的逻辑在里面。

EVS HAL

EVS HAL作为隔离用户态程序(EVS APP)与底层驱动(Camera Driver)的HAL实现,是OEM产商最为关注的部分。这部分也是原生EVS架构中最为复杂的部分。其源码路径位于/hardware/interfaces/automotive/evs目录。

在上文中已经讲过,在安卓12中EVS HAL存在两个版本,即1.0版本与1.1版本。关于两个版本的具体差异,我们可以大致看一下HIDL接口上的差异。从文件上来讲,在1.1中多了IEvsUltrasonicsArray.hal与IEvsUltrasonicsArrayStream.hal两个文件。这两个文件中定义的接口其实是为摄像头上可能存在的超声波Sensor提供相应的API,这部分新增的内容可能是为了未来的自动驾驶做考虑。当然,目前这只是我的个人猜测,具体使用场景可能要等到EVS HAL 1.1实际应用后才能明确。

除了这两个新增的文件,在原有的接口上,EVS HAL 1.1版本相对于1.0版本也有许多拓展与更新,这里简单例举一些:

types.hal

在CameraDesc结构体内,新增加了类型为CameraMetadata的成员metadata,这是直接将Camera HAL中定义的部分直接拿了过来,通过import android.hardware.camera.device@3.2::CameraMetadata;直接进行引用。

针对BufferDesc结构体的变化,也是比较大的,在1.0版本中BufferDesc结构体定义如下:

struct BufferDesc {
    /* A frame width in the units of pixels */
    uint32_t    width;

    /* A frame height in the units of pixels */
    uint32_t    height;

    /* A frame stride in the units of pixels, to match gralloc */
    uint32_t    stride;

    /* The size of a pixel in the units of bytes */
    uint32_t    pixelSize;

    /* The image format of the frame; may contain values from
     * android_pixel_format_t
     */
    uint32_t    format;

    /* May contain values from Gralloc.h */
    uint32_t    usage;

    /* Opaque value from driver */
    uint32_t    bufferId;

    /* Gralloc memory buffer handle */
    handle      memHandle;
};

而在1.1版本中,其是如下定义的:

struct BufferDesc {
    /**
     * HIDL counterpart of `AHardwareBuffer_Desc`.  Please see
     * hardware/interfaces/graphics/common/1.2/types.hal for more details.
     */
    HardwareBuffer buffer;
    /**
     * The size of a pixel in the units of bytes
     */
    uint32_t pixelSize;
    /**
     * Opaque value from driver
     */
    uint32_t bufferId;
    /**
     * Unique identifier of the physical camera device that produces this buffer.
     */
    string deviceId;
    /**
     * Time that this buffer is being filled.
     */
    int64_t timestamp;

    /**
     * Frame metadata.  This is opaque to EVS manager.
     */
    vec<uint8_t> metadata;
};

可以看出两者的差异还是比较大的。

此外1.1版本还多了EvsEventType的枚举体,用于定义数据流变化以及设备变化相关的一些事件。CameraParam用于定义针对摄像头参数的一些类型,如亮度,对比度,自动增益调节等,还包括一些其他传感器相关的类型定义。

IEvsCamera.hal

在IEvsCamera中,增加了许多对于摄像头的控制接口,如下接口:

     *
     * @return result EvsResult::OK if a master role is granted.
     *                EvsResult::OWNERSHIP_LOST if there is already a
     *                master client.
     */
    setMaster() generates (EvsResult result);


     * @return result  EvsResult::OK if a master role is granted.
     *        EvsResult::INVALID_ARG if a given display handle is   null
     *                 or in valid states.
     */
    forceMaster(IEvsDisplay display) generates (EvsResult result);

    /**
     * Retires from a master client role.
     *
     * @return result EvsResult::OK if this call is successful.
     *                EvsResult::INVALID_ARG if the caller client is not a
     *                master client.
     */
    unsetMaster() generates (EvsResult result);

从这些接口定义中,我们可以看到谷歌尝试将允许多个Client读取Camera数据,但是在对Camera的控制权将由Master role的Client掌控。

关于Camera参数的获取接口:

getIntParameterRange(CameraParam id) generates (int32_t min, int32_t max, int32_t step);

setIntParameter(CameraParam id, int32_t value) generates (EvsResult result, vec<int32_t> effectiveValue);

getIntParameter(CameraParam id) generates(EvsResult result, vec<int32_t> value);

这些接口将为应用获取到Camera配置参数的一些细节信息,这些信息是可以通过配置文件进行配置的。

关于EVS HAL接口上的变化不再过多描述,现在我们来看看代码。

关于原生EVS HAL的实现,我们可以参考对于文件夹下的default目录,但这部分实现都是脱离于底层驱动的,不可以直接使用。为了便于理解,谷歌官方还实现了一个Sample,实现将EVS HAL于内核v4l2驱动相结合,用作具体实现上的一个参考,这里参考的代码来自于该Sample。

该Sample的源码路径位于/packages/services/Car/cpp/evs/sampleDriver,实现的HAL接口为1.1版本。

程序入口位于service.cpp中的main函数:

int main()
{
    ALOGI("EVS Hardware Enumerator service is starting");
    android::sp<IAutomotiveDisplayProxyService> carWindowService =
                 IAutomotiveDisplayProxyService::getService("default");
    if (carWindowService == nullptr)
    {
        ALOGE("Cannot use AutomotiveDisplayProxyService,Exiting");
        return 1;
    }
#ifdef EVS_DEBUG
    SetMinimumLogSeverity(android::base::DEBUG);
#endif
// Start a thread to listen video device addition events.
std::atomic<bool> running{ true };
std::thread ueventHandler(EvsEnumerator::EvsUeventThread,std::ref(running));
   android::sp<IEvsEnumerator> service = new EvsEnumerator(carWindowService);
   configureRpcThreadpool(3, true /* callerWillJoin */);
    status_t status = service->registerAsService(kEnumeratorServiceName);
    if (status == OK)
    {
        ALOGD("%s is ready", kEnumeratorServiceName);
        joinRpcThreadpool();
    }
    else
    {
        ALOGE("Could not register service:%s,status:%s", kEnumeratorServiceName, statusToString(status).c_str());
    }
    // Exit a uevent handler thread.
    running = false;
    if (ueventHandler.joinable())
    {
        ueventHandler.join();
    }
    // In normal operation, we don't expect the thread pool to exit
    ALOGE("EVS Hardware Enumerator is shutting down");
    return 1;
}

在main函数执行的过程中,我们可以看到以下几个环节:

1.获取系统Display

2.使能对Uevent事件的处理线程,该线程内将会处理设备添加,移除等

3.注册为service,加入binder线程池

EVS HAL开始运行后,当上层应用调用到相应的接口,会由EVS Manager传递到EVS HAL,从而进入相应的接口执行。

其他

上述三部分是EVS架构中的主体部分,但从其架构图中我们也可以看到除了上述三部分之外还有其他的几个服务,也是整体架构中的一部分,这里只做简单介绍。

Vehicle HAL:Vehicle HAL是整体Android Automotive版本中对外(与汽车其他控制器)沟通的桥梁,一般而言Vehicle向下会接入CAN/Uart/Ethernet实现对外通信,向上接入CarService,服务于Java Framework,当然Native Framework也是可以接入的。Vehicle HAL的接口定义与默认实现位于hardware/interfaces/automotive/vehicle/2.0目录。

Display Proxy:由于EVS需要将绘制完成的图像渲染到界面上,需要Display相关的支持,最终送入SurfaceFlinger进行消费、合成显示;在Android12.1版本中,Display部分的支持依赖于名为android.frameworks.automotive.display@1.0-service的服务,其源码位于frameworks/native/services/automotive/display。

至此,关于EVS架构的软件架构讲解到此结束,后续将会针对Sample实现的细节进行分析。同时由于笔者的能力有限,难免会存在错误的地方,还请各位多多包涵。

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

文章: 86

一条评论

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注

zh_CNCN