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\n", __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\n");
        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).
}

至此,終於將整個流程連接到了一塊兒.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章