程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> v4l2 編程接口(二) — driver

v4l2 編程接口(二) — driver

編輯:C++入門知識

V4L2 驅動隨著硬件的變化也越來越復雜,現在大部分設備有裡面包含了多個IC, 在/dev目錄下不僅要建立 V4L2 的節點,而且還需要建立如:DVB、ALSA、FB、I2C、input等設備節點。事實上 V4L2 驅動需要支持音頻/視頻的混音/編碼/解碼等IC所以比其他驅動都要復雜很多,通常這些IC通過 i2c 總線連接到主板,這些設備都統稱為sub-devices。在很長的一段時間裡 V4L2 被限制只能在 video_device 結構體裡面創建,並且用video_buf 控制視頻緩存,這意味著所有的驅動創建自己的實例都將連接到自己的sub-devices,這些工作通常很復雜並經常引起錯誤,許多常見的代碼因為缺乏一個框架而無法重構。因此這個框架建立起了基本的機制,所有的驅動都需要和這個框架結合以便共用其中的函數。因此 V4L2  框架作了相應的優化:它有一個 v4l2_device 結構作為設備實例,一個v4l2_subdev 結構作為子設備實例,video_device 結構包含了v4l2_device 節點,以後將會有一個 v4l2_fh 的結構作為與文件句柄的實例。每個設備都采用 v4l2_device 結構來表示。非常簡單的設備都可以申請這個結構,但通常會將這個結構嵌入一個更大的結構中。
1、 video_device
在 v4l2 中用 struct video_device 代表一個視頻設備,該結構說明如下:
[cpp] 
struct video_device 

    /* 設備操作合集 */ 
    const struct v4l2_file_operations *fops; 
 
    /* sysfs節點 */ 
    struct device dev;      /* v4l device */ 
    struct cdev *cdev;      /* 字符設備節點 */ 
 
    /* Set either parent or v4l2_dev if your driver uses v4l2_device */ 
    struct device *parent;      /* 父設備 */ 
    struct v4l2_device *v4l2_dev;   /* v4l2_device parent */ 
 
    /* 設備信息 */ 
    char name[32]; 
    int vfl_type; 
    /* 如果注冊失敗 minor 將被設置為 -1 */ 
    int minor; 
    u16 num; 
    /* 需要用位操作 flags */ 
    unsigned long flags; 
    /* attribute to differentiate multiple indices on one physical device */ 
    int index; 
 
    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); 
 
    /* ioctl 回調函數 */ 
    const struct v4l2_ioctl_ops *ioctl_ops; 
}; 
其中 
struct cdev { 
    struct kobject kobj;    /* 內核對象 */ 
    struct module *owner; 
    const struct file_operations *ops;  /* 設備操作合集 */ 
    struct list_head list; 
    dev_t dev; 
    unsigned int count; 
}; 
 
struct v4l2_file_operations { 
    struct module *owner; 
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);        /* 讀數據 */ 
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 寫數據 */ 
    unsigned int (*poll) (struct file *, struct poll_table_struct *);        /* 同步操作 */ 
    long (*ioctl) (struct file *, unsigned int, unsigned long);              /* 特殊命令 */ 
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 
    unsigned long (*get_unmapped_area) (struct file *, unsigned long, 
                unsigned long, unsigned long, unsigned long); 
    int (*mmap) (struct file *, struct vm_area_struct *);                    /* 內存映射 */ 
    int (*open) (struct file *);                                             /* 打開設備 */ 
    int (*release) (struct file *);                                          /* 釋放設備 */ 
}; 
視頻設備在 linux 中作為字符設備出現,域 cdev 與 /dev/videox 節點關聯,打開節點就相當於執行cdev 的 open 函數,cdev 的 ops 域即 file_operations 的一些接口在經過一定的參數過濾後最終都調用了video_device 的 fops 域即v4l2_file_operations的成員,所以在編寫驅動程序的時候需要實現 v4l2_file_operations 的接口:其中 open 用於打開視頻設備, read 接口用於讀取視頻數據,poll 接口用於視頻流的同步,mmap 將視頻設備的保存數據的內存空間的物理地址映射到用戶空間,ioctl 用於向視頻設備發送命令並查詢相關信息(ioctl 一般設置為 v4l2 提供的 video_ioctl2 函數,並最終調用 video_device 的 ioctl_ops 域即 v4l2_ioctl_ops),通常需要實現的 ioctl 接口如下:
[cpp] 
static const struct v4l2_ioctl_ops xxx_cam_ioctl_ops = { 
    .vidioc_querycap         = vidioc_querycap, 
    .vidioc_enum_input       = vidioc_enum_input, 
    .vidioc_g_input          = vidioc_g_input, 
    .vidioc_s_input          = vidioc_s_input, 
    .vidioc_queryctrl        = vidioc_queryctrl, 
    .vidioc_s_ctrl           = vidioc_s_ctrl, 
    .vidioc_g_ctrl           = vidioc_g_ctrl, 
    .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, 
    .vidioc_try_fmt_vid_cap  = vidioc_try_fmt_vid_cap, 
    .vidioc_g_fmt_vid_cap    = vidioc_g_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_streamon         = vidioc_streamon, 
    .vidioc_streamoff        = vidioc_streamoff, 
    .vidioc_default          = vidioc_default, 
}; 
該結構各域的作用如上篇文章所述。video_device 通過 video_register_device 函數注冊,函數原型如下:
[cpp] 
/**
 *  video_register_device - 注冊一個 v4l2 設備
 *  @vdev: video_device 結構
 *  @type: v4l2 設備的類型
 *  @nr:   從設備號(0 == /dev/video0, 1 == /dev/video1, -1 == first free)
 *
 *  注冊代碼將會根據注冊設備的類型指派從設備號,如果沒有合適的從設備號將會返回錯誤值.
 *
 *  通常有如下幾種設備類型
 *
 *  %VFL_TYPE_GRABBER - 視頻采集設備
 *
 *  %VFL_TYPE_VTX     - 圖文電視設備
 *
 *  %VFL_TYPE_VBI     - 場消隱區解碼設備(undecoded)
 *
 *  %VFL_TYPE_RADIO   - 無線設備
 */ 
int video_register_device(struct video_device *vdev, int type, int nr) 

    return __video_register_device(vdev, type, nr, 1); 

EXPORT_SYMBOL(video_register_device); 
該函數注冊流程較簡單:首先會根據設備類型確定在 /dev 目錄下的節點名稱以及從設備號的偏移和值,然後為 cdev 申請內存空間並注冊,將 vdev->cdev->ops 設置為內核提供的 v4l2_fops,最後將 vdev->dev 注冊到 sysfs 中。
2、v4l2_subdev
許多驅動需要與子設備通信,這些設備做的任務比較常見的是音視頻處理、編解碼等, 網絡攝像頭比較常見的子設備是傳感器和攝像頭控制器。為了提供一個統一的接口給這些子設備,內核將涉及到子設備控制的那部分(如 vidioc_s_ctrl、vidioc_s_frequency 等)獨立了出來,用 struct v4l2_subdev 來表示以方便用戶實現 v4l2 驅動程序:
[cpp]
struct v4l2_subdev { 
    struct list_head list;              /* 鏈接至 v4l2_device */ 
    struct module *owner; 
    u32 flags; 
    struct v4l2_device *v4l2_dev;       /* 指向 v4l2_device */ 
    const struct v4l2_subdev_ops *ops;  /* subdev 操作合集 */ 
    /* name must be unique */ 
    char name[V4L2_SUBDEV_NAME_SIZE]; 
    /* can be used to group similar subdevs, value is driver-specific */ 
    u32 grp_id; 
    /* 私有數據 */ 
    void *priv; 
}; 
其中 list 域作為鏈表節點鏈接至 v4l2_dev 指向的  v4l2_device 結構中,這個結構中最重要的成員就是 struct v4l2_subdev_ops *ops,該域包含了 v4l2 設備支持的所有操作,定義如下:
[cpp]
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;  /* 視頻操作合集 */ 
}; 
v4l2_subdev_core_ops 包含的操作合集是各種類型設備通用的:
[cpp]
struct v4l2_subdev_core_ops { 
    int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);  /* 獲取設備id */ 
    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);                                         /* 加載firmware */ 
    int (*reset)(struct v4l2_subdev *sd, u32 val);                                  /* 重置設備 */ 
    int (*s_gpio)(struct v4l2_subdev *sd, u32 val);                                 /* 設置gpio */ 
    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 
}; 
v4l2_subdev_tuner_ops 包含的操作合集則是調諧器獨有的:
[cpp] 
struct v4l2_subdev_tuner_ops { 
    int (*s_mode)(struct v4l2_subdev *sd, enum v4l2_tuner_type);               /* 設置調諧器模式 */ 
    int (*s_radio)(struct v4l2_subdev *sd);                                    /* 設置無線設備信息 */ 
    int (*s_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 設置頻率 */ 
    int (*g_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 獲取頻率 */ 
    int (*g_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 獲取調諧器信息 */ 
    int (*s_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 設置調諧器信息 */ 
    int (*g_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 獲取調幅器信息 */ 
    int (*s_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 設置調幅器信息 */ 
    int (*s_type_addr)(struct v4l2_subdev *sd, struct tuner_setup *type);      /* 安裝調諧器 */ 
    int (*s_config)(struct v4l2_subdev *sd, const struct v4l2_priv_tun_config *config);   /* 設置配置信息 */ 
    int (*s_standby)(struct v4l2_subdev *sd);                                  /* 設置標准 */ 
}; 
v4l2_subdev_audio_ops 包含的操作合集則是音頻部分獨有的:
[cpp] view plaincopy
struct v4l2_subdev_audio_ops { 
    int (*s_clock_freq)(struct v4l2_subdev *sd, u32 freq);       /* 設置音頻設備頻率 */ 
    int (*s_i2s_clock_freq)(struct v4l2_subdev *sd, u32 freq);   /* 設置i2s總線頻率 */ 
    int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);   /* 設置音頻路由 */ 
}; 
v4l2_subdev_video_ops 包含的操作合集則是視頻部分獨有的:
[cpp] 
struct v4l2_subdev_video_ops { 
    int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);             /* 設置視頻路由 */ 
    int (*s_crystal_freq)(struct v4l2_subdev *sd, u32 freq, u32 flags);                      /* 設置設備頻率 */ 
    int (*decode_vbi_line)(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi_line);   /* 消隱區信息解碼 */ 
    int (*s_vbi_data)(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *vbi_data);  /* 設置消隱區數據 */ 
    int (*g_vbi_data)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *vbi_data);        /* 獲取消隱區數據 */ 
    int (*g_sliced_vbi_cap)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_cap *cap); 
    int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std);                            /* 設置標准輸出 */ 
    int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);                               /* 查詢標准 */ 
    int (*g_input_status)(struct v4l2_subdev *sd, u32 *status);                              /* 獲取輸入狀態 */ 
    int (*s_stream)(struct v4l2_subdev *sd, int enable);                                     /* 設置數據流 */ 
    int (*enum_fmt)(struct v4l2_subdev *sd, struct v4l2_fmtdesc *fmtdesc);                   /* 枚舉視頻格式 */ 
    int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                           /* 獲取視頻格式 */ 
    int (*try_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                         /* 嘗試設置視頻格式 */ 
    int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                           /* 設置視頻格式 */ 
    int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);                         /* 視頻剪輯功能 */ 
    int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);                           /* 獲取剪輯功能 */ 
    int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);                           /* 設置剪輯功能 */ 
    int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);                    /* 獲取參數 */ 
    int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);                    /* 設置參數 */ 
    int (*enum_framesizes)(struct v4l2_subdev *sd, struct v4l2_frmsizeenum *fsize);          /* 枚舉幀大小 */ 
    int (*enum_frameintervals)(struct v4l2_subdev *sd, struct v4l2_frmivalenum *fival);      /* 枚舉幀間隔 */ 
}; 
因為 v4l2 設備一般用 i2c 總線通信,所以注冊函數需要提供 i2c_client,函數原型如下:
[cpp] 
/**
 *  v4l2_i2c_subdev_init - 注冊一個 v4l2_subdev
 *  @sd: v4l2_subdev 結構
 *  @client: 通信用的i2c設備
 *  @ops: v4l2_subdev_ops 操作合集
 */ 
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops) 
當 video_device 中的接口需要調用 v4l2_subdev 的成員函數時一般采用如下宏定義:
[cpp] 
/* 調用成員函數之前需要先檢查成員函數是否被設置
 *  v4l2_subdev_call - 調用 v4l2_subdev 成員函數
 *  @sd: v4l2_subdev 結構
 *  @o:  v4l2_subdev_ops 成員名稱
 *  @f:  v4l2_subdev 成員函數
 *  @args:  v4l2_subdev 成員函數的參數
 
   使用示例: err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
 */ 
#define v4l2_subdev_call(sd, o, f, args...)             \ 
    (!(sd) ? -ENODEV : (((sd) && (sd)->ops->o && (sd)->ops->o->f) ?  \ 
        (sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD)) 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved