现在的位置: 首页 > 综合 > 正文

linux —— V4L2 视频驱动简述

2017年08月26日 ⁄ 综合 ⁄ 共 14636字 ⁄ 字号 评论关闭

    V4L2视频驱动主要涉及到以下几个知识点:摄像头方面的知识、Camera解码器、控制器、V4L2的API和数据结构、V4L2的驱动架构。

    一. 视频驱动的整体框架

          Video的基本框架图如下:

         

            对驱动程序员来说,主要是关心camera驱动这部分代码的实现。

    二. V4L2重要的数据结构

          常用的结构体在内核文件 include/linux/videodev2.h与v4l2-dev.h中定义,重要的数据结构如下面这些。

          1. struct v4l2_requestbuffers //申请帧缓冲,对应命令 VIDIOC_REQBUFS

                 struct v4l2_requestbuffers{
                        __u32   count;   //缓存数量,也就是说在缓存队列里保持多少张照片
                        enum v4l2_buf_type      type;  //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
                        enum v4l2_memory        memory; 
//V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
                        __u32   reserved[2];
                  };

          2. struct v4l2_capability //视频设备的功能,对应命令 VIDIOC_QUERYCAP

                 struct v4l2_capability{
                         __u8 driver[16];  //驱动名称
                         __u8 card[32];   
                         __u8 bus_info[32];  //总线信息
                         __u32   version;
                         __u32 capabilities;   //设备能力
                         __u32 device_caps;
                         __u32 reserved[3];
                  };

          3. struct v4l2_input  //视频输入信息,对应命令 VIDIOC_ENUMINPUT

                 struct v4l2_input{
                        __u32      index;  /*  Which input */ //应用关注的输入的索引号; 这是惟一一个用户空间设定的字段. 驱动要分配索引号给输入,从0开始,依次往上增加.
                        __u8      name[32];  /*  Label */
//输入的名字,由驱动设定.简单起见,可以设为”Camera”,如果卡上有多个输入,名称就要与接口的打印相符合.
                        __u32      type;  /*  Type of input */
//输入的类型,现在只有两个值可选:V4L2_INPUT_TYPE_TUNER 和V4L2_INPUT_TYPE_CAMERA.
                        __u32      audioset;  /*  Associated audios (bitfield) *///描述哪个音频输入可以与些视频输入相关联.如果没有音频输入可以与之关联,或是只有一个可选,那么就可以简单地把这个字段置0.
                        __u32        tuner;             /*  Associated tuner *///如果输入是一个调谐器 (type 字段置为V4L2_INPUT_TYPE_TUNER), 这个字段就是会包含一个相应的调谐设备的索引号.
                        v4l2_std_id  std; //描述设备支持哪个或哪些视频标准.
                        __u32      status; //给出输入的状态.,status 中设置的每一位都代表一个问题. 这些问题包括没有电源,没有信号,没有同频锁,或是其他一些不幸的问题.
                        __u32      capabilities;
                        __u32      reserved[3];
                  };

          4. struct v4l2_standard //视频的制式,比如PAL ,NTSC ,对应命令 VIDIOC_ENUMSTD

                 struct v4l2_standard{
                        __u32       index;
                        v4l2_std_id          id;
                        __u8       name[24];
                        struct v4l2_fract    frameperiod; /* Frames, not fields */
                        __u32       framelines;
                        __u32       reserved[4];
                  };

          5. struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT 、VIDIOC_S_FMT 等

                 struct v4l2_format{
                         enum v4l2_buf_type type;   //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
                         union {
                                struct v4l2_pix_format  pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
                                struct v4l2_pix_format_mplane pix_mp; /*V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE*/
                                struct v4l2_window  win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
                                struct v4l2_vbi_format  vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
                                struct v4l2_sliced_vbi_format sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
                                __u8 raw_data[200];                   /* user-defined */
                         } fmt;
                  };

          6. struct v4l2_buffer //驱动 中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

                 struct v4l2_buffer{
                        __u32   index;  //字段是鉴别缓冲区的序号;它只在内存映射缓冲区中使用。内存映射缓冲区的index从0开始,依次递增。
                        enum v4l2_buf_type      type;  //是缓冲区的类型 ,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE 或V4L2_BUF_TYPE_VIDEO_OUTPUT.
                        __u32   bytesused;  //.缓冲区中的图像数据大小,对于捕获设备而言,驱动会设置bytesused; 对输出设备而言,应用必须设置这个字段。

                        __u32   flags;
                        enum v4l2_field  field;  //描述图像存在缓冲区的那一个区域。
                        struct timeval  timestamp; //对于输入设备来说,代表帧捕获的时间.对输出设备来说,在没有到达时间戳所代表的时间前,驱动不可以把帧发送出去;时间戳值为0代表越快越 好。驱动会把时间戳设为帧的第一个字节传送到设备的时间,或者说是驱动所能达到的最接近的时间。
                        struct v4l2_timecode timecode;  //用来存放时间编码,对于视频编辑类的应用是非常有用的。
                        __u32   sequence;  //驱动对传过设备的帧维护了一个递增的计数; 每一帧传送时,它都会在sequence字段中存入现行序号。对于输入设备来讲,应用可以观察这一字段来检测帧。

                        enum v4l2_memory        memory;   //表示的是缓冲是内存映射的还是用户空间的。

                        union {
                                __u32           offset;  //对于内存映谢的缓冲区,m.offset 描述的是缓冲区的位置. 规范将它描述为“从设备存储器开发的缓冲区偏移”,但其实质却是一个 magic cookie,应用可以将其传给mmap(),以确定那一个缓冲区被映射了。
                                unsigned long   userptr;  //然而对于用户空间缓冲区而言,m.userptr是缓冲区的用户空间地址。
                                struct v4l2_plane *planes;
                        } m;
                        __u32   length;
                        __u32   input;    //可以用来快速切换捕获设备的输入 – 当然,这要得设备支持帧与帧音的快速切换才来。
                        __u32   reserved;
                  };

          7. struct v4l2_crop //视频信号矩形边框

                 struct v4l2_crop{
                        enum v4l2_buf_type      type;
                        struct v4l2_rect        c;
                 };

          8. v4l2_std_id  //视频制式

                  typedef __u64 v4l2_std_id;

          9. struct video_device  //视频设备

                 struct video_device
                 {
                        #if defined(CONFIG_MEDIA_CONTROLLER)
                             struct media_entity entity;
                        #endif
                        const struct v4l2_file_operations *fops;     //设置v4l2_file_operations结构
                        struct device dev;  /* v4l device */
                        struct cdev *cdev;  /* character device */
                        struct device *parent;  /* device parent */ //NULL .如果硬件中有多个PCI设备共享v4l2_device核心,那么就要设置父设备。
                        struct v4l2_device *v4l2_dev; /* v4l2_device parent */ //设置v4l2_device父设备
                        struct v4l2_ctrl_handler *ctrl_handler;
                        struct v4l2_prio_state *prio;  //跟踪属性。用来实现VIDIOC_G/S_PRIORITY,如果设置为NULL,将使用v4l2_device中的v4l2_prio_state
                        char name[32];           //设置唯一的描述名
                        int vfl_type;
                        int minor;
                        u16 num;
                        unsigned long flags; //可选,设置V4L2_FL_USE_FH_PRIO,如果想让framework来处理VIDIOC_G/S_PRIORITYioctls的话。
                        int index;
                        spinlock_t  fh_lock; /* Lock for all v4l2_fhs */
                        struct list_head fh_list; /* List of struct v4l2_fh */
                        int debug;   /* Activates debug level*/
                        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;         //使用v4l2_ioctl_ops来简化ioctl的维护

                        struct mutex *lock;  //如果想在驱动中进行全局锁定的话设置为NULL,否则初始化为一个 mutex_lock,这样就可以在unlocked_ioctl的操作之前和之后对操作内容进行保护
                 };

    三. 关键API函数

          1. 注册视频设备 video_register_device

                 static inline int __must_check video_register_device(struct video_device *vdev, int type, int nr)
                 {
                         return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
                 }

                 video核心层提供的注册函数,video_device是核心构建的数据结构,video_device_alloc()分配好一个video_device后,然再给这个结构初始化,最后调用这个函数来注册。type表示设备类型,nr,次设备号为base+nr。

          2. 注销视频设备 video_unregister_device

                 void video_unregister_device(struct video_device *vdev)
                 {
                        if (!vdev || !video_is_registered(vdev))
                              return;
                        mutex_lock(&videodev_lock);
                        clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
                        mutex_unlock(&videodev_lock);
                        device_unregister(&vdev->dev);
                 }

    四. 驱动编写流程

          1. 包含头文件

               #include <linux/videodev2.h>
               #include <media/videobuf2-vmalloc.h>
               #include <media/v4l2-device.h>
               #include <media/v4l2-ioctl.h>
               #include <media/v4l2-ctrls.h>
               #include <media/v4l2-fh.h>
               #include <media/v4l2-event.h>
               #include <media/v4l2-common.h>

          2. 定义video_device并初始化各成员

               static const struct v4l2_file_operations vivi_fops= {
                       .owner  = THIS_MODULE,
                       .open           = v4l2_fh_open,
                       .release        = vivi_close,
                       .read           = vivi_read,
                       .poll  = vivi_poll,
                       .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
                       .mmap           = vivi_mmap,
                };
                static const struct v4l2_ioctl_ops vivi_ioctl_ops= {
                       .vidioc_querycap      = vidioc_querycap,
                       .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
                       .vidioc_g_fmt_vid_cap     = vidioc_g_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_streamon      = vidioc_streamon,
                       .vidioc_streamoff     = vidioc_streamoff,
                       .vidioc_log_status    = v4l2_ctrl_log_status,
                       .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
                       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
                 };
                 static struct video_device vivi_template= {
                        .name  = "vivi",
                        .fops           = &vivi_fops,
                        .ioctl_ops  = &vivi_ioctl_ops,
                        .release = video_device_release,
                        .tvnorms              = V4L2_STD_525_60,
                        .current_norm         = V4L2_STD_NTSC_M,
                 };

          3. 调用video_register_device注册video设备

                static int __init vivi_create_instance(int inst)
                {
                       struct vivi_dev *dev;
                       struct video_device *vfd;
                       struct v4l2_ctrl_handler *hdl;
                       struct vb2_queue *q;
                       int ret;

                       ......
       
                       ret = -ENOMEM;
                       vfd = video_device_alloc();
                       if (!vfd)
                            goto unreg_dev;

                       *vfd = vivi_template;
                       vfd->debug = debug;
                       vfd->v4l2_dev = &dev->v4l2_dev;
                       set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);

                       ret = video_register_device(vfd,VFL_TYPE_GRABBER, video_nr);  //VFL_TYPE_GRABBER 表明是一个图像采集设备–包括摄像头、调谐器,诸如此类。
                       if (ret < 0)
                       {

                               video_device_release(vfd);

                               v4l2_device_unregister(&dev->v4l2_dev);

                       }      

                       ......
 
                       return 0;
              }

    五. 应用程序的处理流程 

          1. 打开设备文件

              int fd = open(Devicename,mode);
              Devicename:/dev/video0, /dev/video1
              Mode:O_RDWR [| O_NONBLOCK]

              如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

          2. 取得设备的capability

              struct v4l2_capability capability;
              int ret = ioctl(fd,
VIDIOC_QUERYCAP
, &capability);

              查看设备具有什么功能,比如是否具有视频输入特性。

          3. 选择视频输入

               struct v4l2_input input;

               初始化input;

               int ret = ioctl(fd,
VIDIOC_QUERYCAP
, &input);

               一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。

          4. 检测视频支持的制式

               v4l2_std_id std;
               do {
                       ret = ioctl(fd,
VIDIOC_QUERYSTD
, &std);
               } while (ret == -1 && errno == EAGAIN);
               switch (std) {
                       case V4L2_STD_NTSC:
                             //……
                       case V4L2_STD_PAL:
                             //……
                }

          5. 设置视频捕获格式

               struct v4l2_format fmt;
               fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

               fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
               fmt.fmt.pix.height = height;
               fmt.fmt.pix.width = width;
               fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
               ret = ioctl(fd,
VIDIOC_S_FMT
, &fmt);
               if(ret) {
                       perror("VIDIOC_S_FMT\n");
                       close(fd);
                       return -1;
               }

          6. 向驱动申请帧缓存

               struct v4l2_requestbuffers req;
               if (ioctl(fd,
VIDIOC_REQBUFS
, &req)
== -1) {
                      return -1;
               }

              v4l2_requestbuffers 结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立 FIFO ,来提高视频采集的效率。

          7. 获取每个缓存的信息,并 mmap 到用户空间

              typedef struct VideoBuffer {
                      void *start;
                      size_t length;
              }VideoBuffer;
              VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
              struct v4l2_buffer buf;
              for (numBufs = 0; numBufs < req.count; numBufs++) {  //映射所有的缓存
                       memset( &buf, 0, sizeof(buf) );
                       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                       buf.memory = V4L2_MEMORY_MMAP;
                       buf.index = numBufs;
                       if (ioctl( fd,
VIDIOC_QUERYBUF
, &buf)
== -1) { //获取到对应 index 的缓存信息,此处主要利用 length 信息及 offset 信息来完成后面的 mmap 操作。
                             return -1;
                       }
                       buffers[numBufs].length = buf.length;  //转换成相对地址
                       buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, buf.m.offset);
                       if (buffers[numBufs].start == MAP_FAILED) {
                              return -1;
                }

          8. 开始采集视频

              int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
              int ret = ioctl(fd,
VIDIOC_STREAMON
, &buf_type);

          9. 取出 FIFO 缓存中已经采样的帧缓存

               struct v4l2_buffer buf;
               memset(&buf,0,sizeof(buf));
               buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
               buf.memory=V4L2_MEMORY_MMAP;
               buf.index=0;// 此值由下面的 ioctl 返回
               if (ioctl(fd,
VIDIOC_DQBUF
, &buf)
== -1)
               {
                     return -1;
               }

                根据返回的 buf.index 找到对应的 mmap 映射好的缓存,取出视频数据。

          10. 将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

                 if (ioctl(fd,
VIDIOC_QBUF
, &buf)
== -1) {
                       return -1;
                 }

          11. 停止视频的采集

                 int ret = ioctl(fd,
VIDIOC_STREAMOFF
, &buf_type);

          12. 关闭视频设备

                 close(fd);

抱歉!评论已关闭.