Camera HIDL接口实现camera preview功能

项目背景

  • 为了在工厂测试中快速启动芯片外设测试项目,而不启动Android系统,使用fastmmi(FFBM: Fast Factory Boot Mode)测试框架。
  • 本人负责测试芯片中的camera外设,保证camera preview function,设置最原始的camera xml参数,preview图像清晰。
  • 使用Vendor NDK实现Android Camera preview.博客中使用vendor NDK的方式实现camera preview function。但是在项目中并没有能够使用,因为使用FFBM测试的时候发现camera service根本无法正常启动,所以只能使用HIDL interface来实现。

HIDL interface介绍

  • 可以从Google的官方网站上查看,写得非常的详细:HIDL Interface.
  • HIDL 的目标是可以在无需重新构建 HAL 的情况下上层系统替换框架。HAL 将由供应商或 SOC 制造商构建,并放置在设备的 /vendor
    分区中,这样一来,就可以在框架自己的分区中通过 OTA 替换框架,而无需重新编译 HAL。这是一种解耦的思想,降低vendor和system 之间耦合度,通过把之间的接口媒介规定好,system和vendor各自升级替换都非常的方便。
  • 因为无法使用camera service作为中间媒介调用camera HAL,通过NDK interface就无法实现camera preview function。只能直接调用camera HAL层,虽然Qualcomm HAL是有interface提供使用的,但是太过于复杂而且并没有非常好的文档和sample支持。考虑使用Android camera HIDL interface,本身有Google强制规定接口形式和功能定义,并且有VTS testcase可以参考流程(虽然VTS testcase在configure stream中只是request一个buffer,result回调回来也没有进行处理,而是直接抛弃掉,但是configure stream的过程也是极具参考性)

HIDL C++ interface

源码逻辑分析

代码逻辑分析主要突出主干,送request到camera HAL进行处理,然后接收function callback result的一个循环。
代码中只给出大概的接口流程,突出整个camera设置的过程,并且讲解设置接口的意义,完整项目大家可以参考camera VTS code。

//获取HAL实现的命名
android::hardware::getAllHalInstanceNames(ICameraProvider::descriptor)[0];
//获取有几个camera device
ICameraProvider::getService(service_name);
getCameraDeviceNames(mProvider);
//HIDL interface获取到device实例,本示例code中使用的是HAL所以是V3
ret = getCameraDeviceInterface_V3_x(
                name,
                [&](auto status, const auto& device) {
                ALOGI("getCameraDeviceInterface_V1_x returns status:%d",
                    (int)status);
                if(status != Status::OK)
                    MMI_ALOGI("status != Status::OK");
                if(device == nullptr)
                    MMI_ALOGI("device == nullptr");
                mdevice3 = device;
        });
//如果使用HIDL interface一定要判断return值,如果不判断,在运行过程中会crash,
//Google使用crash的机制强制使你判断
if(!ret.isOk())
            MMI_ALOGE("return error");
//获取能够使用的camera metadata,设置camera tuning参数
ret = getCameraCharacteristics([&] (Status s,
            ::android::hardware::camera::device::V3_2::CameraMetadata metadata) {
                staticMeta = clone_camera_metadata(
                reinterpret_cast<const camera_metadata_t*>(metadata.data()));
       });
//通过metadata获取到当前这颗camera是front还是rear,对前后摄设置不同的参数
camera_metadata_ro_entry facing;
auto status = find_camera_metadata_ro_entry(staticMeta,
                ANDROID_LENS_FACING, &facing);
//new device callback函数,并且在open device的过程中去注册,capture result就通过callback回调回来
mDeviceCb = new DeviceCb(this, deviceVersion, staticMeta);
ret = mdevice3->open(mDeviceCb, [&session](auto status, const auto& newSession) {
            MMI_ALOGI("device::open returns status:%d", (int)status);
            if(status != Status::OK)
                MMI_ALOGI("returnStatus != Status::OK");
            if(newSession == nullptr)
                MMI_ALOGI("newSession == nullptr");
            *session = newSession;
      });
//configure stream 因为HAL是3.5version,使用_3_5
ret = configureStreams_3_5(config3_5,
                [&] (Status s, device::V3_4::HalStreamConfiguration halConfig) {
                    if(s != Status::OK)
                        MMI_ALOGI("s != Status::OK");
                    if(1u != halConfig.streams.size())
                        MMI_ALOGI("1u != halConfig.streams.size()");
                    halStreamConfig->streams.resize(1);
                    halStreamConfig->streams[0] = halConfig.streams[0].v3_3.v3_2;
                    if (*useHalBufManager) {
                        hidl_vec<V3_4::Stream> streams(1);
                        hidl_vec<V3_2::HalStream> halStreams(1);
                        streams[0] = config3_4.streams[0];
                        halStreams[0] = halConfig.streams[0].v3_3.v3_2;
                        mDeviceCb->setCurrentStreamConfig(streams, halStreams);
                    }
     });
//通过camera session construct request setting
ret = constructDefaultRequestSettings(reqTemplate,
             [&](auto status, const auto& req) {
             if(status != Status::OK)
                  MMI_ALOGE("return error");
             msettings = req;
       });
//这一段代码表示了使用gralloc的接口通过buffer handle创建graphic buffer,创建六个buffer进行轮转,
//使用之前gralloc循环申请对应的buffer地址,
//这个地址作为存放preview image的地址的handle
void allocateGraphicBuffer(uint32_t width, uint32_t height, uint64_t usage,
        ::android::hardware::graphics::common::V1_0::PixelFormat format, hidl_handle *buffer_handle /*out*/) {
    buffer_handle_t buffer;
    buffer = nullptr;
    uint32_t stride;
    android::status_t err = android::GraphicBufferAllocator::get().allocate(
            width, height, static_cast<int32_t>(format), 1u /*layerCount*/, usage, &buffer, &stride,
            "VtsHalCameraProviderV2_4");
    MMI_ALOGI("allocateGraphicBuffer_buffer %p", buffer);
    if(err !=  android::NO_ERROR){
        MMI_ALOGI("err !=  android::NO_ERROR err %d", err);
    }

    buffer_handle->setTo(const_cast<native_handle_t*>(buffer), false /*shouldOwn*/);
}
   hidl_handle buffer_handle[6];
   memset(buffer_handle, 0, sizeof(buffer_handle));
   for(int i=0; i<6; i++){
       if (museHalBufManager) {
           bufferId = 0;
       } else {
           allocateGraphicBuffer(mpreviewStream.width, mpreviewStream.height,
                   android_convertGralloc1To0Usage(halStreamConfig.streams[0].producerUsage,
                       halStreamConfig.streams[0].consumerUsage),
                   halStreamConfig.streams[0].overrideFormat, &buffer_handle[i]);
       }
       std::shared_ptr <StreamBuffer> outputBuffer = std::make_shared<StreamBuffer>();
       outputBuffer->streamId = halStreamConfig.streams[0].id;
       outputBuffer->bufferId = bufferId;
       outputBuffer->buffer = buffer_handle[i].getNativeHandle();
       outputBuffer->status = BufferStatus::OK;
       outputBuffer->acquireFence = nullptr;
       outputBuffer->releaseFence = nullptr;
       MMI_ALOGI("module run allocateGraphicBuffer buffer_handle :%p bufferId %d",
           buffer_handle[i].getNativeHandle(), bufferId);
       buffer_queue.push(outputBuffer);
       bufferId++;
   }
//为了让camera循环不断地下发request,在NDK interface中很简单,直接调用repeating request就可以,但是在HIDL interface中是没有的,
//所以只能够创建出一个连续request方法,使用Android自带的threadloop不断下发request,调用run方法激活thread loop
camerahidltest->run("camera threads", ANDROID_PRIORITY_FOREGROUND);
//treadloop函数不断下发request流程
bool CameraHidlTest::threadLoop(){
    std::shared_ptr <StreamBuffer> outputBuffer;
    Return<void> ret;
    pthread_mutex_lock(&g_mutex_lock);
    if(buffer_queue.size() > 0){
        outputBuffer = buffer_queue.front();
        buffer_queue.pop();
        pthread_mutex_unlock(&g_mutex_lock);
    }else{
        pthread_mutex_unlock(&g_mutex_lock);
        std::unique_lock<std::mutex> con_lock(mtx);
        condition.wait(con_lock);
        if(camerahidltest->mthreadloop_t == true){
            return true;
        }else{
            return false;
        }
    }
    mframenumberbuffer[mframeNumber] = outputBuffer->buffer.getNativeHandle();
    ::android::hardware::hidl_vec<StreamBuffer> outputBuffers = {*outputBuffer};
    const StreamBuffer emptyInputBuffer = {-1, 0, nullptr,
                                            BufferStatus::ERROR, nullptr, nullptr};
    //构建request,mframeNumber必须唯一不能重复
    CaptureRequest request = {mframeNumber, 0 /* fmqSettingsSize */, msettings,
                                emptyInputBuffer, outputBuffers};
    {
        std::unique_lock<std::mutex> l(mLock);
        mInflightMap.add(mframeNumber, &minflightReq);
    }
    Status status = Status::INTERNAL_ERROR;
    uint32_t numRequestProcessed = 0;
    hidl_vec<BufferCache> cachesToRemove;
    //process_capture_request: Send a new capture request to the HAL.
    //The HAL should not return from this call until it is ready to accept the next request to process.
    //下发request到HAL interface
    ret = msession->processCaptureRequest(
        {request}, cachesToRemove, [&status, &numRequestProcessed](auto s,
                uint32_t n) {
            status = s;
            numRequestProcessed = n;
        });
    if(!ret.isOk())
        MMI_ALOGE("return error");
    mframeNumber++;
}
bool DeviceCb::processCaptureResultLocked(const CaptureResult& results,
        hidl_vec<PhysicalCameraMetadata> physicalCameraMetadata) {
uint32_t frameNumber = results.frameNumber;
ssize_t idx = mParent->mInflightMap.indexOfKey(frameNumber);
//其中有一些解析metadata的数据代码,省略,具体参考VTS testcase code
//通过result上来的frame number查找到对应的buffer handle
//std::map<uint32_t, buffer_handle_t> mframenumberbuffer
auto buffer_iter = mframenumberbuffer.find(frameNumber);
}
    hidl_handle buffer_handle;
    buffer_handle.setTo(const_cast<native_handle_t*>(buffer_iter->second), false);
    std::vector<ui::PlaneLayout> planeLayouts;
    //gralloc interface通过buffer handle获取到plane payout
    status_t err = GraphicBufferMapper::get().getPlaneLayouts(buffer_handle, &planeLayouts);
    if (err == NO_ERROR && !planeLayouts.empty()){
        MMI_ALOGE("err == NO_ERROR && !planeLayouts.empty()");
    }
    int32_t Y_Stride = planeLayouts[0].strideInBytes;
    int32_t UV_Stride = planeLayouts[1].strideInBytes;
//gralloc interface lock到真实的image buffer地址
    Rect bounds(0, 0, preview_width, preview_height);
    uint8_t *buffer_pointer = nullptr;
    err = GraphicBufferMapper::get().lock(buffer_handle,
                                    GRALLOC_USAGE_SW_READ_OFTEN,
                                    bounds,
                                    (void **)&buffer_pointer(image buffer真实地址));
//处理image buffer过程,code省略
//使用完之后unlock buffer地址,gralloc才能够再次填充数据
err = GraphicBufferMapper::get().unlock(buffer_handle);
//这一部分主要是重新把stream buffer 保存在queue中然后在thread loop中再次使用
    for(auto iter=results.outputBuffers.begin(); iter!=results.outputBuffers.end(); iter++){
        std::shared_ptr <StreamBuffer> finalSb = std::make_shared<StreamBuffer>();
        finalSb->streamId = iter->streamId;
        finalSb->bufferId = iter->bufferId;
        finalSb->buffer = buffer_iter->second;
        finalSb->status = BufferStatus::OK;
        finalSb->acquireFence = nullptr;
        finalSb->releaseFence = nullptr;
        if(finalSb->buffer.getNativeHandle() != nullptr){
            mParent->buffer_queue.push(finalSb);
                std::unique_lock<std::mutex> con_lock(mParent->mtx);
                mParent->condition.notify_all();
        }else{
            MMI_ALOGI("results outputBuffers address is nullptr");
        }
    }
//这段代码表示在进程结束当前实例后做的清理功能,需要把gralloc中注册的buffer handle进行free,避免占用资源
    while (!camerahidltest->buffer_queue.empty())
        camerahidltest->buffer_queue.pop();
    buffer_handle_t handle[7];
    for(int i=1; i<7; i++){
        handle[i] = camerahidltest->mframenumberbuffer[i];
    }
    camerahidltest->mframenumberbuffer.clear();
    for(int i=1; i<7; i++ ){
        android::GraphicBufferAllocator::get().free(handle[i]);
    }
  • 创建map表来同时对应frame number和buffer handle,因为在result返回中只有frame number非常的明确,通过和buffer handle之间的对应来获取真实的image buffer address。补充一点,capture result回调是有buffer address地址的,但是为nullptr,可以在HIDL interface的文档中发现这个buffer address设计就是为nullptr,没有指向真实的buffer地址,无法使用,我当时做项目的时候还以为哪里弄错了怎么address 指针为nullptr,所以只能在上层建立map来对应链接buffer handle。在capture result的回调函数中其实有很多非data buffer的回调,比如说metadata的回调函数,还有很多具体什么我忘记了,可以首先判空过滤一下
  • 创建了buffer_queue来存放stream buffer,在thread loop的过程中取出并通过request下发到HAL,然后在capture result中重新存入到buffer queue中。
  • 为什么不直接建立buffer handle queue,因为在thread loop中需要stream buffer来组成request construction下发到camera HAL,使用buffer handle循环就没办法组装stream buffer。

acknowledge

  • 这个项目吸取教训,什么事情都得自己去验证才能保证准确性,最开始和fastmmi负责人沟通的时候他确定的说camera service和camera provider进程都是可以启动的,没有问题。之后芯片下来,我离线调试了一个周的时间,VNDK的方法基本上功能完整才到chip上面去测试,发现居然不行。当时人都蒙了,然后发现camera service居然无法正常去启动,不断地重启。和负责人沟通之后反馈没有办法启动camera service,因为会连带启动zygote UI进程,严重影响启动时间导致FFBM测试没有价值。当时听到这个消息人都蒙了,只能使用HIDL interface来构建,但是只有一周时间chip就要evb贴片量产,根本来不及,最后还是组长和PM决定在evb工厂测试中只是测试open camera,后续preview function后面再开发,觉得挺难受的,幸好头和组长也没有多说什么,希望今年年中奖不受影响。
  • 如果博客有什么不清楚或者错误或者疑惑的地方欢迎大家留言讨论,虚心向大家求教!新年快乐!

版权声明:本文为CSDN博主「chinamaoge」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chinamaoge/article/details/122658994

生成海报
点赞 0

chinamaoge

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

Android Camera AE和AF的设置

以geekcamera为例,当关闭闪光灯时,flash mode 0,代表flash为off模式; ae mode 1,代表ae为off模式,flash state 2

ESP8266 无限重启踩坑

最近做了一个电子墨水屏万年历,在移植屏幕代码时遇到了esp8266无限软复位的问题,如果你的串口打印是以下图片所示,那么恭喜你问题解决了。 造成软复位的原因是因为,程序里有死循环&#xf