linux v4l2入門(轉)

http://work-blog.readthedocs.io/en/latest/v4l2%20intro.html

第一章 V4L2簡介

1.1、什麼是v4l2

V4L2(Video4Linux的縮寫)是Linux下關於視頻採集相關設備的驅動框架,爲驅動和應用程序提供了一套統一的接口規範。

V4L2支持的設備十分廣泛,但是其中只有很少一部分在本質上是真正的視頻設備:

  • Video capture device : 從攝像頭等設備上獲取視頻數據。對很多人來講,video capture是V4L2的基本應用。設備名稱爲/dev/video,主設備號81,子設備號0~63
  • Video output device : 將視頻數據編碼爲模擬信號輸出。與video capture設備名相同。
  • Video overlay device : 將同步鎖相視頻數據(如TV)轉換爲VGA信號,或者將抓取的視頻數據直接存放到視頻卡的顯存中。
  • Video output overlay device :也被稱爲OSD(On-Screen Display)
  • VBI device : 提供對VBI(Vertical Blanking Interval)數據的控制,發送VBI數據或抓取VBI數據。設備名/dev/vbi0~vbi31,主設備號81,子設備號224~255
  • Radio device : FM/AM發送和接收設備。設備名/dev/radio0~radio63,主設備號81,子設備號64~127

V4L2在Linux系統中的結構圖如下:

_images/V4L2框圖.png

V4L2簡單框圖

1.2、從應用層看V4L2

V4L2簡單框圖可以看出,V4L2是一個字符設備,而V4L2的大部分功能都是通過設備文件的ioctl導出的。

**可以將這些ioctl分類如下**:

  1. Query Capability:查詢設備支持的功能,只有VIDIOC_QUERY_CAP一個。
  2. 優先級相關:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,設置優先級。
  3. capture相關:視頻捕獲相關Ioctl。
capture ioctl list
ID描述
VIDIOC_ENUM_FMT枚舉設備所支持的所有數據格式
VIDIOC_S_FMT設置數據格式
VIDIOC_G_FMT獲取數據格式
VIDIOC_TRY_FMT與VIDIOC_S_FMT一樣,但不會改變設備的狀態
VIDIOC_REQBUFS向設備請求視頻緩衝區,即初始化視頻緩衝區
VIDIOC_QUERYBUF查詢緩衝區的狀態
VIDIOC_QBUF從設備獲取一幀視頻數據
VIDIOC_DQBUF將視頻緩衝區歸回給設備,
VIDIOC_OVERLAY開始或者停止overlay
VIDIOC_G_FBUF獲取video overlay設備或OSD設備的framebuffer參數
VIDIOC_S_FBUF設置framebuffer參數
VIDIOC_STREAMON開始流I/O操作,capture or output device
VIDIOC_STREAMOFF關閉流I/O操作
  1. TV視頻標準:
TV Standard
ID描述
VIDIOC_ENUMSTD枚舉設備支持的所有標準
VIDIOC_G_STD獲取當前正在使用的標準
VIDIOC_S_STD設置視頻標準
VIDIOC_QUERYSTD有的設備支持自動偵測輸入源的視頻標準,此時使用此ioctl查詢偵測到的視頻標準
  1. input/output:
Input / Output
ID描述
VIDIOC_ENUMINPUT枚舉所有input端口
VIDIOC_G_INPUT獲取當前正在使用的input端口
VIDIOC_S_INPUT設置將要使用的input端口
VIDIOC_ENUMOUTPUT枚舉所有output端口
VIDIOC_G_OUTPUT獲取當前正在使用的output端口
VIDIOC_S_OUTPUT設置將要使用的output端口
VIDIOC_ENUMAUDIO枚舉所有audio input端口
VIDIOC_G_AUDIO獲取當前正在使用的audio input端口
VIDIOC_S_AUDIO設置將要使用的audio input端口
VIDIOC_ENUMAUDOUT枚舉所有audio output端口
VIDIOC_G_AUDOUT獲取當前正在使用的audio output端口
VIDIOC_S_AUDOUT設置將要使用的audio output端口
  1. controls:設備特定的控制,例如設置對比度,亮度
controls
ID描述
VIDIOC_QUERYCTRL查詢指定control的詳細信息
VIDIOC_G_CTRL獲取指定control的值
VIDIOC_S_CTRL設置指定control的值
VIDIOC_G_EXT_CTRLS獲取多個control的值
VIDIOC_S_EXT_CTRLS設置多個control的值
VIDIOC_TRY_EXT_CTRLS與VIDIOC_S_EXT_CTRLS相同,但是不改變設備狀態
VIDIOC_QUERYMENU查詢menu
  1. 其他雜項:
controls
ID描述
VIDIOC_G_MODULATOR 
VIDIOC_S_MODULATOR 
VIDIOC_G_CROP 
VIDIOC_S_CROP 
VIDIOC_G_SELECTION 
VIDIOC_S_SELECTION 
VIDIOC_CROPCAP 
VIDIOC_G_ENC_INDEX 
VIDIOC_ENCODER_CMD 
VIDIOC_TRY_ENCODER_CMD 
VIDIOC_DECODER_CMD 
VIDIOC_TRY_DECODER_CMD 
VIDIOC_G_PARM 
VIDIOC_S_PARM 
VIDIOC_G_TUNER 
VIDIOC_S_TUNER 
VIDIOC_G_FREQUENCY 
VIDIOC_S_FREQUENCY 
VIDIOC_G_SLICED_VBI_CAP 
VIDIOC_LOG_STATUS 
VIDIOC_DBG_G_CHIP_IDENT 
VIDIOC_S_HW_FREQ_SEEK 
VIDIOC_ENUM_FRAMESIZES 
VIDIOC_ENUM_FRAMEINTERVALS 
VIDIOC_ENUM_DV_PRESETS 
VIDIOC_S_DV_PRESET 
VIDIOC_G_DV_PRESET 
VIDIOC_QUERY_DV_PRESET 
VIDIOC_S_DV_TIMINGS 
VIDIOC_G_DV_TIMINGS 
VIDIOC_DQEVENT 
VIDIOC_SUBSCRIBE_EVENT 
VIDIOC_UNSUBSCRIBE_EVENT 
VIDIOC_CREATE_BUFS 
VIDIOC_PREPARE_BUF 

v4l2設備的基本操作流程如下:

  1. **打開設備**,例如 fd = open("/dev/video0",0)
  2. 查詢設備能力. 例如:
struct capability cap;
ioctl(fd,VIDIOC_QUERYCAP,&cap)
  1. 設置優先級(可選).
  2. **配置設備**。包括:
  • 視頻輸入源的視頻標準,VIDIOC_*_STD
  • 視頻數據的格式 , VIDIOC_*_FMT
  • 視頻輸入端口, VIDIOC_*_INPUT
  • 視頻輸出端口,VIDIOC_*_OUTPUT
  1. **啓動設備開始I/O操作**。V4L2支持一下三種I/O方式:

    • **Read/Write**:通過調用設備節點文件的Read/Write函數,與設備交互數據。打開設備後,默認使用的是此方法。
    • **Stream I/O**:流操作,只傳遞數據緩衝區指針,不拷貝數據。使用此方法,需要調用VIDIOC_REQBUFS ioctl來通知設備。流操作I/O有兩種方式memory map和user buffer。(具體區別後面章節介紹)
    • overlay : 也可以理解爲memory to memory 傳輸。將數據從內存拷貝到顯存中。overlay設備獨有的。

    對於Capture device可以以如下方式啓動設備:

    • 調用VIDIOC_REQBUFS ioctl來分配緩衝區隊列;
    • 調用VIDIOC_STREAMON ioctl通知設備開始stream IO
    • 調用VIDIOC_QBUF ioctl從設備獲取一幀視頻數據;
    • 使用完數據後,調用VIDIOC_DQBUF將緩衝區還給設備,以便設備填充下一幀數據。
  2. 釋放資源並關閉設備。

1.3、從驅動層看V4L2

在驅動層,V4L2爲驅動編寫者做了很多工作。只需要實現硬件相關的代碼,並且註冊相關設備即可。

硬件相關代碼的編寫,除了編寫具體硬件的控制代碼外,最主要的就是將代碼與V4L2框架綁定。綁定主要分爲以下兩個部分:

  • 關係綁定:也就是要將我們自己的結構體,與V4L2框架中相關連的結構體綁定在一起。
  • iocontrol等函數綁定:將V4L2所定義的空的函數指針,與自己的函數綁定在一起。

3.1 關係綁定

提到關係綁定,就必須介紹下V4L2幾個重要結構體。

  • struct video_device:主要的任務就是負責向內核註冊字符設備
  • struct v4l2_device:一個硬件設備可能包含多個子設備,比如一個電視卡除了有capture設備,可能還有VBI設備或者FM tunner。而v4l2_device就是所有這些設備的根節點,負責管理所有的子設備。
  • struct v4l2_subdev:子設備,負責實現具體的功能。

v4l2_device,v4l2_subdev可以看作所有設備和子設備的基類。我們在編寫自己的驅動時,往往需要繼承這些設備基類,添加一些自己的數據成員。例如第三章要講到的soc_camera_host結構體,就是繼承v4l2_device,並添加了互斥鎖、子設備列表等成員變量。

_images/v4l2-intro.png

v4l2 framework 簡略版

綁定的基本流程

  • 根據需要”重載”v4l2_device或v4l2_subdev結構體,添加需要的結構體成員。例如 :

    • linux/include/media/soc_camera.h文件中soc_camera_host重載了v4l2_device:

      struct soc_camera_host {
      struct v4l2_device v4l2_dev;
      struct list_head list;
      struct mutex host_lock;         /* Protect during probing */
      unsigned char nr;               /* Host number */
      void *priv;
      const char *drv_name;
      struct soc_camera_host_ops *ops;
      };
      
    • linux/drivers/media/video/Ml86v7667.c中ml86v7667_priv結構體”重載”了v4l2_subdev:

        struct ml86v7667_priv {
            struct v4l2_subdev                sd;
            struct v4l2_ctrl_handler  hdl;
            v4l2_std_id                       std;
        };
      
  • v4l2_device與V4L2框架的綁定:通過調用v4l2_device_register函數實現。例如,上面提到的soc_camera_host的綁定:

    int soc_camera_host_register(struct soc_camera_host *ici)
    {
          struct soc_camera_host *ix;
          int ret;
    
          if (!ici || !ici->ops ||
            !ici->ops->try_fmt ||
            !ici->ops->set_fmt ||
            !ici->ops->set_bus_param ||
            !ici->ops->querycap ||
            ((!ici->ops->init_videobuf ||
            !ici->ops->reqbufs) &&
            !ici->ops->init_videobuf2) ||
            !ici->ops->add ||
            !ici->ops->remove ||
            !ici->ops->poll ||
            !ici->v4l2_dev.dev)
              return -EINVAL;
    
          if (!ici->ops->set_crop)
              ici->ops->set_crop = default_s_crop;
          if (!ici->ops->get_crop)
              ici->ops->get_crop = default_g_crop;
          if (!ici->ops->cropcap)
              ici->ops->cropcap = default_cropcap;
          if (!ici->ops->set_parm)
              ici->ops->set_parm = default_s_parm;
          if (!ici->ops->get_parm)
              ici->ops->get_parm = default_g_parm;
          if (!ici->ops->enum_fsizes)
              ici->ops->enum_fsizes = default_enum_fsizes;
    
          mutex_lock(&list_lock);
          list_for_each_entry(ix, &hosts, list) {
              if (ix->nr == ici->nr) {
                  ret = -EBUSY;
                  goto edevreg;
              }
          }
    
          ret = v4l2_device_register(ici->v4l2_dev.dev, &ici->v4l2_dev);
          if (ret < 0)
              goto edevreg;
    
          list_add_tail(&ici->list, &hosts);
          mutex_unlock(&list_lock);
    
          mutex_init(&ici->host_lock);
          scan_add_host(ici);
    
          return 0;
    
          edevreg:
          mutex_unlock(&list_lock);
          return ret;
      }
    
  • v4l2_subdev與v4l2_device的綁定:通過v4l2_device_register_subdev函數,將subdev註冊到根節點上。例如:

    static int soc_camera_platform_probe(struct platform_device *pdev)
    {
        struct soc_camera_host *ici;
        struct soc_camera_platform_priv *priv;
        struct soc_camera_platform_info *p = pdev->dev.platform_data;
        struct soc_camera_device *icd;
        int ret;
    
        if (!p)
    	return -EINVAL;
    
        if (!p->icd) {
    	    dev_err(&pdev->dev,
    		    "Platform has not set soc_camera_device pointer!\n");
    	    return -EINVAL;
        }
    
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv)
    	    return -ENOMEM;
    
        icd = p->icd;
    
        /* soc-camera convention: control's drvdata points to the subdev */
        platform_set_drvdata(pdev, &priv->subdev);
        /* Set the control device reference */
        icd->control = &pdev->dev;
    
        ici = to_soc_camera_host(icd->parent);
    
        v4l2_subdev_init(&priv->subdev, &platform_subdev_ops);
        v4l2_set_subdevdata(&priv->subdev, p);
        strncpy(priv->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);
    
        ret = v4l2_device_register_subdev(&ici->v4l2_dev, &priv->subdev);
        if (ret)
    	    goto evdrs;
    
        return ret;
    
      evdrs:
        platform_set_drvdata(pdev, NULL);
        kfree(priv);
        return ret;
    }
    
  • video_device與v4l2_device的綁定:將v4l2_device的地址賦值給video_device的v4l2_dev即可。

此步不一定必要。只要有辦法通過文件節點file(struct file)找到v4l2_device即可。

3.2 函數綁定

v4l2 framework 簡略版圖中,綠色的方框都是需要我們綁定並實現的。

其中v4l2_file_operations和v4l2_ioctl_ops是必須實現的。而v4l2_subdev_ops下的八類ops中,v4l2_subdev_core_ops是必須實現的,其餘需要根據設備類型選擇實現的。比如video capture類設備需要實現v4l2_subdev_core_ops, v4l2_subdev_video_ops。

  • v4l2_file_operations:實現文件類操作,比如open,close,read,write,mmap等。但是ioctl是不需要實現的,一般都是用video_ioctl2代替。例如linux/drivers/media/video/soc_camera.c文件中soc_camera_fops的實現:
static struct v4l2_file_operations soc_camera_fops = {
    .owner          = THIS_MODULE,
    .open           = soc_camera_open,
    .release        = soc_camera_close,
    .unlocked_ioctl = video_ioctl2,
    .read           = soc_camera_read,
    .mmap           = soc_camera_mmap,
    .poll           = soc_camera_poll,
};
  • v4l2_ioctl_ops:V4L2導出給應用層使用的所有ioctl都是在這個地方實現的。但不必全部實現,只實現自己相關的ioctl即可。例如linux/drivers/media/video/soc_camera.c中soc_camera_ioctl_ops的實現:
static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = {
    .vidioc_querycap         = soc_camera_querycap,
    .vidioc_try_fmt_vid_cap  = soc_camera_try_fmt_vid_cap,
    .vidioc_g_fmt_vid_cap    = soc_camera_g_fmt_vid_cap,
    .vidioc_s_fmt_vid_cap    = soc_camera_s_fmt_vid_cap,
    .vidioc_enum_fmt_vid_cap = soc_camera_enum_fmt_vid_cap,
    .vidioc_enum_input       = soc_camera_enum_input,
    .vidioc_g_input          = soc_camera_g_input,
    .vidioc_s_input          = soc_camera_s_input,
    .vidioc_s_std            = soc_camera_s_std,
    .vidioc_g_std            = soc_camera_g_std,
    .vidioc_enum_framesizes  = soc_camera_enum_fsizes,
    .vidioc_reqbufs          = soc_camera_reqbufs,
    .vidioc_querybuf         = soc_camera_querybuf,
    .vidioc_qbuf             = soc_camera_qbuf,
    .vidioc_dqbuf            = soc_camera_dqbuf,
    .vidioc_create_bufs      = soc_camera_create_bufs,
    .vidioc_prepare_buf      = soc_camera_prepare_buf,
    .vidioc_streamon         = soc_camera_streamon,
    .vidioc_streamoff        = soc_camera_streamoff,
    .vidioc_cropcap          = soc_camera_cropcap,
    .vidioc_g_crop           = soc_camera_g_crop,
    .vidioc_s_crop           = soc_camera_s_crop,
    .vidioc_g_parm           = soc_camera_g_parm,
    .vidioc_s_parm           = soc_camera_s_parm,
    .vidioc_g_chip_ident     = soc_camera_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
    .vidioc_g_register       = soc_camera_g_register,
    .vidioc_s_register       = soc_camera_s_register,
#endif
};
  • v4l2_subdev_ops:v4l2_subdev有可能需要實現的ops的總合。分爲8類,core,audio,video,vbi,tuner......等。例如,

    linuxdriversmediavideosoc_camera_platform.c中platform_subdev_ops的實現

static struct v4l2_subdev_video_ops platform_subdev_video_ops = {
    .s_stream       = soc_camera_platform_s_stream,
    .enum_mbus_fmt  = soc_camera_platform_enum_fmt,
    .cropcap        = soc_camera_platform_cropcap,
    .g_crop         = soc_camera_platform_g_crop,
    .try_mbus_fmt   = soc_camera_platform_fill_fmt,
    .g_mbus_fmt     = soc_camera_platform_fill_fmt,
    .s_mbus_fmt     = soc_camera_platform_fill_fmt,
    .g_mbus_config  = soc_camera_platform_g_mbus_config,
};

static struct v4l2_subdev_ops platform_subdev_ops = {
    .core   = &platform_subdev_core_ops,
    .video  = &platform_subdev_video_ops,
};

函數綁定只是將驅動所實現的函數賦值給相關的變量即可。


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