• android从应用到驱动之—camera(1)---程序调用流程


    一、开篇

    写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧.

    话说camera模块,从上层到底层一共包含着这么几个部分:

    1、apk------java语言

    2、camera的java接口----java语言

    3、camera的java接口的具体实现,即所谓的JNI-----(java—>C++)

    4、camera客户端-----C++语言

    5、camera服务器----C++语言

    6、camera硬件抽象层,即所谓的HAL------C++语言

    7、camera驱动

    如上也是camera的整个调用流程.

    但是,如此泛泛而谈,实在是太空了.想当初刚开始搞camera,单单驱动的V4L2已经够头大了,何况要涉及调试具体sensor的图像帧率,频率,输出格式等等,更不要说什么DMA对齐,图像闪动,色彩不对之类的疑难杂症了.

    还是分别讲讲它们分别的作用吧

    二、介绍

    1、apk

    APK是AndroidPackage的缩写,即Android安装包(apk)。(摘自百度百科)

    说白了,也就像windows系统上我们经常使用的qq等应用程序。它们通过调用系统提供的API(极个别的简单程序不调用)来实现某项或某几项功能。

    2、camera的java接口

    上文刚提到API,这里就已经到了介绍它的地方。没错,这里这个所谓的接口就是传说中的API.

    作为程序员,最通俗的说法无非是举例子.在apk中要想操作camera,必须要如下获取一个具体的camera对象.

    Camera camera = Camera.open(int cameraId);

    这个open是哪来的呢.它就是系统已经实现好的,你不需要管它哪来的,尽管调用就好了,这就是接口,这就是API.

    当然,应用开发者不需要管它是哪来的,我们得知道.对于android4.0来说.跟camera相关的接口是在这个文件中写好的:

    frameworks/base/core/java/android/hardware/Camera.java

    3、camera的java接口的具体实现

    对应用程序来说,接口已经提供出来了,直接调用就是了.但是这个接口是怎么实现的呢?

    众所周知,android系统(不包括内核)主要是由java和C++来写的.而内核(linux)是由c和汇编语言来写的(越底层,程序也就越面向机器,效率一般也越高).它们是怎么互相调用的呢?

    这里先说java是如何调用C++的.请大家稍微看一下我另一篇博客---JNI的实现方式.这里也稍微举一个小例子来简单说明java是怎么调用c++的.

    还是刚才那句:

    Camera camera = Camera.open(int cameraId);

    open的具体实现如下:frameworks/base/core/java/android/hardware/Camera.java

    public static Camera open(int cameraId) {
            return new Camera(cameraId);//新建一个camera(id);对象
        }

    如下是camera(id)的具体实现:

        Camera(int cameraId) {
            mShutterCallback = null;
            mRawImageCallback = null;
            mJpegCallback = null;
            mPreviewCallback = null;
            mPostviewCallback = null;
            mZoomListener = null;
     
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else if ((looper = Looper.getMainLooper()) != null) {
                mEventHandler = new EventHandler(this, looper);
            } else {
                mEventHandler = null;
            }
        
            native_setup(new WeakReference<Camera>(this), cameraId);    //此处camera的初始化
        }
    让我们来看一下camera的native_setup函数(一般android中native这个字段就表明了调用的是本地接口):
    private 
    native
     final void native_setup(Object camera_this, int cameraId);
    此处通过红色native字段可以确定native_setup调用的就是jni接口.即frameworks/base/core/jni/android_hardware_Camera.cpp中具体实现了这个函数,让我们来看看它是是如何转接这个函数的.
    { "native_setup",
        "(Ljava/lang/Object;I)V",
        (void*)android_hardware_Camera_native_setup },

    对,就是这里,它表示java中native_setup()真正的实现是android_hardware_Camera_native_setup() .就这样,java语言就直接调用了C++来实现.

    4、camera客户端+5、camera服务器

    client:frameworks/base/libs/camera/Camera.cpp

    service:frameworks/base/services/camera/libcameraservice/CameraService.cpp

    Service Manager在Binder机制中既充当守护进程的角色,同时它也充当着Server角色,然而它又与一般的Server不一样。对于普通的Server来说,Client如果想要获得Server的远程接口,那么必须通过Service Manager远程接口提供的getService接口来获得,这本身就是一个使用Binder机制来进行进程间通信的过程。而对于Service Manager这个Server来说,Client如果想要获得Service Manager远程接口,却不必通过进程间通信机制来获得,因为Service Manager远程接口是一个特殊的Binder引用,它的引用句柄一定是0。

    这是网上直接搜来的。不是太通俗,而且牵扯到binder通信。下面说说我的理解。

    现在正值春运,就拿乘坐火车回家来说吧。client(客户端)其实就相当于我们每个人,我们都想回家,都想买到理想中的火车票(最好是卧铺).但是火车的运力是一定的.不可能每个人都买到回家的票.如果没人管,那坐火车就变成谁的块头大谁能上车了.铁道部(今年刚改革了)就是这个管理者(service),我买了今天的A火车B车厢C座.其他人要是还想坐这个座位,对不起,应经被占用了.要不您就不坐或者等一会看我退票了您再来抢(注意这个抢字,说多了都是泪)。

    对应到android.一个android手机中有可能有好几个照相机应用.每一个应用实现一个client.如果A应用从service申请到了camera,并且在后台录像.这时候B也要打开照相机,那service就会直接回复说,不好意思,设备正使用中(BUZY).您晚些时候再来吧.

    6、camera硬件抽象层

    HAL是干什么的呢?

    由client和service可知,火车的管理是掌握在铁道部手里的,其他人想不经过铁道部直接上车,一个字:难!  两个字:拼爹。

    要说这HAL,其实对应的就是这个火车的车长。哪个车厢是卧铺哪个车厢是硬座,都是它来搞定的.

    service是领导,如果要它直接跟每个驱动打交道,那还不得累死,通过HAL多好,有什么需求,直接发个HAL就好了,它来调用跟内核跟驱动协商的具体实现步骤.

    当然,官方一点的解释是这样的(从这里偷来的):

    Android的HAL是为了保护一些硬件提供商的知识产权而提出的,是为了避开linux的GPL束缚。思路是把控制硬件的动作都放到了Android HAL中,而linux driver仅仅完成一些简单的数据交互作用,甚至把硬件寄存器空间直接映射到user space。而Android是基于Aparch的license,因此硬件厂商可以只提供二进制代码,所以说Android只是一个开放的平台,并不是一个开源的平台。也许也正是因为Android不遵从GPL,所以Greg Kroah-Hartman才在2.6.33内核将Andorid驱动从linux中删除。GPL和硬件厂商目前还是有着无法弥合的裂痕。Android想要把这个问题处理好也是不容易的。

        总结下来,Android HAL存在的原因主要有:

        1. 并不是所有的硬件设备都有标准的linux kernel的接口

        2. KERNEL DRIVER涉及到GPL的版权。某些设备制造商并不原因公开硬件驱动,所以才去用HAL方 式绕过GPL。

        3. 针对某些硬件,An有一些特殊的需求

    7、camera驱动

    说完hal,总算该说驱动了,可是真要开始介绍,又不知从哪里开始讲比较好.

    暂且放着吧,我会有专门的博客来写驱动的.

     

    三、调用流程

    啰嗦了一大堆,就为了这篇博客的最终目的----流程.那下面就用一个实例来简单分析一下它们之间是如何调用到对方的.这里仅仅通过一两个功能分析其调用流程,这个搞通了之后再分别介绍一下cameraHAL的实现和驱动的调试(也仅仅是这两个模块了,毕竟主要是搞底层开发,java不是我的擅长)

    还是从应用程序开始:

     
    private void initCamera()
     {
      Camera camera;  // 定义系统所用的照相机 
      if (!isPreview)
      {
       camera = Camera.open();  //1.调用Camera的open()方法打开相机。
      }
      if (camera != null && !isPreview)
      {
       try
       {
        //2.调用Camera的setParameters()方法获取拍照参数。该方法返回一个Camera.Parameters对象。
        Camera.Parameters parameters = camera.getParameters();   
        //3.调用Camera.Paramers对象方法设置拍照参数
        // 设置预览照片的大小
        parameters.setPreviewSize(screenWidth, screenHeight);
        // 每秒显示4帧
        parameters.setPreviewFrameRate(4);
        // 设置图片格式
        parameters.setPictureFormat(PixelFormat.JPEG);
        // 设置JPG照片的质量
        parameters.set("jpeg-quality",85);
        //设置照片的大小
        parameters.setPictureSize(screenWidth, screenHeight);   
        
        //4.调用Camera的setParameters,并将Camera.Paramers作为参数传入,这样即可对相机的拍照参数进行控制
        camera.setParameters(parameters);   
        /**
        *  5.调用Camera的startPreview()方法开始预览取景,在预览取景之前需要调用
        *  Camera的setPreViewDisplay(SurfaceHolder holder)方法设置使用哪个SurfaceView来显示取景图片。
        *  通过SurfaceView显示取景画面
        */   
        camera.setPreviewDisplay(surfaceHolder);
        // 6.开始预览
        camera.startPreview();
        // 7.自动对焦
        camera.autoFocus(afcb);
        // 8.调用Camera的takePicture()方法进行拍照.
        camera.takePicture(null, null , myjpegCallback);
       }
       catch (Exception e)
       {
        e.printStackTrace();
       }
       isPreview = true;
      }
     }

    别指望上边的程序能运行,这里只是为了介绍其流程,预览界面什么都没有.这里我们只看open的过程,它调用

    --->frameworks/base/core/java/android/hardware/Camera.java

        public static Camera open() {                
            int numberOfCameras = getNumberOfCameras();
            CameraInfo cameraInfo = new CameraInfo();
            for (int i = 0; i < numberOfCameras; i++) {
                getCameraInfo(i, cameraInfo);
                if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {        //此处强制打开后置,遍历存在的摄像头之后还是没有后置则会返回null
                    return new Camera(i);        //此处新建一个camera(id)对象
                }    
            }    
            return null;
        }

    getNumberOfCameras和getCameraInfo这两个介绍cameraHAL的时候再具体介绍.下面看new  camera的实现.

    Camera(int cameraId) {
        mShutterCallback = null;
        mRawImageCallback = null;
        mJpegCallback = null;
        mPreviewCallback = null;
        mPostviewCallback = null;
        mZoomListener = null;
     
        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }
     
        native_setup(new WeakReference<Camera>(this), cameraId);    //此处camera的初始化
    }
    private native final void native_setup(Object camera_this, int cameraId);

    上文我们就已经知道native_setup调用的是jni接口.即

    frameworks/base/core/jni/android_hardware_Camera.cpp的android_hardware_Camera_native_setup

        static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
        jobject weak_this, jint cameraId)
        {
            sp<Camera> camera = Camera::connect(cameraId);    //调用的是frameworks/base/libs/camera/Camera.cpp
            ...
        }

    然后来看client的调用(注意调用时sp<Camera> ,有玄机哦,自行百度,我还不大懂).

    frameworks/base/libs/camera/Camera.cpp

    sp<Camera> Camera::connect(int cameraId)
    {   
       LOGV("connect");
       sp<Camera> c = new Camera();
       const sp<ICameraService>& cs = getCameraService();
       if (cs != 0) {
           c->mCamera = cs->connect(c, cameraId);        //此处调用frameworks/base/services/camera/libcameraservice/CameraService.cpp
       }
       if (c->mCamera != 0) {
           c->mCamera->asBinder()->linkToDeath(c);
           c->mStatus = NO_ERROR;
       } else {
           c.clear();
       }
       return c;
    }

    就是这个connect,它直接调用了cameraservice.

    frameworks/base/services/camera/libcameraservice/CameraService.cpp

    sp<ICamera> CameraService::connect(
           const sp<ICameraClient>& cameraClient, int cameraId) {
            .....
            hardware = new CameraHardwareInterface(camera_device_name);
            if (hardware->initialize(&mModule->common) != OK) {                //此处调用frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h初始化camera
                hardware.clear();
                return NULL;
            }
            client = new Client(this, cameraClient, hardware, cameraId, info.facing, callingPid);
            mClient[cameraId] = client;
            LOG1("CameraService::connect X");
            return client;
    }

    继续往下调用,它应该去调用HAL层的初始化函数,刚开始找的时候怎么也找不到,原来是在头文件中调用的,我嘞个去.

    frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h

    status_t initialize(hw_module_t *module)
    {   
        LOGI("Opening camera %s", mName.string());
        int rc = module->methods->open(module, mName.string(),                //module就是hardware所建立的module. 通过hardware.h可以看出最终调用的是厂商的hardware
                                       (hw_device_t **)&mDevice);
        if (rc != OK) {
            LOGE("Could not open camera %s: %d", mName.string(), rc);
            return rc; 
        }   
        initHalPreviewWindow();
        return rc; 
    } 

    下面让我们看看这个open.以下就是所谓的HAL

    它是这样定义的

    static hw_module_methods_t camera_module_methods = {
                open : HAL_camera_device_open
    };
    static int HAL_camera_device_open(const struct hw_module_t* module,
                                      const char *id, 
                                      struct hw_device_t** device)
    {
        int cameraId = atoi(id);
        ...//此处省略参数的配置,介绍HAL的时候再详细介绍
        g_cam_device->priv = new CameraHardwareSec(cameraId, g_cam_device);                //此处初始化具体的camera设备
     
    done:
        *device = (hw_device_t *)g_cam_device;
        return 0;
    }

    camera设备的初始化:

    CameraHardwareSec::CameraHardwareSec(int cameraId, camera_device_t *dev)
            :
              mCaptureInProgress(false),
              mParameters(),
              mFrameSizeDelta(0),
              mCameraSensorName(NULL),
              mUseInternalISP(false),
              mSkipFrame(0),
              mNotifyCb(0),
              mDataCb(0),
              mDataCbTimestamp(0),
              mCallbackCookie(0),
              mMsgEnabled(CAMERA_MSG_RAW_IMAGE),
              mRecordRunning(false),
              mPostViewWidth(0),
              mPostViewHeight(0),
              mPostViewSize(0),
              mCapIndex(0),
              mCurrentIndex(-1),
              mOldRecordIndex(-1),
              mRecordHint(false),
              mRunningSetParam(0),
              mTouched(0),
              mFDcount(0),
              mRunningThread(0),
              mHalDevice(dev)
    {
    .....
        ret = mSecCamera->CreateCamera(cameraId);        //Step1:此处新建camera
        if (ret < 0) {
            mSecCamera->DestroyCamera();
        }
        initDefaultParameters(cameraId);//使用默认的参数初始化指定摄像头
        mPreviewThread = new PreviewThread(this);//预览线程
        mPreviewFimcThread = new PreviewFimcThread(this);//预览线程
        mRecordFimcThread = new RecordFimcThread(this);//录像线程
        mSnapshotFimcThread = new SnapshotFimcThread(this);//快照线程
        mCallbackThread = new CallbackThread(this);//回调线程
        mAutoFocusThread = new AutoFocusThread(this);//自动对焦线程
        mPictureThread = new PictureThread(this);//照相线程
    }

    介绍CreateCamera之前要先知道一些定义,这里也是三星驱动中FIMCx是怎么被配置成不同功能的:

    #define CAMERA_DEV_NAME   "/dev/video0"
    #define CAMERA_DEV_NAME2  "/dev/video2"
    #define CAMERA_DEV_NAME3  "/dev/video1"

    具体实现:

    bool SecCamera::CreateCamera(int index){
            ...    //此处省略n行
            /* FIMC0 open */                                              
            ret = createFimc(&m_cam_fd, CAMERA_DEV_NAME, V4L2_BUF_TYPE_VIDEO_CAPTURE, index);    //Step1-1:和底层打交道开始    打开FIMC0作为取景输入(##define CAMERA_DEV_NAME   "/dev/video0")
            CHECK(ret);
            m_camera_use_ISP = getUseInternalISP();
            if (m_camera_use_ISP) {
                if (!m_recording_en)
                    fimc_v4l2_s_fmt_is(m_cam_fd, m_preview_max_width, m_preview_max_height,
                            m_preview_v4lformat, (enum v4l2_field) IS_MODE_PREVIEW_STILL);
                else
                    fimc_v4l2_s_fmt_is(m_cam_fd, m_preview_max_width, m_preview_max_height,
                            m_preview_v4lformat, (enum v4l2_field) IS_MODE_PREVIEW_VIDEO);
            }
            ret = fimc_v4l2_s_fmt(m_cam_fd, m_preview_max_width, m_preview_max_height,
                          m_preview_v4lformat, V4L2_FIELD_ANY, PREVIEW_NUM_PLANE);
            CHECK(ret);
            initParameters(m_camera_use_ISP);
            if (m_camera_use_ISP) {        //使用4412自带的ISP
                /* FIMC2 open for recording and zsl, video snapshot m2m */
                ret = createFimc(&m_cam_fd3, CAMERA_DEV_NAME2, V4L2_BUF_TYPE_VIDEO_OUTPUT, index);    //Step1-2:打开FIMC2作为输出设备,(将图像数据输出到FIMC2进行处理)
                CHECK(ret);
                /* FIMC1 open for preview m2m */
                ret = createFimc(&m_cam_fd2, CAMERA_DEV_NAME3, V4L2_BUF_TYPE_VIDEO_OUTPUT, index);    //Step1-3:打开FIMC1作为输出设备,(将图像数据输出到FIMC2进行处理)
                CHECK(ret);
            } else {                    //不使用4412自带的ISP
                /* FIMC1 open */    //Step1-4:打开FIMC1作为输入设备
                ret = createFimc(&m_cam_fd2, CAMERA_DEV_NAME3, V4L2_BUF_TYPE_VIDEO_CAPTURE, index);
                CHECK(ret);
            }
            setExifFixedAttribute();
            if (m_camera_use_ISP) {            //如果使用4412自带ISP,则定义如下
                m_prev_fd = m_cam_fd2;        //此处定义fimc1为预览        fimc0为取景
                m_cap_fd = m_cam_fd3;        //此处定义fimc2为照相
                m_rec_fd = m_cam_fd3;        //此处定义fimc2为录像
                m_num_capbuf = CAP_BUFFERS;
            } else {                        //如果不使用4412自带ISP,则定义如下
                m_prev_fd = m_cam_fd;        //此处定义fimc0为预览        
                m_cap_fd = m_cam_fd;        //此处定义fimc0为照相
                m_rec_fd = m_cam_fd2;        //此处定义fimc1为录像
                m_num_capbuf = 1;
            }
        }
        return 0;
    }

    看到了吧,如上就是配置不同的FIMC作为不同功能的地方.这里指跟踪createFimc函数

    int SecCamera::createFimc(int *fp, char *dev_name, int mode, int index)
    {
        struct v4l2_format fmt; 
        int ret = 0; 
        *fp = open(dev_name, O_RDWR);        //打开指定的FIMCx 即对应设备节点/dev/videox
        if (fp < 0) { 
            return -1;
        }    
        if (mode == V4L2_BUF_TYPE_VIDEO_CAPTURE) {        //如果buf类型为v4l2 capture(捕获、输入) 此处对应Step1-1和Step1-4
            ret = fimc_v4l2_querycap(*fp);        //查询设备是否支持V4L2_BUF_TYPE_VIDEO_CAPTURE    Step1-1-1
            CHECK(ret);
            if (!fimc_v4l2_enuminput(*fp, index)) {        //查询是否存在此摄像头Step1-1-2
                return -1;
            }    
            ret = fimc_v4l2_s_input(*fp, index);        //选择此摄像头作为输入设备Step1-1-3
            CHECK(ret);
        } else if (mode == V4L2_BUF_TYPE_VIDEO_OUTPUT) {    //如果缓冲区类型为V4L2_BUF_TYPE_VIDEO_OUTPUT(输出)    此处对应Step1-2和Step1-3
            ret = fimc_v4l2_querycap_m2m(*fp);            //查询是否存在此设备及其能力是否支持Step1-2-1
            CHECK(ret);
            /* malloc fimc_outinfo structure */
            fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
            if (ioctl(*fp, VIDIOC_G_FMT, &fmt) < 0) { //查询设备是否支持视频输出(处理图像)  V4L2接口哦
                return -1;
            }
        }
        return ret;
    }

    具体函数:

    Step1-1-1:
    static int fimc_v4l2_querycap(int fp)
    {
        struct v4l2_capability cap; 
        if (ioctl(fp, VIDIOC_QUERYCAP, &cap) < 0) {         //查询设备能力    //Step1-1-1-1 ioctl的底层实现
            return -1;
        }    
        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {    //判断设备是否是图像采集设备
            return -1;
        }    
        return 0;
    }
    Step1-1-2:
    static const __u8* fimc_v4l2_enuminput(int fp, int index)
    {
        static struct v4l2_input input;
     
        input.index = index;
        if (ioctl(fp, VIDIOC_ENUMINPUT, &input) != 0) {    //查询是否存在此摄像头设备
            return NULL;
        }
        LOGI("Name of input channel[%d] is %s", input.index, input.name);
        return input.name;
    }

    Step1-1-1-1 ioctl的底层实现:这里就该调用到内核层了,让我们先看一下V4L2基本定义:

    VIDIOC_QUERYCAP     /* 获取设备支持的操作 */
    VIDIOC_G_FMT        /* 获取设置支持的视频格式 */
    VIDIOC_S_FMT        /* 设置捕获视频的格式 */
    VIDIOC_REQBUFS      /* 向驱动提出申请内存的请求 */
    VIDIOC_QUERYBUF     /* 向驱动查询申请到的内存 */
    VIDIOC_QBUF         /* 将空闲的内存加入可捕获视频的队列 */
    VIDIOC_DQBUF        /* 将已经捕获好视频的内存拉出已捕获视频的队列 */
    VIDIOC_STREAMON     /* 打开视频流 */
    VIDIOC_STREAMOFF    /* 关闭视频流 */
    VIDIOC_QUERYCTRL    /* 查询驱动是否支持该命令 */
    VIDIOC_G_CTRL       /* 获取当前命令值 */
    VIDIOC_S_CTRL       /* 设置新的命令值 */
    VIDIOC_G_TUNER      /* 获取调谐器信息 */
    VIDIOC_S_TUNER      /* 设置调谐器信息 */
    VIDIOC_G_FREQUENCY  /* 获取调谐器频率 */
    VIDIOC_S_FREQUENCY  /* 设置调谐器频率 */
    VIDIOC_CROPCAP        /* 视频裁剪和缩放功能信息 */
    VIDIOC_G_CROP        /* 获取当前的裁剪矩形 */
    VIDIOC_S_CROP        /* 设置当前的裁剪矩形 */
    VIDIOC_ENUM_INPUT    /* 枚举视频输入 */
    VIDIOC_G_INPUT        /* 查询当前视频输入设备是哪个 */
    VIDIOC_S_INPUT        /* 选择当前视频输入设备是哪个 */
    VIDIOC_G_PARM        /* 获取流参数 */
    VIDIOC_S_PARM        /* 设置流参数 */
    VIDIOC_QUERYCTRL    /* 枚举控制项目 */
    VIDIOC_QUERYMENU    /* 枚举菜单控制项目 */
    VIDIOC_G_FBUF        /* 获取帧缓冲区 */        获取参数?
    VIDIOC_S_FBUF        /* 覆盖帧缓冲区 */        设置参数?

    后边这两个暂时没看.

    最终调用的函数是这样定义的.至于如何调用到这里,稍后专门讲驱动的时候再详细写.

    drivers/media/video/samsung/fimc/fimc_v4l2.c

    const struct v4l2_ioctl_ops fimc_v4l2_ops = { 
            .vidioc_querycap                = fimc_querycap,    
            .vidioc_reqbufs                 = fimc_reqbufs,            
            .vidioc_querybuf                = fimc_querybuf,        
            .vidioc_g_ctrl                  = fimc_g_ctrl,            
            .vidioc_g_ext_ctrls             = fimc_g_ext_ctrls,                
            .vidioc_s_ctrl                  = fimc_s_ctrl,        
            .vidioc_s_ext_ctrls             = fimc_s_ext_ctrls,                
            .vidioc_cropcap                 = fimc_cropcap,
            .vidioc_g_crop                  = fimc_g_crop,
            .vidioc_s_crop                  = fimc_s_crop,
            .vidioc_streamon                = fimc_streamon,
            .vidioc_streamoff               = fimc_streamoff,
            .vidioc_qbuf                    = fimc_qbuf,
            .vidioc_dqbuf                   = fimc_dqbuf,
            .vidioc_enum_fmt_vid_cap        = fimc_enum_fmt_vid_capture,    
            .vidioc_g_fmt_vid_cap           = fimc_g_fmt_vid_capture,        
            .vidioc_s_fmt_vid_cap           = fimc_s_fmt_vid_capture,        
            .vidioc_s_fmt_type_private      = fimc_s_fmt_vid_private,        
            .vidioc_try_fmt_vid_cap         = fimc_try_fmt_vid_capture,        
            .vidioc_enum_input              = fimc_enum_input,
            .vidioc_g_input                 = fimc_g_input,
            .vidioc_s_input                 = fimc_s_input,
            .vidioc_g_parm                  = fimc_g_parm,
            .vidioc_s_parm                  = fimc_s_parm,
            .vidioc_queryctrl               = fimc_queryctrl,
            .vidioc_querymenu               = fimc_querymenu,
            .vidioc_g_fmt_vid_out           = fimc_g_fmt_vid_out,            
            .vidioc_s_fmt_vid_out           = fimc_s_fmt_vid_out,            
            .vidioc_try_fmt_vid_out         = fimc_try_fmt_vid_out,            
            .vidioc_g_fbuf                  = fimc_g_fbuf,
            .vidioc_s_fbuf                  = fimc_s_fbuf,
            .vidioc_try_fmt_vid_overlay     = fimc_try_fmt_overlay,
            .vidioc_g_fmt_vid_overlay       = fimc_g_fmt_vid_overlay,
            .vidioc_s_fmt_vid_overlay       = fimc_s_fmt_vid_overlay,
            .vidioc_enum_framesizes         = fimc_enum_framesizes,
            .vidioc_enum_frameintervals     = fimc_enum_frameintervals,
    };

    如下是HAL层调用的具体查询camera属性的实现.

    static int fimc_querycap(struct file *filp, void *fh,
                             struct v4l2_capability *cap)
    {
            struct fimc_control *ctrl = ((struct fimc_prv_data *)fh)->ctrl;
            fimc_info1("%s: called
    ", __func__);
            strcpy(cap->driver, "SEC FIMC Driver");
            strlcpy(cap->card, ctrl->vd->name, sizeof(cap->card));        //Step1-1-1-1-1:name是直接取值于具体的驱动
            sprintf(cap->bus_info, "FIMC AHB-bus");
            cap->version = 0;
            cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
                                    V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING);    //真把自己当神器了,什么属性都有....
            return 0;
    }

    最后再贴两个函数,解释name是如何获取到的

    drivers/media/video/s5k4ea.c        //摄像头的驱动文件

    static int s5k4ea_probe(struct i2c_client *client,
                             const struct i2c_device_id *id)
    {
            struct s5k4ea_state *state;
            struct v4l2_subdev *sd;
            state = kzalloc(sizeof(struct s5k4ea_state), GFP_KERNEL);
            if (state == NULL)
                    return -ENOMEM;
            sd = &state->sd;
            strcpy(sd->name, S5K4EA_DRIVER_NAME);        //此处赋值成摄像头的具体名字
            /* Registering subdev */
            v4l2_i2c_subdev_init(sd, client, &s5k4ea_ops);    //此处注册摄像头设备,此函数中获得摄像头的名字
            dev_info(&client->dev, "s5k4ea has been probed
    ");
            return 0;
    }

    v4l2_i2c_subdev_init函数将具体摄像头的驱动中获取的名字加工后搞到设备名里边供返回给上层应用(HAL)

    drivers/media/video/v4l2-common.c

    void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
                    const struct v4l2_subdev_ops *ops)
    {
            v4l2_subdev_init(sd, ops);
            sd->flags |= V4L2_SUBDEV_FL_IS_I2C;
            /* the owner is the same as the i2c_client's driver owner */
            sd->owner = client->driver->driver.owner;
            /* i2c_client and v4l2_subdev point to one another */
            v4l2_set_subdevdata(sd, client);
            i2c_set_clientdata(client, sd);
            /* initialize name */
            snprintf(sd->name, sizeof(sd->name), "%s %d-%04x",
                    client->driver->driver.name, i2c_adapter_id(client->adapter),
                    client->addr);            //这里将摄像头名字更新到sd->name中,上层调用时返回sd->name(位置Step1-1-1-1-1).
    }

    至此,终于将整个流程连接到了一块儿.

    很抱歉的是没有完全介绍初始化时整个camera模块每一步的具体实现.还是留到HAL层和驱动层分别介绍吧,明天写HAL,不知道一天时间能否写完.希望春节回家之前把camera模块都写完吧.

  • 相关阅读:
    html----响应式布局,左侧栏目固定,右侧内容随着屏幕宽度变化而变化
    es6----set map应用场景
    html----实现元素上下左右居中
    html----怎样实现元素的垂直居中
    html----BFC独立渲染区
    js-----new一个对象的过程
    解决ios手机上传竖拍照片旋转90度的问题
    软键盘遮挡问题
    在不同浏览器中,input里面的输入光标大小表现形式却大不相同
    样式兼容开头
  • 原文地址:https://www.cnblogs.com/joseph-linux/p/3515151.html
Copyright © 2020-2023  润新知