1. 程式人生 > >Linux V4L2驅動架構解析與開發導引

Linux V4L2驅動架構解析與開發導引

Andrew按:眾所周知,linux中可以採用靈活的多層次的驅動架構來對介面進行統一與抽象,最低層次的驅動總是直接面向硬體的,而最高層次的驅動在linux中被劃分為“面向字元裝置、面向塊裝置、面向網路介面”三大類來進行處理,前兩類驅動在檔案系統中形成類似檔案的“虛擬檔案”,又稱為“節點node”,這些節點擁有不同的名稱代表不同的裝置,在目錄/dev下進行統一管理,系統呼叫函式如open、close、read等也與普通檔案的操作有相似之處,這種介面的一致性是由VFS(虛擬檔案系統層)抽象完成的。面向網路介面的裝置仍然在UNIX/Linux系統中被分配代表裝置的名稱(如eth0),但是沒有對映入檔案系統中,其驅動的呼叫方式也與檔案系統的呼叫open、read等不同。

video4linux2(V4L2)是Linux核心中關於視訊裝置的中間驅動層,向上為Linux應用程式訪問視訊裝置提供了通用介面,向下為linux中裝置驅動程式開發提供了統一的V4L2框架。在Linux系統中,V4L2驅動的視訊裝置(如攝像頭、影象採集卡)節點路徑通常為/dev中的videoX,V4L2驅動對使用者空間提供“字元裝置”的形式,主裝置號為81,對於視訊裝置,其次裝置號為0-63。除此之外,次裝置號為64-127的Radio裝置,次裝置號為192-223的是Teletext裝置,次裝置號為224-255的是VBI裝置。由V4L2驅動的Video裝置在使用者空間通過各種ioctl呼叫進行控制,並且可以使用mmap進行記憶體對映。

linux核心概略架構(source:《Linux Device Drivers  Edition 3》chaper1)

                      (上傳不了,看官去翻書...)

V4l2在linux中的驅動架構(Edit By Andrew):

 

     可以看到從視訊輸入輸入硬體的整個驅動鏈被抽象成3個層次:

1、  最底層是直接面向硬體的,驅動框架由v4l2提供。值得注意的是,往往該層驅動需要匯流排驅動的支援,比如常見的USB2.0匯流排。

2、  中間層便是v4l2。這是v4l的第二版,由Bill Dirks最開始開發,最終被收入標準核心驅動樹。

3、  上層是linux核心三大驅動模組之一的“字元裝置驅動層”,因此最終視訊裝置以檔案系統中/dev目錄下的字元裝置的面目出現,並被應用程式使用。

V4L2的是V4L的第二個版本。原來的V4L被引入到Linux核心2.1.x的開發週期後期。Video4Linux2修正了一些設計缺陷,並開始出現在2.5.X核心,並在核心2.6.38之後,取消了對第一個版本v4l的支援。Video4Linux2驅動程式包括Video4Linux1應用的相容模式,但實際上,支援是不完整的,並建議V4L2的裝置使用V4L2的模式。現在,該專案的DVB-Wiki託管在LinuxTV(--->http://linuxtv.org/wiki/index.php/Main_Page)的網站上。

要想了解 V4l2 有幾個重要的文件是必須要讀的:

1、原始碼Documentation/video4linux目錄下的V4L2-framework.txtvideobuf

2、V4L2的官方API文件V4L2 API Specification

3、原始碼drivers/media/video目錄下的sample程式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高頻頭裝置接收來的音訊流.

1、  由於在核心2.6.38版本之後就已經完全支援v4l2,且拋棄了第一版。如今(2012-11)的核心版本已到3.3.6,且幾乎所有的視訊裝置驅動都已過渡到新版,因此在開發程式的時候應該按照v4l2的版本API標準來開發。

2、  一般市場上容易買到的“免驅”攝像頭就是符合UVC標準的,核心都可以支援。

3、  其他種類的如中星微的ZC3XX、Sunplus系列等,GSPCA一般能支援,核心也是自帶的。

4、  核心支援的所有種類的視訊裝置驅動,都可以在檔案系統目錄/lib/modules/kernel%uname –r/kernel/driver/meida/video下找到。或者在編譯核心時,一一列舉。

相關網站:

http://linuxtv.org

===========一些v4l2驅動層細節==========

——注意這些細節不是應用程式呼叫的,而是編寫驅動程式需要了解的,或者是關於v4l2本身的一些細節。

所有的v4l2驅動程式有以下結構:

      1) 每個裝置包含裝置狀態的例項結構。

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

      3) 建立V4L2的裝置節點 (/dev/videoX, /dev/vbiX and /dev/radioX)和跟蹤裝置節點的具體資料。

      4)檔案控制代碼特定的結構,包含每個檔案控制代碼資料;

      5) 視訊緩衝處理。

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   (進行視訊程式設計時v4l的最重要也是最常用功能)

      在/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

Sample驅動原始碼分析: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 函式

http://www.cnblogs.com/andrew-wang/archive/2012/11/14/2770692.html