• 虚拟摄像头之二: 配置v4l2loopback虚拟摄像头为前置或后置摄像头


    前言

    在android8.1系统中调用摄像头是通过 CameraManager::getCameraIdList() 方法获取系统摄像头列表,从列表中选择满足需要摄像头,用以拍照、录像或全景拍照。
    上篇中以介绍如何把v4l2loopback移植到android内核,本章介绍如何配置虚拟摄像头参数,以满足android用户程序直接使用。

    谁在读取camera配置信息

    首先,我们看一下 class CameraBase 这个类的定义,文件路径:
    @frameworks/av/camera/include/camera/CameraBase.h

    class CameraBase : public IBinder::DeathRecipient
    {
    public:
    	static status_t      getCameraInfo(int cameraId,
                                           /*out*/
                                           struct hardware::CameraInfo* cameraInfo);
    
        sp<TCamUser>         remote();
    
        // Status is set to 'UNKNOWN_ERROR' after successful (re)connection
        status_t             getStatus();
    protected:
        Mutex                            mLock;
        // helper function to obtain camera service handle
        static const sp<::android::hardware::ICameraService> getCameraService();
    
        sp<TCamUser>                     mCamera;
        status_t                         mStatus;
    
        sp<TCamListener>                 mListener;
    
        const int                        mCameraId;
    
        typedef CameraBase<TCam>         CameraBaseT;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    基类中 getCameraInfo() 是获取摄像头配置信息,其实现方法如下:
    @frameworks/av/camera/CameraBase.cpp

    // this can be in BaseCamera but it should be an instance method
    template <typename TCam, typename TCamTraits>
    status_t CameraBase<TCam, TCamTraits>::getCameraInfo(int cameraId,
            struct hardware::CameraInfo* cameraInfo) {
        const sp<::android::hardware::ICameraService> cs = getCameraService();
        if (cs == 0) return UNKNOWN_ERROR;
        binder::Status res = cs->getCameraInfo(cameraId, cameraInfo);
        return res.isOk() ? OK : res.serviceSpecificErrorCode();
    }
    template class CameraBase<Camera>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为camera在android系统中是采用C/S结构,/system/bin/mediaserverr是服务端守护进程,frameworks/av/camera下实现的是client内容,
    此方法是通过 getCameraService() 获取 sp<::android::hardware::ICameraService> cs 智能指针,从而调用服务端的
    cs->getCameraInfo(cameraId, cameraInfo) 的方法,我们进一步跟踪。

    camera 服务端守护进程

    文件路径
    @frameworks/av/services/camera/libcameraservice/CameraService.cpp

    Status CameraService::getCameraInfo(int cameraId,
            CameraInfo* cameraInfo) {
        ATRACE_CALL();
        Mutex::Autolock l(mServiceLock);
    
        if (!mInitialized) {
            return STATUS_ERROR(ERROR_DISCONNECTED,
                    "Camera subsystem is not available");
        }
    
        if (cameraId < 0 || cameraId >= mNumberOfCameras) {
            return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT,
                    "CameraId is not valid");
        }
    
        Status ret = Status::ok();
        status_t err = mCameraProviderManager->getCameraInfo(std::to_string(cameraId), cameraInfo);
        if (err != OK) {
            ret = STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
                    "Error retrieving camera info from device %d: %s (%d)", cameraId,
                    strerror(-err), err);
        }
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    通过跟踪源码、发现摄像属性参数是在CameraHAL.cpp文件中,构造摄像头时配置 Facing和Orientation属性值,
    源码路径如下:
    @vendor/nxp-opensource/imx/libcamera3/CameraHAL.cpp文件中,笔者使用的NXP的android8.1软件系统,
    硬件平台IMX8QM。以此类推,摄像头属性是在摄像头HAL层构建设备时通过源码类配置该属性。

    CameraHAL 配置参数分析

    因此参数因硬件平台的差异,配置文件会有差异,请读者根据自有硬件平台代码追踪此过程。
    @vendor/nxp-opensource/imx/libcamera3/CameraHAL.cpp

    CameraHAL::CameraHAL()
      : mCameraCount(0),
        mCallbacks(NULL)
    {
        // Allocate camera array and instantiate camera devices
        mCameras = new Camera*[MAX_CAMERAS];
        memset(mSets, 0, sizeof(mSets));
        memset(mCameras, 0, MAX_CAMERAS * sizeof(Camera*));
    
        // enumerate all camera sensors.
        enumSensorSet();                  //> 配置 camera 的参数子函数
    
        // check if camera exists.
        for (int32_t index=0; index<MAX_CAMERAS; index++) {
            if (!mSets[index].mExisting) {
                continue;
            }
            mCameras[index] = Camera::createCamera(index, mSets[index].mSensorName,
                                    mSets[index].mFacing, mSets[index].mOrientation,
                                    mSets[index].mDevPath);
    
            if (mCameras[index] == NULL) {
                // camera sensor is not supported now.
                // So, camera count should not change.
                ALOGW("Error: camera:%d, %s create failed", index,
                                     mSets[index].mSensorName);
            }
            else {
                mCameraCount++;
            }
        }
        ALOGI("camera number is %d", mCameraCount);
    
        mHotplugThread = new HotplugThread(this);
    }
    
    //> 配置 camera 的参数子函数
    void CameraHAL::enumSensorSet()
    {
        // get property from init.rc mSets.
        char orientStr[CAMERA_SENSOR_LENGTH];
    
        ALOGI("%s", __func__);
        // get back camera property.
        memset(orientStr, 0, sizeof(orientStr));
        property_get("back_camera_name", mSets[BACK_CAMERA_ID].mPropertyName, "0");
        property_get("back_camera_orient", orientStr, "0");
        mSets[BACK_CAMERA_ID].mOrientation = atoi(orientStr);
        mSets[BACK_CAMERA_ID].mFacing = CAMERA_FACING_BACK;
        mSets[BACK_CAMERA_ID].mExisting = false;
    
        // get front camera property.
        memset(orientStr, 0, sizeof(orientStr));
        property_get("front_camera_name", mSets[FRONT_CAMERA_ID].mPropertyName, "0");
        property_get("front_camera_orient", orientStr, "0");
        mSets[FRONT_CAMERA_ID].mOrientation = atoi(orientStr);
        mSets[FRONT_CAMERA_ID].mFacing = CAMERA_FACING_FRONT;
        mSets[FRONT_CAMERA_ID].mExisting = false;
    
        // make sure of back&front camera parameters.
        matchDevNodes();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    配置摄像头前置或后置

    通过源码分析、camera的参数配置是通过property设置来管理,此参数配置是在与硬件平台相关的 init.rc 中
    进行配置,IMX8QM的摄像配置参数如下:

    @device/fsl/imx8qm/mek_8q/init.rc 文件

        #Define the config for dual camera
        #For landscape mode, orient is 0
        #For portrait mode, orient is 90
        #the android before honycomb are all in portrait mode
        setprop camera.disable_zsl_mode 1
        # 后置摄像头
        setprop back_camera_name imx8_ov5640_mipi,max9286_mipi,imx8_ov5640
        setprop back_camera_orient 0
        # 前置摄像头
        setprop front_camera_name imx8_ov5640,imx8_ov5640_mipi,uvc
        setprop front_camera_orient 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们只需要把此摄像头名称修改为 v4l2loopback_back 和 v4l2loopback_front 名称,在构建 CameraHAL 库
    时,采用此名称就可以标注出并区别开来。

    NXP是如何匹配 Camera 节点属性

    int32_t CameraHAL::matchDevNodes()
    {
        DIR *vidDir = NULL;
        struct dirent *dirEntry;
        size_t nameLen = CAMERA_SENSOR_LENGTH - 1;
        nodeSet *nodes = NULL, *node = NULL, *last = NULL;
    
        ALOGI("%s", __func__);
        vidDir = opendir("/sys/class/video4linux");
        if (vidDir == NULL) {
            return -1;
        }
    
        while ((dirEntry = readdir(vidDir)) != NULL) {
            if (strncmp(dirEntry->d_name, "video", 5)) {
                continue;
            }
    
            node = (nodeSet*)malloc(sizeof(nodeSet));
            if (node == NULL) {
                ALOGE("%s malloc failed", __func__);
                break;
            }
    
            memset(node, 0, sizeof(nodeSet));
            if (nodes == NULL) {
                nodes = node;
            }
            else {
                last->next = node;
            }
            last = node;
            sprintf(node->devNode, "/dev/%s", dirEntry->d_name);
            getNodeName(node->devNode, node->nodeName, nameLen);
        }
    
        closedir(vidDir);
    
        for (int32_t index=0; index<MAX_CAMERAS; index++) {
            matchPropertyName(nodes, index);
        }
    
        node = nodes;
        while (node != NULL) {
            last = node->next;
            free(node);
            node = last;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    我们看一下 sys/class/video4linux 路径下都有哪些摄像头设备

    1|mek_8q:/ # ls sys/class/video4linux/                                                                                                               
    video0 video1 video12 video13 video2 video3 video4 video5 
    
    • 1
    • 2

    获取摄像头名称

    int32_t CameraHAL::getNodeName(const char* devNode, char name[], size_t length)
    {
        int32_t ret = -1;
        int32_t fd = -1;
        size_t strLen = 0;
        struct v4l2_capability vidCap;
        struct v4l2_dbg_chip_ident vidChip;
    
        ALOGI("getNodeName: dev path:%s", devNode);
        if ((fd = open(devNode, O_RDWR, O_NONBLOCK)) < 0) {
            ALOGW("%s open dev path:%s failed:%s", __func__, devNode,
                    strerror(errno));
            return ret;
        }
    
        ret = ioctl(fd, VIDIOC_QUERYCAP, &vidCap);
        if (ret < 0) {
            ALOGW("%s QUERYCAP dev path:%s failed", __func__, devNode);
            close(fd);
            fd = -1;
            return ret;
        }
    
        if (!(vidCap.capabilities &
              (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE))) {
            ALOGW("%s dev path:%s is not capture", __func__, devNode);
            close(fd);
            fd = -1;
            ret = -1;
            return ret;
        }
    
        strncat(name, (const char*)vidCap.driver, length);
        strLen = strlen((const char*)vidCap.driver);
        length -= strLen;
        ALOGI("getNodeName: node name:%s", name);
    
        ret = ioctl(fd, VIDIOC_DBG_G_CHIP_IDENT, &vidChip);
        if (ret < 0) {
            ALOGI("%s CHIP_IDENT dev path:%s failed", __func__, devNode);
            strncat(name, ",", length);
            strLen = 1;
            length -= strLen;
            strncat(name, (const char*)vidCap.card, length);
            ALOGI("getNodeNames: node name:%s", name);
            close(fd);
            fd = -1;
            return ret;
        }
    
        strncat(name, ",", length);
        strLen = 1;
        length -= strLen;
        strncat(name, vidChip.match.name, length);
    
        ALOGI("getNodeNames: node name:%s", name);
        close(fd);
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    摄像头名称属性是在驱动中标注的,需修改v4l2loopback驱动中节点名称,以此把该摄像头配置为
    前置或后置摄像头。

    至此位置,我们把 android8.1 中的、摄像头前置或后置的属性配置逻辑给梳理清晰了,如果感觉对你
    有所启发或帮助,请点赞或关注,以资鼓励笔者,感谢喽。

  • 相关阅读:
    【GPGPU编程模型与架构原理】第一章1.2 GPGPU 发展概述
    jvm虚拟机浅谈(二)
    Python学习五:面向对象设计程序
    IDEA代码替换
    Python——LRU_Cache
    css齿轮转动效果
    GeminiDB PITR,让游戏回档“进退自如”!
    深度学习损失函数
    [R] How to communicate with your data? - ggplot2
    使用开源的zip.cpp和unzip.cpp实现压缩包的创建与解压(附源码)
  • 原文地址:https://blog.csdn.net/weixin_38387929/article/details/126118717