Android設備驅動之——V4L2

Video for Linux Two
          
           V4L2的是V4L的第二個版本。原來的V4L被引入到Linux內核2.1.x的開發周期後期。Video4Linux2修正瞭一些設計缺陷,並開始出現在2.5.X內核。Video4Linux2驅動程序包括Video4Linux1應用的兼容模式,但實際上,支持是不完整的,並建議V4L2的設備使用V4L2的模式。現在,該項目的DVB-Wiki托管在LinuxTV的網站上。

        要想瞭解 V4l2 有幾個重要的文檔是必須要讀的,Documentation/video4linux目錄下的V4L2-framework.txt和videobuf、V4L2的官方API文檔V4L2 API Specification 、drivers/media/video目錄下的vivi.c(虛擬視頻驅動程序 -此代碼模擬一個真正的視頻設備V4L2 API)。

        V4l2可以支持多種設備,它可以有以下幾種接口:

         1. 視頻采集接口(video capture interface):這種應用的設備可以是高頻頭或者攝像頭.V4L2的最初設計就是應用於這種功能的.

         2. 視頻輸出接口(video output interface):可以驅動計算機的外圍視頻圖像設備–像可以輸出電視信號格式的設備.

         3. 直接傳輸視頻接口(video overlay interface):它的主要工作是把從視頻采集設備采集過來的信號直接輸出到輸出設備之上,而不用經過系統的CPU.

         4. 視頻間隔消隱信號接口(VBI interface):它可以使應用可以訪問傳輸消隱期的視頻信號.

         5. 收音機接口(radio interface):可用來處理從AM或FM高頻頭設備接收來的音頻流.

V4L2 驅動核心

         V4L2 的驅動源碼在 drivers/media/video目錄下,主要核心代碼有:
              v4l2-dev.c                  //linux版本2視頻捕捉接口,主要結構體 video_device 的註冊
 
              v4l2-common.c        //在Linux操作系統體系采用低級別的操作一套設備structures/vectors的通用視頻設備接口。
                                  //此文件將替換videodev.c的文件配備常規的內核分配。

              v4l2-device.c            //V4L2的設備支持。註冊v4l2_device
 
              v4l22-ioctl.c             //處理V4L2的ioctl命令的一個通用的框架。
                   
              v4l2-subdev.c          //v4l2子設備
 
              v4l2-mem2mem.c  //內存到內存為Linux和videobuf視頻設備的框架。設備的輔助函數,使用其源和目的地videobuf緩沖區。

   頭文件linux/videodev2.h、media/v4l2-common.h、media/v4l2-device.h、media/v4l2-ioctl.h、media/v4l2-dev.h、media/v4l2-ioctl.h等。

V4l2相關結構體

 1.V4l2_device

 struct V4l2_device{

      /* DEV-> driver_data指向這個結構。 註:DEV可能是空的,如果沒有父設備是如同ISA設備。 */
      struct device *dev;
     /* 用於跟蹤註冊的subdevs */
    struct list_head subdevs;
    /*鎖定此結構體;可以使用的驅動程序以及如果這個結構嵌入到一個更大的結構。 */
    spinlock_t lock;
    /* 獨特的設備名稱,默認情況下,驅動程序姓名+總線ID */
    char name[V4L2_DEVICE_NAME_SIZE];
   /*報告由一些子設備調用的回調函數。 */
     void (*notify)(struct v4l2_subdev *sd,
     unsigned int notification, void *arg);
};

v4l2_device註冊和註銷     
   
       v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);  

     第一個參數‘dev’通常是一個pci_dev的struct device的指針,但它是ISA設備或一個設備創建多個PCI設備時這是罕見的DEV為NULL,因此makingit不可能聯想到一個特定的父母v4l2_dev。 您也可以提供一個notify()回調子設備,可以通過調用通知你的事件。取決於你是否需要設置子設備。一個子設備支持的任何通知必須在頭文件中定義 .
  
    註冊時將初始化 v4l2_device 結構體. 如果 dev->driver_data字段是空, 它將連接到 v4l2_dev.

       v4l2_device_unregister(struct v4l2_device *v4l2_dev);
        
    註銷也將自動註銷設備所有子設備。

2.video_device

      在/dev目錄下的設備節點使用的 struct video_device(v4l2_dev.h)創建。
 struct video_device
     {
         /*設備操作函數 */
         const struct v4l2_file_operations *fops;

         /* 虛擬文件系統 */
         struct device dev;        /* v4l 設備 */
         struct cdev *cdev;        /* 字符設備 */

        struct device *parent;        /*父設備 */
        struct v4l2_device *v4l2_dev;    /* v4l2_device parent */

        /* 設備信息 */
         char name[32];
         int vfl_type;
         /* 'minor' is set to -1 if the registration failed */
        int minor;
        u16 num;
         /* use bitops to set/clear/test flags */
         unsigned long flags;
         /*屬性來區分一個物理設備上的多個索引 */
        int index;

         /* V4L2 文件句柄 */
         spinlock_t        fh_lock; /*鎖定所有的 v4l2_fhs */
         struct list_head    fh_list; /* List of struct v4l2_fh */

         int debug;            /* Activates debug level*/

         /* Video standard vars */
         v4l2_std_id tvnorms;        /* Supported tv norms */
         v4l2_std_id current_norm;    /* Current tvnorm */

         /* 釋放的回調函數 */
        void (*release)(struct video_device *vdev);

         /* 控制的回調函數 */
         const struct v4l2_ioctl_ops *ioctl_ops;
     }

  動態分配:
          struct video_device *vdev = video_device_alloc();

   結構體配置:

         fops:設置這個v4l2_file_operations結構,file_operations的一個子集。v4l2_dev: 設置這個v4l2_device父設備
         name:
         ioctl_ops:使用v4l2_ioctl_ops簡化的IOCTL,然後設置v4l2_ioctl_ops結構。
         lock:如果你想要做的全部驅動程序鎖定就保留為NULL。否則你給它一個指針指向一個mutex_lock結構體和任何v4l2_file_operations被調用之前核心應該釋放釋放鎖。
         parent:一個硬件設備有多個PCI設備,都共享相同v4l2_device核心時,設置註冊使用NULL v4l2_device作為父設備結構。
         flags:可選的。設置到V4L2_FL_USE_FH_PRIO如你想讓框架處理VIDIOC_G/ S_PRIORITY的ioctl。這就需要您使用結構v4l2_fh。這個標志最終會消失,一旦所有的驅動程序使用的核心優先處理。但現在它必須明確設定。

   如果使用v4l2_ioctl_ops,那麼你應該設置。unlocked_ioctlvideo_ioctl2在v4l2_file_operations結構。

 註冊/註銷 video_device:

    video_register_device(struct video_device *vdev, int type, int nr);
    __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use)
        參數:
           vdev:我們要註冊的視頻設備結構。
           type:設備類型註冊
           nr:設備號(0==/dev/video0,1​​== /dev/video1,…-1==釋放第一個)
           warn_if_nr_in_use:如果所需的設備節點號碼已經在使用另一個號碼代替選擇。
   
       註冊程式分配次設備號和設備節點的數字根據請求的類型和註冊到內核新設備節點。如果無法找到空閑次設備號或設備節點編號,或者如果設備節點註冊失敗,就返回一個錯誤。
 
           video_unregister_device(struct video_device *vdev);

  3.v4l2_subdev
 
          每個子設備驅動程序必須有一個v4l2_subdev結構。這個結構可以獨立簡單的設備或者如果需要存儲更多的狀態信息它可能被嵌入在一個更大的結構。由於子設備可以做很多不同的東西,你不想結束一個巨大的OPS結構其中隻有少數的OPS通常執行,函數指針進行排序按類別,每個類別都有其自己的OPS結構。頂層OPS結構包含的類別OPS結構,這可能是NULL如果在subdev驅動程序不支持任何從該類別指針。
         struct v4l2_subdev {
     #if defined(CONFIG_MEDIA_CONTROLLER)
         struct media_entity entity;
     #endif
         struct list_head list;
         struct module *owner;
         u32 flags;
         struct v4l2_device *v4l2_dev;
         const struct v4l2_subdev_ops *ops;
         /* 從驅動程序中不要調用這些內部操作函數! */
         const struct v4l2_subdev_internal_ops *internal_ops;
         /*這個subdev控制處理程序。可能是NULL。 */
         struct v4l2_ctrl_handler *ctrl_handler;
         /* 名字必須是唯一 */
         char name[V4L2_SUBDEV_NAME_SIZE];
         /* 可用於到類似subdevs組,值是驅動程序特定的 */
         u32 grp_id;
         /* 私有數據的指針 */
         void *dev_priv;
         void *host_priv;
         /* subdev 設備節點*/
         struct video_device devnode;
         /* 事件的數量在打開的時候被分配 */
         unsigned int nevents;
      };
 

 4.v4l2_buffer

struct v4l2_buffer {
    __u32            index;
    enum v4l2_buf_type      type;
    __u32            bytesused;
    __u32            flags;
     enum v4l2_field        field;
    struct timeval        timestamp;
     struct v4l2_timecode    timecode;
    __u32            sequence;

    /* memory location */
    enum v4l2_memory        memory;
    union {
        __u32           offset;
        unsigned long   userptr;
    } m;
    __u32            length;
    __u32            input;
    __u32            reserved;
};

   V4L2核心API提供瞭一套標準方法的用於處理視頻緩沖器(稱為“videobuf”)。這些方法允許驅動程序以一致的方式來實現read(),mmap()和overlay()。目前使用的設備上的視頻緩沖器,支持scatter/gather方法(videobuf-dma-SG),線性存取的DMA的(videobuf-DMA-contig),vmalloc分配的緩沖區,主要用於在USB驅動程序(DMA緩沖區的方法videobuf-vmalloc)。

   videobuf層的功能為一種V4L2驅動和用戶空間之間的粘合層。它可以處理存儲視頻幀緩沖區的分配和管理。有一組可用於執行許多標準的POSIX I / O系統調用的功能,包括read(),poll()的,happily,mmap()。另一套功能可以用來實現大部分的V4L2的ioctl()調用相關的流式I/ O的,包括緩沖區分配,排隊和dequeueing,流控制。驅動作者使用videobuf規定瞭一些設計決定,但回收期在驅動器和一個V4L2的用戶空間API的貫徹實施在減少代碼的形式。

   關於videobuf的層的更多信息,請參閱Documentation/video4linux/videobuf

V4l2驅動架構
      驅動架構圖


       
      所有的驅動程序有以下結構:

         1) 每個設備包含設備狀態的實例結構。

         2) 子設備的初始化和命令方式(如果有).

         3) 創建V4L2的設備節點 (/dev/videoX, /dev/vbiX and /dev/radioX)和跟蹤設備節點的具體數據。

         4)文件句柄特定的結構,包含每個文件句柄數據;

         5) 視頻緩沖處理。

驅動源碼分析 www.aiwalls.com
        vivi.c 虛擬視頻驅動程序
                      —– 此代碼模擬一個真正的視頻設備V4L2 API (位於drivers/media/video目錄下)

  入口:+int __init vivi_init(void)
                 + vivi_create_instance(i) /*創建設備*//**/。
                         + 分配一個vivi_dev的結構體 /*它嵌套這結構體v4l2_device 和video_device*/
                         + v4l2_device_register(NULL, &dev->v4l2_dev);/*註冊vivi_dev中的V4l2_device*/
                         + 初始化視頻的DMA隊列
                         + 初始化鎖
                         + video_device_alloc(); 動態分配video_device結構體
                         + 構建一個video_device結構體 vivi_template 並賦給上面分配的video_device

                                static struct video_device vivi_template = {
                                          . name        = "vivi",
                                          .fops           = &vivi_fops,
                                          .ioctl_ops     = &vivi_ioctl_ops,
                                          .minor        = -1,
                                          .release    = video_device_release,

                                          .tvnorms              = V4L2_STD_525_60,
                                          .current_norm         = V4L2_STD_NTSC_M,
                                 };

                       + video_set_drvdata(vfd, dev);設置驅動程序專有數據
                       + 所有控件設置為其默認值
                       + list_add_tail(&dev->vivi_devlist, &vivi_devlist);添加到設備列表
          + 構建 v4l2_file_operations 結構體vivi_fops 並實現.open .release .read .poll .mmap函數
                            —– .ioctl 用標準的v4l2控制處理程序
          + 構建 v4l2_ioctl_ops結構體 vivi_ioctl_ops

                             static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
                                        .vidioc_querycap      = vidioc_querycap,
                                        .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
                                        .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
                                        .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
                                        .vidioc_reqbufs       = vidioc_reqbufs,
                                        .vidioc_querybuf      = vidioc_querybuf,
                                        .vidioc_qbuf          = vidioc_qbuf,
                                        .vidioc_dqbuf         = vidioc_dqbuf,
                                        .vidioc_s_std         = vidioc_s_std,
                                        .vidioc_enum_input    = vidioc_enum_input,
                                        .vidioc_g_input       = vidioc_g_input,
                                        .vidioc_s_input       = vidioc_s_input,
                                        .vidioc_queryctrl     = vidioc_queryctrl,
                                        .vidioc_g_ctrl        = vidioc_g_ctrl,
                                        .vidioc_s_ctrl        = vidioc_s_ctrl,
                                        .vidioc_streamon      = vidioc_streamon,
                                        .vidioc_streamoff     = vidioc_streamoff,
                             #ifdef CONFIG_VIDEO_V4L1_COMPAT
                                       .vidiocgmbuf          = vidiocgmbuf,
                           #endif
                       };

           + int vivi_open(struct file *file)
                     + vivi_dev *dev = video_drvdata(file);  訪問驅動程序專用數據
                     + 分配+初始化句柄(vivi_fh)數據
                     + 重置幀計數器
                     + videobuf_queue_vmalloc_init(); 初始化視頻緩沖隊列
                     + 開啟一個新線程用於開始和暫停
           + 實現自定義的v4l2_ioctl_ops 函數

附:Sub-devices
                   Sub-devices 支持的頭文件 V4l2-subdev.h
           子設備是某種程度上主橋設備連接的設備。這些設備通常是音頻/視頻流 合並器/編碼器/解碼器或傳感器和攝像頭控制器。
                         一般控制這些器件通過I2C總線,但也可用於其他總線。
          v4l2_subdev結構提供瞭一個通用的方式訪問這些設備, sub-devices支持的大部分操作分為以下幾個類別:
                    核心操作、音頻操作、視頻操作、調諧器操作,每個類別都可以在實現subdev的驅動程序時設置自己的操作。
                                 struct v4l2_subdev_ops {
                                           const struct v4l2_subdev_core_ops  *core;
                                           const struct v4l2_subdev_tuner_ops *tuner;
                                           const struct v4l2_subdev_audio_ops *audio;
                                           const struct v4l2_subdev_video_ops *video;
                                 };
          一個subdev驅動程序可以設置不執行的操作函數指針為空(如要產生音頻subdev通常沒有實現視頻類操作)。唯一的例外是核心類:必須始終存在。
          這些OPS都在內部使用,所以它是沒有必要去更改,添加或刪除它們或從一個移動到另一個類別。目前這些OPS基於原有的控制命令,但OPS不僅限於一個參數,一旦所有的I2C驅動subdev轉換為使用這些OPS,就有改進的餘地。
   1. 核心操作 v4l2_subdev_core_ops
struct v4l2_subdev_core_ops {
    int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);
    int (*log_status)(struct v4l2_subdev *sd);
    int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);
    int (*init)(struct v4l2_subdev *sd, u32 val);
    int (*load_fw)(struct v4l2_subdev *sd);
    int (*reset)(struct v4l2_subdev *sd, u32 val);
    int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
    int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
    int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
    int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);
    int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
    long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_VIDEO_ADV_DEBUG
    int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
    int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
#endif
};
           我們強烈建議至少實現這些操作:
                        g_chip_ident
                        log_status
                        g_register
                        s_register
              這些提供瞭基本的調試支持。
               ioctl:是指為仿制的ioctl類的命令。根據使用情況,它可能是更好地使用特定子設備操作(目前尚未實施),因為OPS提供適當的類型檢查。
               s_config:如果設置,那麼它總是在v4l2_subdev被註冊後由v4l2_i2c_new_subdev函數調用後。它用於將數據傳遞到平臺,可用於subdev在初始化過程中。
               init:一些適當的默認值初始化傳感器寄存器。不使用新的驅動程序時現有的驅動程序應該刪除它。
               reset:復位通用的命令。參數選擇復位哪一個子系統。傳遞0將復位整個芯片。使用新的驅動程序前要先掛載在linux-media 列表上再復位。
               s_gpio:設置GPIO引腳。如果需要的話可能還需要擴展方向參數。

     2.分類操作
                   struct v4l2_subdev_video_ops、
                   struct v4l2_subdev_audio_ops、
                   struct v4l2_subdev_tuner_ops
             以上結構體都在V4l2-subdev.h 中定義。由於操作函數太多就沒有必要一一列舉。

    3.v4l2_subde初始化
                   (1)子設備驅動初始化的v4l2_subdev結構使用:
                                        v4l2_subdev_init(sd, &ops);
                   (2)V4l2 i2c子設備的初始化
                                  void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops)

 

作者 jmq_0000

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。