Android12 EVS架构全解之软件架构

Android for automotive中EVS架构的整体介绍。
Views: 52
1 1
Read Time:5 Minute, 33 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去进行图像绘制,开发难度相对较大。

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

整体架构

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

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

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

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

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

下面,将会针对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 [email protected]::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架构的软件架构讲解到此结束,后续将会针对Sample实现的细节进行分析。同时由于笔者的能力有限,难免会存在错误的地方,还请各位多多包涵。

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
默认图片
FranzKafka95
极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。
文章: 51

留下评论

zh_CNCN