本文主要是通过对虚拟视频驱动程序 vivi.c 源码分析,加深对 V4L2 框架的理解。转载于:https://blog.csdn.net/yanbixing123/article/details/52300886

一、V4L2 驱动核心

V4L2 驱动源码在 drivers/media/video 目录下,主要核心代码有:

  • v4l2-dev.c:Linux 版本 2 视频捕捉接口,主要结构体 video_device 的注册;
  • v4l2-common.c:在 Linux 操作系统体系采用低级别的操作一套设备 struct  tures/vectors 的通用视频设备接口;
  • v4l2-device.c:V4L2 的设备支持,注册 v4l2_device;
  • v4l2-ioctl.c:处理 V4L2 的 ioctl 命令的一个通用的框架;
  • v4l2-subdev.c:v4l2 子设备;
  • v4l2-mem2mem.c:内存到内存为 Linux 和 videobuf 视频设备的框架,设备的辅助函数,使用其源和目的 videobuf 缓冲区。

直接来看驱动源码的话,还是对驱动的框架没有一个感性的认识,尤其这个 V4L2 框架非常复杂,我们先从内核源码中提供的虚拟视频驱动程序 vivi.c 来分析,内核版本 3.4.2。

二、虚拟视频驱动程序 vivi.c 源码分析

2.1、分析一个程序从它的 init 入口函数开始分析:

static int __init vivi_init(void)
{const struct font_desc *font = find_font("VGA8x16");int ret = 0, i;if (font == NULL) {printk(KERN_ERR "vivi: could not find font\n");return -ENODEV;}font8x16 = font->data;if (n_devs <= 0)n_devs = 1;for (i = 0; i < n_devs; i++) {ret = vivi_create_instance(i);if (ret) {/* If some instantiations succeeded, keep driver */if (i)ret = 0;break;}}if (ret < 0) {printk(KERN_ERR "vivi: error %d while loading driver\n", ret);return ret;}printk(KERN_INFO "Video Technology Magazine Virtual Video ""Capture Board ver %s successfully loaded.\n",VIVI_VERSION);/* n_devs will reflect the actual number of allocated devices */n_devs = i;return ret;
}static void __exit vivi_exit(void)
{vivi_release();
}module_init(vivi_init);
module_exit(vivi_exit);

其中 n_devs 的定义在前面,如下所示:

static unsigned n_devs = 1;
module_param(n_devs, uint, 0644);
MODULE_PARAM_DESC(n_devs, "numbers of video devices to create");

写的很清楚了,n_devs 表示想要创建的 video devices 个数。

注:一般用户态传递参数是通过 main 函数,第一个参数表示 args 个数,第二个参数表示具体的参数。在 kernel 态 ,无法通过这样的方式传递参数,一般使用 module_param 的方式,步骤如下:

  1. 使用 module_param 指定模块的参数;

  2. 加载 driver 时给模块传递参数;

去掉其他的判断语句,发现重要的函数只有一个 vivi_create_instance(i) 函数,我们下面就来分析这个函数。

2.2、vivi_create_instance(i) 函数:

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;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return -ENOMEM;snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),"%s-%03d", VIVI_MODULE_NAME, inst);ret = v4l2_device_register(NULL, &dev->v4l2_dev);if (ret)goto free_dev;dev->fmt = &formats[0];dev->width = 640;dev->height = 480;hdl = &dev->ctrl_handler;v4l2_ctrl_handler_init(hdl, 11);dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_CONTRAST, 0, 255, 1, 16);dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_SATURATION, 0, 255, 1, 127);dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_HUE, -128, 127, 1, 0);dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_AUTOGAIN, 0, 1, 1, 1);dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_GAIN, 0, 255, 1, 100);dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);dev->bitmask = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_bitmask, NULL);if (hdl->error) {ret = hdl->error;goto unreg_dev;}v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);dev->v4l2_dev.ctrl_handler = hdl;/* initialize locks */spin_lock_init(&dev->slock);/* initialize queue */q = &dev->vb_vidq;memset(q, 0, sizeof(dev->vb_vidq));q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;q->drv_priv = dev;q->buf_struct_size = sizeof(struct vivi_buffer);q->ops = &vivi_video_qops;q->mem_ops = &vb2_vmalloc_memops;vb2_queue_init(q);mutex_init(&dev->mutex);/* init video dma queues */INIT_LIST_HEAD(&dev->vidq.active);init_waitqueue_head(&dev->vidq.wq);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);/** Provide a mutex to v4l2 core. It will be used to protect* all fops and v4l2 ioctls.*/vfd->lock = &dev->mutex;ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);if (ret < 0)goto rel_vdev;video_set_drvdata(vfd, dev);/* Now that everything is fine, let's add it to device list */list_add_tail(&dev->vivi_devlist, &vivi_devlist);if (video_nr != -1)video_nr++;dev->vfd = vfd;v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",video_device_node_name(vfd));return 0;rel_vdev:video_device_release(vfd);
unreg_dev:v4l2_ctrl_handler_free(hdl);v4l2_device_unregister(&dev->v4l2_dev);
free_dev:kfree(dev);return ret;
}

1)、函数首先为 struct vivi_dev *dev 分配内存,然后将 dev->v4l2_dev.name 的名字设置为 “vivi-i” 的形式,然后调用 v4l2_device_register 这个函数来注册 dev->v4l2_dev 这个结构体,结构体 v4l2_device 如下所示,看它的名字就叫 V4L2 设备,它肯定就是 V4L2 设备的核心结构体:

struct v4l2_device {/* dev->driver_data points to this struct.Note: dev might be NULL if there is no parent deviceas is the case with e.g. ISA devices.*/
#if defined(CONFIG_MEDIA_CONTROLLER) struct media_device *mdev;
#endif/* used to keep track of the registered subdevs */struct list_head subdevs;/* lock this struct, can be used by the drivers as well if this struct is embedded into a larger struct.*/  spinlock_t lock;/* unique device name, by default the driver name + bus ID */char name[V4L2_DEVICE_NAME_SIZE];/* notify callback called by some sub-devices. */void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);/* The control handler. May be NULL. */struct v4l2_ctrl_handler *ctrl_handler;/* Device's priority state */struct v4l2_prio_state prio;/* BKL replacement mutex.Temporary solution only. */struct kref ref;/* Release function that is called when the ref count goes to 0. */void (*release)(struct v4l2_devcie *v4l2_dev);
};

可以看到这个结构体里面包含一个 device 父设备成员,一个 subdevs 子设备链表头,一个自旋锁,一个 notify 函数指针,v4l2_ctrl_handler 控制句柄,prio 优先级,ref 引用计数,还有一个 release 函数指针。暂时先不对这个结构体进行具体的分析,在以后分析 V4L2 框架的时候再分析。

2)、它通过 v4l2_devie_register(NULL, &dev->v4l2_dev) 函数来完成对结构体的注册,可以看出在 “vivi.c” 中,它的父设备为 NULL,v4l2_device_register 函数在 v4l2-device.c 中:

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{if (v4l2_dev == NULL)return -EINVAL;INIT_LIST_HEAD(&v4l2_dev->subdevs);spin_lock_init(&v4l2_dev->lock);mutex_init(&v4l2_dev->ioctl_lock);v4l2_prio_init(&v4l2_dev->prio);kref_init(&v4l2_dev->ref);get_device(dev);v4l2_dev->dev = dev;if (dev == NULL) {/* If dev == NULL, then name must be filled in by the caller */WARN_ON(!v4l2_dev->name[0]);return 0;}/* Set name to driver name + device name if it is empty. */if (!v4l2_dev->name[0])snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",dev->driver->name, dev_name(dev));if (!dev_get_drvdata(dev))dev_set_drvdata(dev, v4l2_dev);return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);

这个函数完成了子设备链表的初始化,自旋锁,互斥量,优先级,引用计数的初始化,其他并没做太多工作。

3)、继续回到 vivi_create_instance(i) 函数中进行分析,下一句话:dev->fmt = &formats[0];

通过向前搜索发现,vivi.c  维持着一个 formats 数组,它表示 vivi.c 支持的数据格式。关于视频的格式我们在 V4L2 框架中介绍,通过这行代码,我们知道了 vivi.c 所支持的格式为:V4L2_PIX_FMT_YUYV。

struct vivi_fmt {char *name;u32 fourcc; /* v4l2 format id */int depth;
};static struct vivi_fmt formats[] = {{.name = "4:2:2, packed, YUYV",.fourcc = V4L2_PIX_FMT_YUYV,.depth = 16,},{.name = "4:2:2, packed, UYVY",.fourcc = V4L2_PIX_FMT_UYVY,.depth = 16,},{.name = "RGB565 (LE)",.fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */.depth = 16,},{.name = "RGB565 (BE)",.fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */.depth = 16,},{.name = "RGB555 (LE)",.fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */.depth = 16,},{.name = "RGB555 (BE)",.fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */.depth = 16,},
};

4)、继续在 vivi_create_instance(i) 函数中分析:

hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);

v4l2_ctrl_handler 结构体在 v4l2-ctrls.h 中定义,如下所示:

struct v4l2_ctrl_handler {struct mutex lock;struct list_head ctrls;struct list_head ctrl_refs;struct v4l2_ctrl_ref *cached;struct v4l2_ctrl_ref ** buckets;u16 nr_of_buckets;int error;
};

v4l2_ctrl_handler 是用于保存子设备控制方法集的结构体,对于视频设备这些 ctrls 包括设置亮度、饱和度、对比度和清晰度等,用链表的方式来保存 ctrls,可以通过 v4l2_ctrl_new_std 函数向链表添加 ctrls。在下面的代码中用到了这个函数。

/* Initialize the handler */
int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned nr_of_controls_hint)
{mutex_init(&hdl->lock);INIT_LIST_HEAD(&hdl->ctrls);INIT_LIST_HEAD(&hdl->ctrl_refs);hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8;hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]), GFP_KERNEL);hdl->error = hdl->buckets ? 0 : -ENOMEM;return hdl->error;
}

通过 nr_of_control_hint 变量的大小,计算出 nr_of_buckets,计算出 nr_of_buckets,并为 buckets 申请空间,并将申请结果保存在 error 变量中。

5)、继续在 vivi_create_instance(i) 函数中分析,继续设置 dev 结构体中的其他一些参数,对 volume,brightness,contrast,saturation 等参数设置的时候,调用了 v4l2_ctrl_new_std 这个函数,以及对 button,int32,menu,bitmask 等参数设置,调用了 v4l2_ctrl_new_custom 这个函数,一看就知道这两个函数是 V4L2 框架所提供的接口函数。

struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s32 min, s32 max, u32 step, s32 def);
  • hdl 是初始化好的 v4l2_ctrl_handler 结构体;
  • ops 是 v4l2_ctrl_ops 结构体,包含 ctrls 的具体实现;
  • id 是通过 IOCTL 的 arg 参数传过来的指令,定义在 v4l2-controls.h 文件;
  • min、max 用来定义某操作对象的范围。如:v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0);

用户空间可以通过 ioctl 的 VIDIOC_S_CTRL 指令调用到 v4l2_ctrl_handler, id 透过 arg 参数传递。

通过几个含糊是来完成对视频中 亮度,饱和度等的设置。

6)、然后是缓冲区队列的操作,设置 vb2_queue 队列 q 的一些参数,最主要的是下面两个参数:

q->ops = &vivi_qops;
q->mem_ops = &vb2_vmalloc_memops;

可以看到:q->ops = &vivi_video_qops 中 vivi_video_qops 是需要 vivi.c 实现的一个操作函数集,它在 vivi.c 中定义如下:

static struct vb2_ops vivi_video_qops = {.queue_setup = queue_setup,.buf_init = buffer_init,.buf_prepare = buffer_prepare,.buf_finish = buffer_finish,.buf_cleanup = buffer_cleanup,.buf_queue = buffer_queue,.start_streaming = start_streaming,.stop_streaming = stop_streaming,.wait_prepare = vivi_unlock,.wait_finish = vivi_lock,
};

这几个函数是需要我们写驱动程序的时候自己实现的函数。

其中 vb2_ops 结构体在 videobuf2-core.h 中定义,如下所示:

struct vb2_ops {int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,unsigned int *num_buffers, unsigned int *num_planes,unsigned int sizes[], void *alloc_ctxs[]);void (*wait_prepare)(struct vb2_queue *q);void (*wait_finish)(struct vb2_queue *q);int (*buf_init)(struct vb2_buffer *vb);int (*buf_prepare)(struct vb2_buffer *vb);int (*buf_finish)(struct vb2_buffer *vb);void (*buf_cleanup)(struct vb2_buffer *vb);int (*start_streaming)(struct vb2_queue *q, unsigned int count);int (*stop_streaming)(struct vb2_queue *q);void (*buf_queue)(struct vb2_buffer *vb);
};

对于 vb2_vmalloc_memops 结构体,它在 videobuf2-vmalloc.c 中定义,如下所示:

const struct vb2_mem_ops vb2_vmalloc_memops = {.alloc        = vb2_vmalloc_alloc,.put        = vb2_vmalloc_put,.get_userptr    = vb2_vmalloc_get_userptr,.put_userptr    = vb2_vmalloc_put_userptr,.vaddr        = vb2_vmalloc_vaddr,.mmap        = vb2_vmalloc_mmap,.num_users    = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);

看它的名字是 vb2 开头的,这几个函数应该都是系统为我们提供好的函数,通过查看源码发现,它确实存在于 videobuf2-vmalloc.c 中。

然后调用 vb2_queue_init(q) 函数来初始化它,vb2_queue_init(q) 函数如下所示:

/*** vb2_queue_init() - initialize a videobuf2 queue* @q:        videobuf2 queue; this structure should be allocated in driver** The vb2_queue structure should be allocated by the driver. The driver is* responsible of clearing it's content and setting initial values for some* required entries before calling this function.* q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer* to the struct vb2_queue description in include/media/videobuf2-core.h* for more information.*/
int vb2_queue_init(struct vb2_queue *q)
{BUG_ON(!q);BUG_ON(!q->ops);BUG_ON(!q->mem_ops);BUG_ON(!q->type);BUG_ON(!q->io_modes);BUG_ON(!q->ops->queue_setup);BUG_ON(!q->ops->buf_queue);INIT_LIST_HEAD(&q->queued_list);INIT_LIST_HEAD(&q->done_list);spin_lock_init(&q->done_lock);init_waitqueue_head(&q->done_wq);if (q->buf_struct_size == 0)q->buf_struct_size = sizeof(struct vb2_buffer);return 0;
}
EXPORT_SYMBOL_GPL(vb2_queue_init);

它只是完成了一些检查判断的语句,进行了一些链表,自旋锁等的初始化。

7)、/* init video dma queues */

INIT_LIST_HEAD(&dev->vidq.active);

init_waitqueue_head(&dev->vidq.wq);

8)、下面是对 video_device 的操作,它算是这个函数中核心的操作:

struct video_device *vfd;
vfd = video_device_alloc();
*vfd = vivi_template;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
video_set_drvdata(vfd, dev);

8.1)、先来看这个 video_device 结构体,它在 v4l2-dev.h 中定义,显示如下:

struct video_device {/* device ops */const struct v4l2_file_operations *fops;/* sysfs */struct device dev;    /* v4l device */struct cdev *cdev;    /* character device *//* Set either parent or v4l2_dev if your driver uses v4l2_device */struct device *parent;    /* device parent */struct v4l2_device *v4l2_dev;    /* v4l2_device parent *//* Control handler associated with this device node. May be NULL */struct v4l2_ctrl_handler *ctrl_handler;/* Priority state. If NULL, then v4l2_dev->prio will be used */struct v4l2_prio_state *prio;/* device info */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;/* attribute to differentiate multiple indices on one physical device */int index;/* V4L2 file handles */spinlock_t fh_lock;    /* Lock for all v4l2_fhs */struct list_head fh_list;    /* List of struct v4l2_fh */int debug;    /* Activates debug level *//* Video standed vars */v4l2_std_id tvnorms;    /* Supproted tv norms */v4l2_std_id current_norm;    /* Current tv norm *//* callbacks */void (*release)(struct video_device *vdev);/* ioctl callbacks */const struct v4l2_ioctl_ops *ioctl_ops;/* serialization lock */struct mutex *lock;
};

根据注释应该能大致了解各个成员的意义。后面有这个函数的一些初始化和注册函数,里面肯定有对这个结构体成员的设置初始化等,所以我们在后面再具体分析这些成员。

8.2)、下面来看 video_device_alloc 函数。它在 v4l2-dev.c 中定义:

struct video_device *video_device_alloc(void)
{return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}
EXPORT_SYMBOL(video_device_alloc);

只是分配了一段内存,然后将它置为 0,并没有对 video_device 结构体里面的成员进行设置。

8.3)、然后 vivi.c 中下一句是 *vfd = vivi_template 在 vivi.c 搜索发现,它在前面定义:

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,
};

对比 video_device 结构体中的成员,可以确定就是在这进行赋值的。它只是对其中某些成员进行了赋值。

8.3.1)、video_device 结构体中首先是 .fops = &vivi_fops,在 vivi.c 中搜索如下所示:

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,
};

先来看这几个函数的名字,其中 open 函数和 unlocked_ioctl 函数的名字与其他的不同,直觉告诉我,他俩是系统提供的,其他的函数名字都是 vivi 开头的,应该是这个文件里面实现的函数,我们在 vivi.c 中搜索就可以找到,但是我们暂时先不具体分析这几个函数。

8.3.2)、video_device 结构体中第二个是  .ioctl_ops = &vivi_ioctl_ops ,看名字也是在 vivi.c 中定义的:

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,
};

可以看到,这是一大堆的 ioctl 调用,大致可分为以下几类:

  • 查询性能 query capability 的:vidioc_querycap;

  • 对 format 的一些操作:vidioc_enum_fmt_vid_cap,vidioc_g_fmt_vid_cap,vidioc_try_fmt_vid_cap,vidioc_s_fmt_vid_cap;

  • 对缓冲区的一些操作:vidioc_reqbufs,vidioc_querybuf,vidioc_qbuf,vidioc_dqbuf;

  • 对标准 standard 的操作:vidioc_s_std;

  • 对输入 input 的操作:vidioc_enum_input,vidioc_g_input,vidiocs_s_input;

  • 对流 stream 的操作:vidioc_streamon,vidioc_streamoff;

以上几个 ioctl 调用都是需要我们自己实现的,后面 3 个 ioctl 的名字是 v4l2 开头的,应该是系统里面实现好的函数,搜索可以发现在 v4l2-ctrls.c 和 v4l2-event.c 中定义。

8.3.3)、video_device 结构体中第三个是 .release = video_device_release,它在 v4l2-dev.c 中定义,如下所示:

void video_device_release(struct video_device *vdev)
{kfree(vdev);
}
EXPORT_SYMBOL(video_device_release);

8.3.4)、video_device 结构体中第四,五个是:

.tvnorms = V4L2_STD_525_60,

.current_norm = V4L2_STD_NTSC_M,

通过看 video_device 结构体中的注释:

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

是指支持的 TV 制式以及当前的 TV 制式。

8.3.5)、分析完了 vivi_template 中的成员,也即 video_device 结构体中的成员,还是有点迷惑的,在 video_device 结构体中,有一个 struct v4l2_file_operation 成员,这个成员又包含一个 unlocked_ioctl,同时 video_device 结构体中还有一个 struct v4l2_ioctl_ops 成员,怎么有两个 ioctl 成员函数啊?先大致分析一些,struct v4l2_file_operations vivi_fops 中 .unlocked_ioctl = video_ioctl2,这个 video_ioctl2 在 v4l2-ioctl.c 中:

long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2);

它又调用了 __video_do_ioctl 函数,也在 v4l2-ioctl.c 中,这个 __video_do_ioctl 函数是一个大的 switch,case 语句,根据不同的 case,调用不同的函数,以 VIDIOC_QUERYCAP 为例:

struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
case VIDIOC_QUERYCAP:ret = ops->vidioc_querycap(file, fh, cap);

用在 vivi.c 这个例子就是:vfd 这个结构体就是 vivi_template,Ops 就是 vivi_template 中的 ioctl_ops 成员,也就是 vivi_ioctl_ops,对于 VIDIOC_QUERCAP 宏,真正调用的是 vivi_ioctl_ops 中的 .vidiioc_querycap 成员,也即我们在 vivi.c 中自己实现的 static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) 函数。有点绕,我已晕@_@!~~ 咱们在后面再具体分析。

8.4)、继续在 vivi_create_instance 中分析:

vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;

注意这个语句,从这里可以看出在注册 video_device 之前必须先注册了 v4l2_device.

set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
vfd->lock = &dev->mutex;

进行了一些设置,然后就是大 boss 了:

ret = video_register_device(vfd, VFL_TYPE_GRABBER,  video_nr);

它在 v4l2-dev.h 中定义,如下所示:

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

__video_register_device 在 v4l2-dev.c 中定义:(就直接在代码中注释了)

/***    __video_register_device - register video4linux devices*    @vdev: video device structure we want to register*    @type: type of device to register*    @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ...* -1 == first free)*    @warn_if_nr_in_use: warn if the desired device node number*     was already in use and another number was chosen instead.*    @owner: module that owns the video device node**    The registration code assigns minor numbers and device node numbers*    based on the requested type and registers the new device node with*    the kernel.**    This function assumes that struct video_device was zeroed when it*    was allocated and does not contain any stale date.**    An error is returned if no free minor or device node number could be*    found, or if the registration of the device node failed.**    Zero is returned on success.**    Valid types are**    %VFL_TYPE_GRABBER - A frame grabber**    %VFL_TYPE_VBI - Vertical blank data (undecoded)**    %VFL_TYPE_RADIO - A radio card**    %VFL_TYPE_SUBDEV - A subdevice*/
int __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{int i = 0;int ret;int minor_offset = 0;int minor_cnt = VIDEO_NUM_DEVICES;const char *name_base;/* A minor value of -1 marks this video device as neverhaving been registered */vdev->minor = -1;/* the release callback MUST be present */if (WARN_ON(!vdev->release))return -EINVAL;
/* 如果没有提供这个release函数的话,就直接返回错误,它就在vivi_template中提供了。 *//* v4l2_fh support */spin_lock_init(&vdev->fh_lock);INIT_LIST_HEAD(&vdev->fh_list);/* Part 1: check device type */switch (type) {case VFL_TYPE_GRABBER:name_base = "video";break;case VFL_TYPE_VBI:name_base = "vbi";break;case VFL_TYPE_RADIO:name_base = "radio";break;case VFL_TYPE_SUBDEV:name_base = "v4l-subdev";break;default:printk(KERN_ERR "%s called with unknown type: %d\n",__func__, type);return -EINVAL;}
/* 根据传进来的type参数,确定设备在/dev目录下看到的名字 */vdev->vfl_type = type;vdev->cdev = NULL;if (vdev->v4l2_dev) {if (vdev->v4l2_dev->dev)vdev->parent = vdev->v4l2_dev->dev;if (vdev->ctrl_handler == NULL)vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;/* If the prio state pointer is NULL, then use the v4l2_deviceprio state. */if (vdev->prio == NULL)vdev->prio = &vdev->v4l2_dev->prio;}
/* 进行vdev中父设备和ctrl处理函数的初始化。*//* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES/* Keep the ranges for the first four types for historical* reasons.* Newer devices (not yet in place) should use the range* of 128-191 and just pick the first free minor there* (new style). */switch (type) {case VFL_TYPE_GRABBER:minor_offset = 0;minor_cnt = 64;break;case VFL_TYPE_RADIO:minor_offset = 64;minor_cnt = 64;break;case VFL_TYPE_VBI:minor_offset = 224;minor_cnt = 32;break;default:minor_offset = 128;minor_cnt = 64;break;}
#endif/* Pick a device node number */mutex_lock(&videodev_lock);nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);if (nr == minor_cnt)nr = devnode_find(vdev, 0, minor_cnt);if (nr == minor_cnt) {printk(KERN_ERR "could not get a free device node number\n");mutex_unlock(&videodev_lock);return -ENFILE;}
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES/* 1-on-1 mapping of device node number to minor number */i = nr;
#else/* The device node number and minor numbers are independent, sowe just find the first free minor number. */for (i = 0; i < VIDEO_NUM_DEVICES; i++)if (video_device[i] == NULL)break;if (i == VIDEO_NUM_DEVICES) {mutex_unlock(&videodev_lock);printk(KERN_ERR "could not get a free minor\n");return -ENFILE;}
#endifvdev->minor = i + minor_offset;vdev->num = nr;devnode_set(vdev);/* Should not happen since we thought this minor was free */WARN_ON(video_device[vdev->minor] != NULL);vdev->index = get_index(vdev);mutex_unlock(&videodev_lock);
/* 上面的part2就是确定设备的次设备号 *//* Part 3: Initialize the character device */vdev->cdev = cdev_alloc();if (vdev->cdev == NULL) {ret = -ENOMEM;goto cleanup;}
/* 在这进行设备的注册,用cdev_alloc函数,从这我们就可以看出来,它是一个普通的字符设备驱动,然后设置它的一些参数。怎么就是字符设备驱动了???这个在后面的v4l2框架中再说。 */vdev->cdev->ops = &v4l2_fops;
/* cdev结构体里面的ops指向了v4l2_fops这个结构体,这个v4l2_fops结构体也是在v4l2-dev.c这个文件中。又一个file_operations操作函数集,在vivi.c中有一个v4l2_file_operations vivi_fops,他俩又是什么关系呢? */vdev->cdev->owner = owner;ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);if (ret < 0) {printk(KERN_ERR "%s: cdev_add failed\n", __func__);kfree(vdev->cdev);vdev->cdev = NULL;goto cleanup;}/* Part 4: register the device with sysfs */vdev->dev.class = &video_class;vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);if (vdev->parent)vdev->dev.parent = vdev->parent;dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);ret = device_register(&vdev->dev);if (ret < 0) {printk(KERN_ERR "%s: device_register failed\n", __func__);goto cleanup;}/* Register the release callback that will be called when the lastreference to the device goes away. */vdev->dev.release = v4l2_device_release;if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,name_base, nr, video_device_node_name(vdev));/* Increase v4l2_device refcount */if (vdev->v4l2_dev)v4l2_device_get(vdev->v4l2_dev);
/* 在sysfs中创建类,在类下创建设备结点 */#if defined(CONFIG_MEDIA_CONTROLLER)/* Part 5: Register the entity. */if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&vdev->vfl_type != VFL_TYPE_SUBDEV) {vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;vdev->entity.name = vdev->name;vdev->entity.info.v4l.major = VIDEO_MAJOR;vdev->entity.info.v4l.minor = vdev->minor;ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);if (ret < 0)printk(KERN_WARNING"%s: media_device_register_entity failed\n",__func__);}
#endif
/* 创建实体entity,这一步并不是必须的,需要配置了CONFIG_MEDIA_CONTROLLER选项后才会执行这一步,在这一步里面有一个media_entity实体结构体,在后面再分析它。 *//* Part 6: Activate this minor. The char device can now be used. */set_bit(V4L2_FL_REGISTERED, &vdev->flags);
/* 设置标志位 */mutex_lock(&videodev_lock);video_device[vdev->minor] = vdev;
/* 将设置好的video_device结构体vdev按照次设备号保存到video_device数组中。这个数组是在前面static struct video_device *video_device[VIDEO_NUM_DEVICES];定义的。 */mutex_unlock(&videodev_lock);return 0;cleanup:mutex_lock(&videodev_lock);if (vdev->cdev)cdev_del(vdev->cdev);devnode_clear(vdev);mutex_unlock(&videodev_lock);/* Mark this video device as never having been registered. */vdev->minor = -1;return ret;
}
EXPORT_SYMBOL(__video_register_device);

8.5)、注册完 video_device 结构体后继续 vivi_create_instance 中执行:

/* 将 vivi_dev dev 添加到 video_device vfd 中,为什么要这样做呢?是为了以后字符设备驱动接口的使用 */
video_set_drvdata(vfd, dev);/* 添加到 device list 链表中 */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);/* 用于计数 */
if(video_nr != -1)video_nr ++;/* 关联 video_device 和 vivi_dev */
dev->vfd = vfd;

三、分析总结

到这里我们就分析完了 vivi_init 和 vivi_create_instance 函数,vivi.c 中剩下的代码,基本就是以下 3 个结构体的具体实现代码我们暂时先不分析。

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,
};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 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,
};

我们首先分析这个 vivi.c 的目的是为了先大致看一些 v4l2 驱动的代码,留一些疑问,以后分析 v4l2 的代码框架及一些概念的时候,还会以这个 vivi.c 为例子来说明。等到分析完大致的框架之后,我们再来继续仔细分析 vivi.c 中那些具体代码的实现。

实例说明:

以 read 为例,应用程序在调用 read 的时候,对应到驱动 file_operations v4l2_fops 中的 v4l2_read 函数,在函数里面通过 ret = vdev->fops->read(filp, buf, sz, off) 最后调用到我们在 vivi.c 中申请注册的 video_device vivi_template 结构体里面的 fops->read 函数,即 vivi_read 函数。即 V4L2 框架只是提供了一个中转站的效果。再看 vivi_read 函数里面:return vb2_read(&dev->vb_vidq, data, count, ppos, file->f_flags & O_NONBLOCK);它又调用了 videobuf2-core.c 中的 vb2_read 函数。确实说明了 v4l2 框架的中转作用。

这样相似的函数有read,write,poll,mmap,release 等,比较特别的是 ioctl 函数,在后面分析它。它们都是应用程序调用,通过 V4L2 框架中转到对应的驱动程序中,然后驱动程序根据不同的调用,选择调用 videobuf 或 ioctl 中的函数。

对于 const struct v4l2_ioctl_ops *ioctl_ops,在 vivi.c 中就是 static const struct v4l2_ioctl_ops vivi_ioctl_ops;这个 ioctl 更麻烦,首先作为字符设备驱动,当应用程序调用 ioctl 的时候,就调用到了 file_operations v4l2_fops 中的 .unlocked_ioctl = v4l2_ioctl,这个 v4l2_ ioctl 同样通过 ret = vdev->fops->ioctl(filp, cmd, arg) 就调用到了 vivi.c 中申请注册的 struct video_device vivi_template 结构体里面的 fops->unlocked_ioctl 函数,即 v4l2_file_operatios vivi_fops 里面的 video_ioctl2 函数,这个 video_ioctl2 函数又调用 __video_do_ioctl 函数(以上两个函数都在 v4l2-ioctl.c 中),根据不同的 cmd 宏,以 VIDIOC_QUERYCAP 为例:

通过 ret = ops->vidioc_querycap(file, fh, cap);其中 struct video_device *vfd = video_devdata(file);const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;可以看出,__video_do_ioctl 函数又调用了 struct video_device vivi_template 结构体李曼的 .ioctl_ops = &vivi_ioctl_ops,然后根据宏的名字来选择 struct v4l2_ioctl_ios vivi_ioctl_ops 中对应的函数,即 vidioc_querycap 函数。

四、V4L2 框架分析

分析原文为:<V4L2 框架分析>,以下是对其的补充以及根据 vivi.c 进行实例分析。

4.1、ioctl 框架

struct v4l2_ioctl_ops {/* ioctl callbacks *//* VIDIOC_QUERYCAP handler */int(*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);/* Priority handing */int (*vidioc_g_priority)(struct file *file, void *fh, enum v4l2_priority *p);int (*vidioc_s_priority)(struct file *file, void *fh, enum v4l2_priority *p);... .../* For other private ioctls */long (*vidioc_default)(struct file *file, void *fh, bool valid_prio, int cmd, void *arg);};

4.1.1、

驱动要实现的第一个回调函数可能就是:

/* VIDIOC_QUERYCAP handler */int (*vidioc_querycap)(struct file *file, void *priv, struct v4l2_capability *cap);

这个函数处理 VIDIOC_QUERYCAP 的 ioctl(),只是简单问问 “你是谁?你能干什么?” 实现它是 V4L2 驱动的责任。和所有其他 V4L2 回调函数一样,这个函数中的参数 priv 是 file->private_data 域的内容,通常的做法是在 open() 的时候把它指向驱动中表示设备的内部结构体。

驱动应该负责填充 cap 结构并且返回 “0 或负的错误码” 值。如果成功返回,则 V4L2 层会负责把回复拷贝到用户空间。

struct v4l2_capability 定义在 videodev2.h 中,如下所示:

/*** struct v4l2_capability - Describes V4L2 device caps returned by                                         VIDIOC_QUERYCAP** @driver:     name of the driver module (e.g. "bttv")* @card:     name of the card (e.g. "Hauppauge WinTV")* @bus_info:     name of the bus (e.g. "PCI:" + pci_name(pci_dev) )* @version:     KERNEL_VERSION* @capabilities: capabilities of the physical device as a whole* @device_caps: capabilities accessed via this particular device (node)* @reserved:     reserved fields for future extensions*/
struct v4l2_capability {__u8    driver[16]; //driver的名字__u8    card[32]; //设备的硬件描述信息__u8    bus_info[32];__u32 version; //内核版本号__u32    capabilities;__u32    device_caps;__u32    reserved[3];
};

对于 bus_info 成员,驱动程序一般用 strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cpa->bus_info)) 来填充。capabilites 成员是一个位掩码用来描述驱动能做的不同事情,也在 videodev2.h 中定义:

/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE        0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT        0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY        0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE        0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT        0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE    0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT    0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE        0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY    0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK        0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_RDS_OUTPUT        0x00000800 /* Is an RDS encoder *//* Is a video capture device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE    0x00001000
/* Is a video output device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE    0x00002000#define V4L2_CAP_TUNER            0x00010000 /* has a tuner */
#define V4L2_CAP_AUDIO            0x00020000 /* has audio support */
#define V4L2_CAP_RADIO            0x00040000 /* is a radio device */
#define V4L2_CAP_MODULATOR        0x00080000 /* has a modulator */#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */#define V4L2_CAP_DEVICE_CAPS 0x80000000 /* sets device capabilities field */

下面我们来看看 vivi.c 中对它的实现:

static int vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{struct vivi_dev *dev = video_drvdata(file);strcpy(cap->driver, "vivi");strcpy(cap->card, "vivi");strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |V4L2_CAP_READWRITE;cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;return 0;
}

它就是完成上述我们所说的事情。

4.1.2、输入和输出

1)、综述

在很多情况下,视频适配器并不能提供很多的输入输出选项。比如摄像头控制器,可能只是提供摄像头信号输入,而没什么别的功能;然而,在一些其他的情况下,事情就变得复杂了。一个电视卡板上不同的接口可能对应不同的输入。它甚至可能拥有可独立发挥功能的多路调谐器。有时,那些输入会有不同的特性,有些调谐器可以支持比其他的更广泛的视频标准。对于输出来说,也有同样的问题。很明显,一个应用若想有效地利用视频适配器,它必须有能力找到可用的输入和输出,而且他必须能找到他想操作的那一个。为此,Video4Linux2 API 提供三种不同的 ioctl() 调用来处理输入,相应地有三个来处理输出。如下所示:

    int (*vidioc_g_std) (struct file *file, void *fh, v4l2_std_id *norm);int (*vidioc_s_std) (struct file *file, void *fh, v4l2_std_id *norm);int (*vidioc_querystd) (struct file *file, void *fh, v4l2_std_id *a);/* Input handling */int (*vidioc_enum_input)(struct file *file, void *fh, struct v4l2_input *inp);int (*vidioc_g_input) (struct file *file, void *fh, unsigned int *i);int (*vidioc_s_input) (struct file *file, void *fh, unsigned int i);/* Output handling */int (*vidioc_enum_output) (struct file *file, void *fh, struct v4l2_output *a);int (*vidioc_g_output) (struct file *file, void *fh, unsigned int *i);int (*vidioc_s_output) (struct file *file, void *fh, unsigned int i);

对于用户空间而言,V4L2 提供一个 ioctl() 命令(VIDIOC_ENUMSTD),它允许应用查询设备实现了哪些标准。驱动却无需直接回答查询,而是将 video_device 结构体的 tvnorm 字段设置为它所支持的所有标准。

然后 V4L2 层回想应用回复所支持的标准。VIDIOC_G_STD 命令可以用来查询现在哪种标准是激活的,它也是在 V4L2 层通过返回 video_device 结构体的 current_norm 字段来处理的。驱动程序应在启动时,初始化 current_norm 来反映现实情况。当某个应用想要申请某个特定标准时,会发出一个 VIDIOC_S_STD 调用,该调用传到驱动时通过调用 int (*vidioc_s_std)(struct file *file, void *fh, v4l2_std_id *norm) 回调函数来实现,来看 vivi.c 中:

 static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i)
{return 0;
}

扩展:

下面就简单说一些视频标准,这些标志描述的是视频如何为传输而进行格式化 ---  分辨率、帧率等。现在世界上使用的标准主要有三个:NTSC(主要是北美使用)、PAL(主要是欧洲、非洲和中国)和 SECAM(法、俄和非洲部分地区)。

V4L2 使用 v4l2_std_id 来代表视频标准,它是一个 64 位的掩码。每个标准变种在掩码中就是一位。所以 “标准” NTSC 的定义为 V4L2_STD_NTSC_M,值为 0x1000;而日本的变种就是 V4L2_STD_NTSC_M_JP(0x2000)。如果一个设备可以处理所有 NTSC 变种,它就可以设为 V4L2_STD_NTSC,它将所有相关位置位。它同样在 videodev2.h 中定义:

/** A N A L O G V I D E O S T A N D A R D*/typedef __u64 v4l2_std_id;/* one bit for each */
#define V4L2_STD_PAL_B ((v4l2_std_id)0x00000001)
#define V4L2_STD_PAL_B1 ((v4l2_std_id)0x00000002)
#define V4L2_STD_PAL_G ((v4l2_std_id)0x00000004)
#define V4L2_STD_PAL_H ((v4l2_std_id)0x00000008)
#define V4L2_STD_PAL_I ((v4l2_std_id)0x00000010)
#define V4L2_STD_PAL_D ((v4l2_std_id)0x00000020)
#define V4L2_STD_PAL_D1 ((v4l2_std_id)0x00000040)
#define V4L2_STD_PAL_K ((v4l2_std_id)0x00000080)#define V4L2_STD_PAL_M ((v4l2_std_id)0x00000100)
#define V4L2_STD_PAL_N ((v4l2_std_id)0x00000200)
#define V4L2_STD_PAL_Nc ((v4l2_std_id)0x00000400)
#define V4L2_STD_PAL_60 ((v4l2_std_id)0x00000800)#define V4L2_STD_NTSC_M ((v4l2_std_id)0x00001000)    /* BTSC */
#define V4L2_STD_NTSC_M_JP ((v4l2_std_id)0x00002000)    /* EIA-J */
#define V4L2_STD_NTSC_443 ((v4l2_std_id)0x00004000)
#define V4L2_STD_NTSC_M_KR ((v4l2_std_id)0x00008000)    /* FM A2 */#define V4L2_STD_SECAM_B ((v4l2_std_id)0x00010000)
#define V4L2_STD_SECAM_D ((v4l2_std_id)0x00020000)
#define V4L2_STD_SECAM_G ((v4l2_std_id)0x00040000)
#define V4L2_STD_SECAM_H ((v4l2_std_id)0x00080000)
#define V4L2_STD_SECAM_K ((v4l2_std_id)0x00100000)
#define V4L2_STD_SECAM_K1 ((v4l2_std_id)0x00200000)
#define V4L2_STD_SECAM_L ((v4l2_std_id)0x00400000)
#define V4L2_STD_SECAM_LC ((v4l2_std_id)0x00800000)/* ATSC/HDTV */
#define V4L2_STD_ATSC_8_VSB ((v4l2_std_id)0x01000000)
#define V4L2_STD_ATSC_16_VSB ((v4l2_std_id)0x02000000)/** "Common" NTSC/M - It should be noticed that V4L2_STD_NTSC_443 is* Missing here.*/
#define V4L2_STD_NTSC (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR)

2)、输入

输入捕获的应用首先要通过 VIDIOC_ENUMINPUT 命令来枚举所有可用的输入。在 V4L2 层,这个调用会转换成调用驱动中对应的回调函数:

int (*vidioc_enum_input)(struct file *file, void *priv, struct v4l2_input *inp);

在这个调用中,file 对应要打开的视频设备。priv 是驱动的私有字段。inp 字段是传递的真正信息,先来看看这个 v4l2_input 结构体,它在 videodev2.h 中定义:

struct v4l2_input {__u32 index;    /* Which input */__u8 name[32];    /* Label */__u32 type;    /* Type of input */__u32 audioset;    /* Associated audios(bitfield) */__u32 tuner;    /* Associated tuner */v4l2_std_id std;__u32 status;__u32 capabilities;__u32 reserved[3];
};

结构体分析:

  • index:是应用关注的输入索引号;这是唯一一个用户空间设定的字段。驱动要分配索引好给输入,从 0 开始,依次增加。想要知道所有可用的输入,应用会调用 VIDIOC_ENUMINPUT,索引号从 0 开始,并开始递增。一旦驱动返回 -EINVAL,应用就知道:输入已经枚举完了。只要有输入,输入索引,输入索引号 0 就一定存在。

  • name:是输入的名字,由驱动确定。

  • type:输入类型,只有两个值可选:V4L2_INPUT_TYPE_TUNER 和 V4L2_INPUT_TYPE_CAMERA。

  • status:给出输入状态,其中设置的每一位都代表一个问题,比如说掉电,无信号等问题,定义如下:
    #define V4L2_IN_ST_NO_POWER 0x00000001  /* Attached device is off */
    #define V4L2_IN_ST_NO_SIGNAL 0x00000002
    #define V4L2_IN_ST_NO_COLOR 0x00000004

  • std:描述设备支持哪个或哪些视频标准。就是上面咱们说的视频标准。

来看看 vivi.c 中是怎么实现这个函数的:

/* only one input in this sample driver */
static int vidioc_enum_input(struct file *file, void *priv,struct v4l2_input *inp)
{if (inp->index >= NUM_INPUTS)return -EINVAL;inp->type = V4L2_INPUT_TYPE_CAMERA;inp->std = V4L2_STD_525_60;sprintf(inp->name, "Camera %u", inp->index);return 0;
}

当应用想改变当前输入时,驱动会收到一个回调函数 vidioc_s_input() 的调用:

int (*vidioc_s_input)(struct file *file, void *priv, unsigned int index);

index 与上面提到的先沟通呢,它用来确定哪个输入是想要的,驱动要对硬件操作,选择指定输入并返回 0。也有可能要返回 -EINVAL(索引号不正确)或 -EIO(硬件故障)。即使只有一路输入,驱动也要实现这个回调函数。

还有另一个回调函数,指示哪一个输入处在激活状态:

int (*vidioc_g_input)(struct file *file, void *priv, unsigned int *i);

这里驱动把 *index 值设为当前激活输入的索引号。

看看 vivi.c 中对这两个函数的实现:

static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
{struct vivi_dev *dev = video_drvdata(file);*i = dev->input;return 0;
}static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
{struct vivi_dev *dev = video_drvdata(file);if (i >= NUM_INPUTS)return -EINVAL;if (i == dev->input)return 0;dev->input = i;precalculate_bars(dev);precalculate_line(dev);return 0;
}

3)、输出

枚举和选择输出的过程与输入十分相似。

输出枚举的回调函数是这样的:

int (*vidioc_enumoutput)(struct file *file, void *private_data, struct v4l2_output *output);

其中 v4l2_output 结构体如下所示:

struct v4l2_output {__u32 index;    /* Which output */__u8 name[32];    /* Label */__u32 type;    /* Type of output */__u32 audioset;    /* Associated audios(bitfield) */__u32 modulator;    /* Associated modulator */v4l2_std_id std;__u32 capabilities;__u32 reserved[3];
};

结构体分析:

  • index:相关输出索引号,工作方式与输入的索引号相同。

  • type:输出类型,支持的类型如下:
    #define V4L2_OUTPUT_TYPE_MODULETOR    1    //用于模拟电视调制器
    #define V4L2_OUTPUT_TYPE_ANALOG            2    //用于基本模拟视频输出
    #define V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY    3    //用于模拟 VGA 覆盖设备

  • audioset:能与视频协同工作的音频集。

  • modulator:与此设备相关的调制器(仅对类型为 V4L2_OUTPUT_TYPE_MODULATOR 的设备而言)。

  • std:描述设备支持哪个或哪些视频标准。就是上面咱们所说的视频标准。

  • capabilities flags:
    #define V4L2_OUT_CAP_PRESETS    0x00000001    /* Supports S_DV_PRESET */
    #define V4L2_OUT_CAP_CUSTOM_TIMINGS    0x00000002    /* Supports S_DV_TIMINGS */
    #define V4L2_OUT_CAP_STD    0x00000004   /* Supports S_STD */

也有用于获得和设定限行输出设置的回调函数:

int (*vidioc_g_output)(struct file *file, void *fh, unsigned int *i);
int (*vidioc_s_output)(struct file *file, void *fh, unsigned int *i);

有了这些函数之后,V4L2 应用就可以知道有哪些输入和输出,并在它们间进行选择。

4.1.3、颜色与格式

应用在视频设备可以工作之前,它必须与驱动达成一致,知道视频数据是何种格式。这种协商将是一个非常复杂的过程,其原因有二:

  1. 视频硬件所支持的视频格式各不相同;

  2. 在内核的格式转换是令人难以接受的。

所以应用要找出一种硬件支持的格式,并做出一种大家都可以接受的配置。这篇文章将会讲述格式的基本描述方式,下篇文章则会讲述 V4L2 驱动与应用协商格式时所实现的 API。

1)、色域

色域从广义上来讲,就是系统在描述色彩是所使用的坐标系。V4L2 规范中定义了好几个,但只有两个的使用最为广泛。它们是:

a、V4L2_COLORSPACE_SRGB

多数开发者所熟悉的 [ red、green、blue ] 数组就包含在这个色域中。它为每一种颜色提供了一个简单的强度值,把它们混在一起,从而产生了丰富的颜色。表示 RGB 值的方法有很多,我们在下面将会介绍。这个色域也包含 YUV 和 YCbCr 的表示方法,这个表示方法最早是为了早期的彩色电视机信号可以在黑白电视中的播放,所以 Y(或 “亮度”)值只有一个简单的亮度值,单独显示时可以产生灰度图像。U 和 V(或 Cb 和 Cr)色度值描述的是色彩中蓝色和红色的分量。绿色可以通过从亮度中减去这些分量而得到。

YUV 和 RGB 之间的转换并不那么直接,但是我们有一些公式可用。

注意:YUV 和 YCbCr 并非完全一样,虽然有时候他们的名字会替代使用。

b、V4L2_COLORSPACE_SMPTE170M

这个是 NTSC 或 PAL 等电视信号的模拟色彩表示方法,电视调谐器通常产生的色域都属于这个色域。还存在很多其他的色域,他们多数都是电视相关标准的变种。

2)、密集存储和平面存储

如上所述,像素值是以数组的方式表示的,通常由 RGB 或 YUV 值组成。要把这个数组组织成图像,通常有两种常用的方法:

  • Packed 格式把一个像素的所有分量值连续存放在一起;

  • Planar 格式把每一个分量单独存储成一个阵列。例如在 YUV 格式中,所有 Y 值都连续地存储在一个阵列中,U 值存储在另一个中,V 值存在第三个中。这些平面常常都存储在一个缓冲区中,但并不一定非要这样。

紧密型存储方式可能使用更为广泛,特别是 RGB 格式,但这两种存储方式都可以由硬件产生并由应用程序请求。如果设备可以产生紧密型和平面型两种,那么驱动就要让两种都在用户空间可见。

3)、四字符码(four Charactor Code:FourCC)

V4L2 API 中表示色彩格式采用的是广受好评的四字符(fourCC)机制。这些编码都是 32 位的值,由四个 ASCII 码产生。如此一来,它就是有一个优点,易于传递,对人可读,例如,当一个色彩格式读作 “RGB4” 就没有必要去查表了。

注意:四字符码在很多不同的配置中都会使用,有些还真是早于 Linux。Mplayer 内部使用它们,然后,fourcc 只能说明一种编码机制,并不说明使用何种编码。Mplayer 有一个转换函数,用于在它自己的 fourcc 码和 v4l2 用的 fourcc 码之间做出转换。

4)、RGB 格式:(videodev2.h 中)

/* RGB formats */
#define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R', 'G', 'B', '1') /* 8 RGB-3-3-2 */
#define V4L2_PIX_FMT_RGB444 v4l2_fourcc('R', '4', '4', '4') /* 16 xxxxrrrr ggggbbbb */
#define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R', 'G', 'B', 'O') /* 16 RGB-5-5-5 */
#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P') /* 16 RGB-5-6-5 */
#define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R', 'G', 'B', 'Q') /* 16 RGB-5-5-5 BE */
#define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R', 'G', 'B', 'R') /* 16 RGB-5-6-5 BE */
#define V4L2_PIX_FMT_BGR666 v4l2_fourcc('B', 'G', 'R', 'H') /* 18 BGR-6-6-6     */
#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3') /* 24 BGR-8-8-8 */
#define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R', 'G', 'B', '3') /* 24 RGB-8-8-8 */
#define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B', 'G', 'R', '4') /* 32 BGR-8-8-8-8 */
#define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R', 'G', 'B', '4') /* 32 RGB-8-8-8-8 */

5)、YUV 格式:(videodev2.h)

/* Luminance+Chrominance formats */
#define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y', 'V', 'U', '9') /* 9 YVU 4:1:0 */
#define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y', 'V', '1', '2') /* 12 YVU 4:2:0 */
#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YVYU v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_VYUY v4l2_fourcc('V', 'Y', 'U', 'Y') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4', '2', '2', 'P') /* 16 YVU422 planar */
#define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4', '1', '1', 'P') /* 16 YVU411 planar */
#define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y', '4', '1', 'P') /* 12 YUV 4:1:1 */
#define V4L2_PIX_FMT_YUV444 v4l2_fourcc('Y', '4', '4', '4') /* 16 xxxxyyyy uuuuvvvv */
#define V4L2_PIX_FMT_YUV555 v4l2_fourcc('Y', 'U', 'V', 'O') /* 16 YUV-5-5-5 */
#define V4L2_PIX_FMT_YUV565 v4l2_fourcc('Y', 'U', 'V', 'P') /* 16 YUV-5-6-5 */
#define V4L2_PIX_FMT_YUV32 v4l2_fourcc('Y', 'U', 'V', '4') /* 32 YUV-8-8-8-8 */
#define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y', 'U', 'V', '9') /* 9 YUV 4:1:0 */
#define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y', 'U', '1', '2') /* 12 YUV 4:2:0 */
#define V4L2_PIX_FMT_HI240 v4l2_fourcc('H', 'I', '2', '4') /* 8 8-bit color */
#define V4L2_PIX_FMT_HM12 v4l2_fourcc('H', 'M', '1', '2') /* 8 YUV 4:2:0 16x16 macroblocks */
#define V4L2_PIX_FMT_M420 v4l2_fourcc('M', '4', '2', '0') /* 12 YUV 4:2:0 2 lines y, 1 line uv interleaved */

6)、其他格式

/* compressed formats */
#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */
#define V4L2_PIX_FMT_JPEG v4l2_fourcc('J', 'P', 'E', 'G') /* JFIF JPEG */

等等 ... ...

7)、格式描述符

struct v4l2_pix_format,它在 videodev2.h 中定义:

struct v4l2_pix_format {__u32 width;__u32 height;__u32 pixelformat;enum v4l2_field field;__u32 bytesperline;    /* for padding, zero if unused */__u32 sizeimage;enum v4l2_colorspace colorspace;__u32 priv;    /* private data,depends on pixelformat */
};

结构体分析:

  • width:图像宽度,以像素为单位;
  • height:图像高度,以像素为单位;
  • pixelformat:描述图像格式的四字符码;
  • field:很多图像源会使数据交错 —— 先传输奇数行,然后是偶数行。真正的摄像头设备是不会做数据交错的。V4L2 API 允许应用使用多种交错方式。常用的值为 V4L2_FIELD_NONE(非交错)、V4L2_FIELD_TOP(仅顶部交错)或 V4L2_FIELD_ANY(忽略)。具体的值可以去看 enum v4l2_field 的值,在这就不一一列举了;
  • bytesperline:相临扫描行之间的字节数,这包括各种设备可能会加入的填充字节;
  • sizeimage:存储图像所需的缓冲区的大小;
  • colorspace:使用的色域。同样可以查看 enum v4l2_colorspace 的值:
enum v4l2_colorspace {V4L2_COLORSPACE_SMPTE170M = 1,V4L2_COLORSPACE_SMPTE240M = 2,V4L2_COLORSPACE_REC709 = 3,V4L2_COLORSPACE_BT878 = 4,V4L2_COLORSPACE_470_SYSTEM_M = 5,V4L2_COLORSPACE_470_SYSTEM_BG = 6,V4L2_COLORSPACE_JPEG = 7,V4L2_COLORSPACE_SRGB = 8,
};

至此,对于视频数据缓冲区的描述就算完成了,驱动程序需要和应用程序协商,以使硬件设备支持的图像格式能够满足应用程序的使用。下面再来讲驱动程序和应用程序协商的过程。

4.1.4、格式协商

在存储器中表示图像的方法有很多中。市场几乎找不到可以处理所有 V4L2 所理解的视频格式的设备。驱动不应支持底层硬件不理解的视频格式。实际上在内核中进行格式转换是令人难以接收的。所以驱动必须能让应用选择一个硬件可以支持的格式。

1)、第一步就是简单的允许应用产需所支持的格式

VIDIOC_ENUM_FMT ioctl() 就是为此目的而提供的。在驱动内部,这个调用会转化为如下的回调函数(如果查询的是视频捕获设备):

int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_fmtdesc *f);

这个回调函数要求视频捕获设备描述其支持的格式,应用会传入一个 v4l2_fmtdesc 结构体:

struct v4l2_fmtdesc {__u32 index;    /* Format number */enum v4l2_buf_type type;    /* buffer type */__u32 flags;__u8 description[32];    /* Description string */__u32 pixelformat;    /* Format fourcc */__u32 reserved[4];
};

应用会设置 index 和 type 成员:

  • index:用来确定格式的一个简单整型数;与其他 V4L2 所使用的索引一样,这个也是从 0 开始递增,至最大允许值为止。
  • type:描述的是数据流类型,对于视频捕获设备(摄像头)来说就 V4L2_BUF_TYPE_VIDEO_CAPTURE。

如果 index 对就某个支持的格式,驱动应该填写结构体的其他成员:

  • flags:只定义了一个值,即 V4L2_FMT_FLAG_COMPRESSED,表示一个压缩的视频格式。

  • description:一般来说可以是对这个格式的一种简短的字符串描述。

  • pixelformat:描述视频表现方式的四字节码。

对于上述这个回调函数,它其实针对的是视频捕获设备,只有当 type 值为 V4L2_BUF_TYPE_VIDEO_CAPTURE 时才会调用,这是一个回调函数集,对于其他不同的设备,会根据不同的 type 值调用不同的回调函数。下面列举如下:

/* VIDIOC_ENUM_FMT handlers */
int (*vidioc_enum_fmt_vid_cap) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_overlay) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_cap_mplane)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out_mplane)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_type_private)(struct file *file, void *fh, struct v4l2_fmtdesc *f);

来看看 vivi.c 中对于这个回调函数的实现:

static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,struct v4l2_fmtdesc *f)
{struct vivi_fmt *fmt;if (f->index >= ARRAY_SIZE(formats))return -EINVAL;fmt = &formats[f->index];strlcpy(f->description, fmt->name, sizeof(f->description));f->pixelformat = fmt->fourcc;return 0;
}

2)、应用程序可以通过调用 VIDIOC_G_FMT 知道硬件现在的配置

这种情况下传递的参数是一个 v4l2_format 结构体:

struct v4l2_format {enum v4l2_buf_type type;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;
};

对于视频捕获(和输出)设备,联合体中 pix 成员是我们关注的重点。这是我们在上面见过的 v4l2_pix_format 结构体,驱动应该用现在的硬件设置填充那个结构体并且返回。

来看看 vivi.c 中对于这个回调函数的实现:

static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{struct vivi_dev *dev = video_drvdata(file);f->fmt.pix.width = dev->width;f->fmt.pix.height = dev->height;f->fmt.pix.field = dev->field;f->fmt.pix.pixelformat = dev->fmt->fourcc;f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt->depth) >> 3;f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;if (dev->fmt->fourcc == V4L2_PIX_FMT_YUYV ||dev->fmt->fourcc == V4L2_PIX_FMT_UYVY)f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;elsef->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;return 0;
}

3)、多数应用都想最终对硬件进行配置以使其为应用提供一种合适的格式

改变视频格式有两个函数接口,一个是 VIDIOC_TYPE_FMT 调用,它在 V4L2 驱动中转化为下面的回调函数:

int (*vidioc_try_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_format *f);

要处理这个调用,驱动会查看请求的视频格式,然后断定硬件是否支持这个格式。如果应用请求的格式是不被支持的,就会返回 -EINVAL。例如,描述了一个不支持格式的 fourcc 编码或者去请求了一个隔行扫描的视频,而设备只支持逐行扫描的就会失败。在另一方面,驱动可以调整 size 字段,以与硬件支持的图像大小相适应。通常的做法是尽量将大小调小。所以一个只能处理 VGA 分辨率的设备驱动会根据情况相应地调整 width 和 height 参数而成功返回。v4l2_format 结构体会在调用后复制给用户空间,驱动应该更新这个结构体以反应改变的参数,这样应用才可以知道它真正得到是什么。

VIDIOC_TRY_FMT 这个处理对于驱动来说是可选的,但不推荐忽略这个功能。如果提供了的话,这个函数可以在任何时候调用,甚至是设备正在工作的时候。它不可以对实质上的硬件参数做任何改变,只能让应用知道都可以做什么的一种方式。

如果应用要真正的改变硬件的格式,它使用 VIDIOC_S_FMT 调用,它以下面的方式到达驱动:

int (*vidioc_s_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_format *f);

与 VIDIOC_TRY_FMT 不同,这个回调是不能随时调用的。如果硬件正在工作或已经开辟了流缓冲区(后面的文章将介绍),改变格式会带来无尽的麻烦。想想会发生什么?比如,一个新的格式比现在使用的缓冲区大的时候。所以驱动总是要保证硬件是空闲的,否则就对请求返回失败(-EBUSY)。

格式的改变应该是原子的——它要么改变所有的参数以实现请求,要么一个也不改变。同样,驱动在必要时是可以改变图像大小的。

还是来看看 vivi.c 中对于这两个回调函数的实现:

static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{struct vivi_dev *dev = video_drvdata(file);struct vivi_fmt *fmt;enum v4l2_field field;fmt = get_format(f);if (!fmt) {dprintk(dev, 1, "Fourcc format (0x%08x) invalid.\n",f->fmt.pix.pixelformat);return -EINVAL;}field = f->fmt.pix.field;if (field == V4L2_FIELD_ANY) {field = V4L2_FIELD_INTERLACED;} else if (V4L2_FIELD_INTERLACED != field) {dprintk(dev, 1, "Field type invalid.\n");return -EINVAL;}f->fmt.pix.field = field;v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 2,&f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);f->fmt.pix.bytesperline =(f->fmt.pix.width * fmt->depth) >> 3;f->fmt.pix.sizeimage =f->fmt.pix.height * f->fmt.pix.bytesperline;if (fmt->fourcc == V4L2_PIX_FMT_YUYV ||fmt->fourcc == V4L2_PIX_FMT_UYVY)f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;elsef->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;return 0;
}static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f)
{struct vivi_dev *dev = video_drvdata(file);struct vb2_queue *q = &dev->vb_vidq;int ret = vidioc_try_fmt_vid_cap(file, priv, f);if (ret < 0)return ret;if (vb2_is_streaming(q)) {dprintk(dev, 1, "%s device busy\n", __func__);return -EBUSY;}dev->fmt = get_format(f);dev->width = f->fmt.pix.width;dev->height = f->fmt.pix.height;dev->field = f->fmt.pix.field;return 0;
}

4.1.5、IO 访问

V4L2 支持三种不同 IO 访问方式(内核还支持了其他的访问方式,暂不讨论):

  1. read 和 write,是基本帧 IO 访问方式,通过 read 读取每一帧数据,数据需要在内核和用户之间进行拷贝,这种方式访问速度可能会非常慢;

  2. 内存映射缓冲区(V4L2_MEMORY_MMAP),是在内核空间开辟缓冲区,应用通过 mmap()系统调用映射到用户地址空间。这些缓冲区可以是大而连续 DMA 缓冲区、通过 vmalloc() 创建的虚拟缓冲区,或者直接在设备的 IO 内存中开辟的缓冲区(如果硬件支持);

  3. 用户空间缓冲区(V4L2_MEMORY_USERPTR),是用户空间的应用中开辟缓冲区,用户与内核空间之间交换缓冲区指针。很明显,在这种情况下是不需要 mmap() 调用地蛋驱动为有效的支持用户空间缓冲区,其工作将也会更困难。

read 和 write 方式属于帧 IO 访问方式,每一帧都通过 IO 操作,需要用户和内核之间数据拷贝,而后两种是流 IO 访问方式,不需要内存拷贝,访问速度比较快。内存映射缓冲区访问方式是比较常用的方式。

对于其他的两种 IO 访问方式,我们展示不探讨,因为写到这,我已经腰酸背痛腿抽筋了 !__!

4.1.6、流 IO 方式

关于流 IO 的方式,内核中又一个更高层次的 API,它能够帮助驱动作者完成流驱动。当底层设备可以支持分散/聚集 I/O 的时候,这一层(称为 video-buf)可以是工作变得容易。关于 video-buf API 我们将在后面讨论。

支持流 I/O 的驱动应通过在 vidioc_querycap() 方法中设置 V4L2_CAP_STREAMING 标签通知应用。注意:我们无法描述支持哪种缓冲区,那是后话。

1)、v4l2_buffer 结构体

当使用流 I/O 时,帧以 v4l2_buffer 的格式在应用和驱动之间传输。

首先大家要知道一个缓冲区可以有三种基本状态:

  1. 在驱动的传入队列中。如果驱动不用它做什么有用事的话,应用就可以把缓冲区放在这个队列里。对于一个视频捕获设备,传入队列中的缓冲区是空的,等待驱动向其中填入视频数据;对于输出设备来讲,这些缓冲区是要设备发送的帧数据。

  2. 在驱动的传出队列中。这些缓冲区已由驱动处理过,正等待应用来认领。对于捕获设备而言,传出缓冲区内是新的帧数据;对于输出设备而言,这些缓冲区是空的。

  3. 不在上述两个队列里。在这种状态时,缓冲区由用户空间拥有,驱动无法访问。这是应用可对缓冲区进行操作的唯一时间。我们称其为用户空间状态。

将这些状态和他们之间传输的操作放在一起,如下图所示:

其实缓冲区的三种状态是以驱动为中心的,可以理解为传入队列为要给驱动处理的缓冲区;传出队列为驱动处理完毕的缓冲区;而第三种状态是脱离驱动控制的缓冲区。

他们操作的缓冲区核心就是这个 v4l2_buffer 结构体,它在 video_dev2.h 中定义:

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;struct v4l2_plane *planes;} m;__u32            length;__u32            input;__u32            reserved;
};

结构体分析:

  • index:是鉴别缓冲区的序号,它只在内存映射缓冲区中使用。与其他可以在 V4L2 接口中枚举的对象一样,内存映射缓冲区的 index 从 0 开始,一次递增。
  • type:描述的是缓冲区类型,通常是 V4L2_BUF_TYPE_VIDEO_CAPTURE 或 V4L2_BUF_TYPE_VIDEO_OUTPUT。它是一个枚举值,具体可以自己查看。

缓冲区的大小由 length 给定,单位为 byte。缓冲区中的图像数据大小可以在 bytesused 成员中找到。显然,bytesused <= length。对于捕获设备而言,驱动会设置 bytesused;对输出设备而言,应用必须设置这个成员。

  • flags:代表缓冲区的状态,如下所示:
/* Flags for 'flags' field */
#define V4L2_BUF_FLAG_MAPPED    0x0001 /* 缓冲区己映射到用户空间。它只应用于内存映射缓冲区 */
#define V4L2_BUF_FLAG_QUEUED    0x0002    /* 缓冲区在驱动的传入队列。 */
#define V4L2_BUF_FLAG_DONE    0x0004    /* 缓冲区在驱动的传出队列 */
#define V4L2_BUF_FLAG_KEYFRAME    0x0008    /* 缓冲区包含一个关键帧,它在压缩流中是                                    * 非常有用的。 */
#define V4L2_BUF_FLAG_PFRAME    0x0010    /* 应用于压缩流中,代表的是预测帧 */
#define V4L2_BUF_FLAG_BFRAME    0x0020    /* 应用于压缩流中,代表的是差分帧 *//* Buffer is ready, but the data contained within is corrupted. */
#define V4L2_BUF_FLAG_ERROR    0x0040
#define V4L2_BUF_FLAG_TIMECODE    0x0100    /* timecode 字段有效 */
#define V4L2_BUF_FLAG_INPUT 0x0200 /* input 字段有效 */
#define V4L2_BUF_FLAG_PREPARED    0x0400    /* 缓冲区准备好进入队列 */
  • field:描述存在缓冲区的图像属于哪一个域,它也是一个枚举值。
  • timestamp(时间戳):对于输入设备来说,代表帧捕获时间。对输出设备来说,在没有到达时间戳所代表的时间前,驱动不可以把帧发送出去,时间戳为 0 代表越快越好。驱动会把时间戳设定为帧的第一个字节传送到设备的时间,或者说是驱动所能达到的最接近的时间。
  • timecode:可以用来存放时间编码,对于视频编辑类应用是非常有用的。
  • sequence:驱动对传过设备的帧维护了一个递增的计数;每一帧传送时,它都会在 sequence 成员中存入当前序号。对于输入设备来讲,应用可以官场这一成员来检测帧。
  • memory:表示缓冲是内存映射缓冲区还是用户空间缓冲区。如果是 V4L2_MEMORY_MMAP 方式,m.offset 是内核空间图像数据存放的开始地址,会传递给 mmap 函数作为一个偏移,通过 mmap 映射返回一个缓冲区指针 p,p + bytesused 是图像数据在进程的虚拟地址空间所占区域;如果是用户指针缓冲区的方式,可以获取的图像数据开始地址的指针 m.userptr,userptr 是一个用户空间的指针,userptr + bytesused 便是所占的虚拟地址空间,应用可以直接访问。
  • input:可以用来快速切换捕获设备的输入——如果设备支持帧间的快速切换。

以下几个函数是参考 Tekkaman Ninja 整理的 V4L2 驱动编写指南写的,在实际中对于这几个宏的 ioctl 回调函数已经转移 videobuf2-core.c 里面,通过调用 vb2_xxx 函数来实现了,但是它对于缓冲区的操作过程还是非常重要的,同时希望能够了解更多的底层知识,为后面写 videobuf2 做准备。

2)、申请缓冲区

当流应用已经完成了基本设置,它将转去执行组织 I/O 缓冲区的任务。第一步就是使用 VIDIOC_REQBUFS ioctl() 来建立一组缓冲区,它由 V4L2 转换成对驱动 vidioc_reqbufs() 方法的调用。

int (*vidioc_reqbufs)(struct file *file, void *priv, struct v4l2_requestbuffers *buf);

其中 v4l2_requestbuffers 结构体在 videodev2.h 中定义,如下所示:

struct v4l2_requestbuffers {__u32            count;enum v4l2_buf_type type;enum v4l2_memory memory;__u32            reserved[2];
};

结构体分析:

  • count:是期望使用的缓冲区数目。
  • type:它是一个枚举值,部分如下所示:
enum v4l2_buf_type {V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,V4L2_BUF_TYPE_VBI_CAPTURE = 4,...........
};
  • memory:它也是一个枚举值,如下所示:
enum v4l2_memory {V4L2_MEMORY_MMAP = 1,V4L2_MEMORY_USERPTR = 2,V4L2_MEMORY_OVERLAY = 3,
};

如果应用想要使用内存映射缓冲区,它会把 memory 字段置为 V4L2_MEMROY_MMAP,count 置为期望使用的缓冲区数。如果驱动不支持内存映射,它应该返回 -EINVAL。否则它将在内部开辟请求的缓冲区并返回 0。返回后,应用就会认为缓冲区是存在的,所以任何可能失败的操作都应在这个阶段处理(比如说内存申请)。

注意:驱动并不一定要开辟与请求数目一样的缓冲区。在多数情况下,只有最小缓冲区是有意义的。

如果应用请求的比这个最小值小,它可能得到比实际申请的多一些。

应用可以通过设置 count 字段为 0 的方式起来释放掉所有已存在的缓冲区。在这种情况下,驱动必须在释放缓冲前停止所有的 DMA 操作,否则会发生非常严重问题。如果缓冲区已映射到用户空间,则释放缓冲区是不可能的。

相反,如果是用户空间缓冲区,则有意义的字段只有缓冲区的 type 以及仅 V4L2_MEMROY_USERPTR 这个可用的 memory 字段。应用无须指定它想要的缓冲区的数目。因为内存是在用户空间开辟的,驱动无须操心。如果驱动支持用户空间缓冲区,它只需注意应用会使用这一特性,返回 0 就可以了,否则返回 -EINVAL。

VIDIOC_REQBUFS 命令是应用得知驱动所支持的流 I/O 缓冲区类型的唯一方法

3)、将缓冲区映射到用户空间

如果使用用户空间缓存,在应用向传入队列放置缓冲区之前,驱动看不到任何缓冲区的相关调用。内存映射缓冲区需要更多的设置。应用通常会查看每一个开辟的缓冲区,并将其映射到自己的地址空间。

第一步是 VIDIOC_QUERYBUF 命令,它将转换成驱动中的 vidioc_querybuf() 方法:

int (*vidioc_querybuf)(struct file *file, void *priv, struct v4l2_buffer *buf);

进入此方法时,buf 字段中要设置的字段有 type(在缓冲区开辟是,它将被检车是否与给定的类型相符)和 index,它们可以确定一个特定的缓冲区。驱动要保证 index 有意义,并填充 buf 中的其余字段。通常来说,驱动内部存储着一个 v4l2_buffer 结构体数组,所以 vidioc_querybuf() 方法的核心只是对这个 v4l2_buffer 结构体进行赋值。

应用访问内存映射缓冲区的唯一方法就是将其映射到它们自己的地址空间,所以 vidioc_querybuf() 调用后面通常会跟着一个驱动的 mmap() 方法——要记住,这个方法指针是存储在相关设备的 video_device 结构体中 fops 字段中的。设备如何处理 mmap() 依赖于内核中缓冲区是如何设置的。

当用户空间映射缓冲区时,驱动应在相关的 v4l2_buffer 结构体中调置 V4L2_BUF_FLAG_MAPPED 标签。它也必须在 open() 和 close() 中设定 VMA 操作,这样它才能跟踪映射了缓冲区的进程数。只要缓冲区在任何地方被映射了,它就不能在内核中释放。如果一个或多个缓冲区的映射计数为 0,驱动就应该停止正在进行的 I/O 操作,因为没有进程需要它。

4)、流 IO

到现在位置,我们已经看了很多设置,却没有传输过一帧的数据,我们离这步已经很近了,但在此之前还有一个步骤要做。当应用通过 VIDIOC_REQUBUFS 获得了缓冲区后,那个缓冲区处于用户空间状态。如果他们是用户空间缓冲区,他们甚至还不存在。在应用开始流 I/O 之前,它必须至少将一个缓冲区放到驱动传入队列中。对于输出设备,那些缓冲区当然还要先填完有效的视频帧数据。

要把一个缓冲区放进出入队列,应用首先要发出一个 VIDIOC_QBUF ioctl() 调用,V4L2 会将其映射为对驱动 vidioc_qbuf() 方法的调用。

int (*vidioc_qbuf)(struct file *file ,void *priv, struct v4l2_buffer *buf);

对于内存映射缓冲而言,还是只有 buf 的 type 和 index 成员有效。驱动只能进行一些显示的检查(type 和 index 是否有效、缓冲区是否在驱动队列中、缓冲区已映射等),把缓冲区放进传入队列里(设置 V4L2_BUF_FLAG_QUEUED 标签),并返回。

一旦流 I/O 开始,驱动就要从它的传入队列里获取缓冲区,让设备更快地实现转送请求,然后把缓冲区移动到传出队列。传输开始时,缓冲区标签也要相应调整。像序号和时间戳这样的字段必需在这个时候填充。最后,应用会在传出队列中认领缓冲区,让它变回用户态

int (*vidioc_dqbuf)(struct file *file,void *priv, struct v4l2_buffer *buf);

这里,驱动会从传出队列中移除第一个缓冲区,把相关的信息存入 buf。通常,传出队列是空的,这个调用会处于阻塞状态知道有缓冲区可用。然而 V4L2 是用来处理非阻塞 I/O 的,所以如果视频设备是以 O_NONBLOCK 方式打开的,在队列为空的情况下驱动就该返回 -EAGAIN。当然,这个要求也暗示驱动必须为流 I/O 支持 poll()。

5)、打开/关闭流

剩下最后的一个步骤实际上就是告诉设备开始流 I/O 操作。完成这个任务的 Video4Linux2 驱动方法是:

int (*vidioc_streamon)(struct file *file, void *fh, enum v4l2_buf_type i);
int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);

对 vidioc_streamon() 的调用应该在检查类型有意义之后才让设备开始工作。如果需要的话,驱动可以请求等传入队列中有一定数目的缓冲区后再开始流传输。

当应用关闭是,它应发出一个 vidioc_streamoff() 调用,此调用要停止设备。驱动还应从传入和传出队列中移除所有的缓冲区,使它们都处于用户空间状态。当然,驱动必须意识到:应用可能在没有停止流传输的情况下关闭设备。

4.2、控制

4.3、Linux 内核 v4l2 框架中 videobuf2 分析

4.3.1、概述

首先在 vivi.c 中,在 vivi_init 函数的 vivi_create_instance 中,对于缓冲区队列的操作有以下的代码:

    struct vb2_queue *q;/* initialize queue */q = &dev->vb_vidq;memset(q, 0, sizeof(dev->vb_vidq));q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;q->drv_priv = dev;q->buf_struct_size = sizeof(struct vivi_buffer);q->ops = &vivi_video_qops;q->mem_ops = &vb2_vmalloc_memops;vb2_queue_init(q);

它对 struct vb2_queue 结构体进行了设置,然后调用 vb2_queue_init() 函数进行初始化,那么就先来分析这个 vb2_queue 结构体。

4.3.2、struct vb2_queue

vb2_queue 结构体在 videobuf2-core.h 中定义,如下所示(对于结构体的注释,我也放在下面了):

/*** struct vb2_queue - a videobuf queue** @type:    queue type (see V4L2_BUF_TYPE_* in linux/videodev2.h* @io_modes:    supported io methods (see vb2_io_modes enum)* @io_flags:    additional io flags (see vb2_fileio_flags enum)* @ops:    driver-specific callbacks* @mem_ops:    memory allocator specific callbacks* @drv_priv:    driver private data* @buf_struct_size: size of the driver-specific buffer structure;*        "0" indicates the driver doesn't want to use a custom buffer*        structure type, so sizeof(struct vb2_buffer) will is used** @memory:    current memory type used* @bufs:    videobuf buffer structures* @num_buffers: number of allocated/used buffers* @queued_list: list of buffers currently queued from userspace* @queued_count: number of buffers owned by the driver* @done_list:    list of buffers ready to be dequeued to userspace* @done_lock:    lock to protect done_list list* @done_wq: waitqueue for processes waiting for buffers ready to be dequeued* @alloc_ctx:    memory type/allocator-specific contexts for each plane* @streaming:    current streaming state* @fileio:    file io emulator internal data, used only if emulator is active*/
struct vb2_queue {enum v4l2_buf_type        type;unsigned int            io_modes;unsigned int            io_flags;const struct vb2_ops        *ops;const struct vb2_mem_ops    *mem_ops;void                *drv_priv;unsigned int            buf_struct_size;
/* private: internal use only */enum v4l2_memory        memory;struct vb2_buffer        *bufs[VIDEO_MAX_FRAME]; //代表每个buffer (后面分析)unsigned int            num_buffers; //分配的buffer个数struct list_head        queued_list;atomic_t            queued_count;struct list_head        done_list;spinlock_t            done_lock;wait_queue_head_t        done_wq;void                *alloc_ctx[VIDEO_MAX_PLANES];unsigned int            plane_sizes[VIDEO_MAX_PLANES];unsigned int            streaming:1;struct vb2_fileio_data        *fileio;
};

结构体分析:

  • type:缓冲区类型,与 v4l2_buf_type 这个枚举值相同,为下面 其中的一项:
enum v4l2_buf_type {V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,V4L2_BUF_TYPE_VBI_CAPTURE = 4,V4L2_BUF_TYPE_VBI_OUTPUT = 5,V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,V4L2_BUF_TYPE_PRIVATE = 0x80,
};
  • io_modes:访问 IO 的方式,与 enum vb2_io_modes 相同,如下所示:
*** enum vb2_io_modes - queue access methods* @VB2_MMAP:        driver supports MMAP with streaming API* @VB2_USERPTR:    driver supports USERPTR with streaming API* @VB2_READ:        driver supports read() style access* @VB2_WRITE:        driver supports write() style access*/
enum vb2_io_modes {VB2_MMAP    = (1 << 0),VB2_USERPTR    = (1 << 1),VB2_READ    = (1 << 2),VB2_WRITE    = (1 << 3),
};
  • Ops:buffer 队列操作函数集合;
  • mem_ops:buffer memory 操作集合;

vb2_queue 代表一个 videobuffer 队列,vb2_buffer 是这个队列中的成员,vb2_mem_ops 是缓冲内存的操作函数集,vb2_ops 用来管理队列。根据 vivi.c 中的设置,它都对这两个函数集设置了初始值,那么下面我们就来看看这两个函数集。

1)、vb2_mem_ops 包含了内存映射缓冲区、用户空间缓冲区的内存操作方法:

struct vb2_mem_ops {void        *(*alloc)(void *alloc_ctx, unsigned long size);void        (*put)(void *buf_priv);void        *(*get_userptr)(void *alloc_ctx, unsigned long vaddr,unsigned long size, int write);void        (*put_userptr)(void *buf_priv);void        *(*vaddr)(void *buf_priv);void        *(*cookie)(void *buf_priv);unsigned int    (*num_users)(void *buf_priv);int        (*mmap)(void *buf_priv, struct vm_area_struct *vma);
};

通过在 vivi.c 中使用这个结构体,我们发现这几个函数都是直接调用内核中实现好的函数。它提供了三种类型的视频缓冲区操作方法:连续的 DMA 缓冲区、集散的 DMA 缓冲区以及 vmalloc 创建的缓冲区,分别由 videobuf2-dma-config.c、videobuf2-dma-sg.c 和 videobuf-vmalloc.c 文件实现,可以根据实际情况来使用。

vivi.c 中,q->mem_ops = &vb2_vmalloc_memops 它使用 vmalloc 的方式创建缓冲区,搜索发现 vb2_vmalloc_memops 在 videobuf2-vmalloc.c 中定义,如下所示:

const struct vb2_mem_ops vb2_vmalloc_memops = {.alloc        = vb2_vmalloc_alloc,.put        = vb2_vmalloc_put,.get_userptr    = vb2_vmalloc_get_userptr,.put_userptr    = vb2_vmalloc_put_userptr,.vaddr        = vb2_vmalloc_vaddr,.mmap        = vb2_vmalloc_mmap,.num_users    = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);

这几个函数暂时先不具体分析他们的代码。

2)、vb2_ops 是用来管理 buffer 队列的函数集合,包括队列和缓冲区初始化:

struct vb2_ops {int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,unsigned int *num_buffers, unsigned int *num_planes,unsigned int sizes[], void *alloc_ctxs[]);void (*wait_prepare)(struct vb2_queue *q);void (*wait_finish)(struct vb2_queue *q);int (*buf_init)(struct vb2_buffer *vb);int (*buf_prepare)(struct vb2_buffer *vb);int (*buf_finish)(struct vb2_buffer *vb);void (*buf_cleanup)(struct vb2_buffer *vb);int (*start_streaming)(struct vb2_queue *q, unsigned int count);int (*stop_streaming)(struct vb2_queue *q);void (*buf_queue)(struct vb2_buffer *vb);
};

vivi.c 中 q->ops = &vivi_video_qops 这几个函数是需要我们自己实现的,如下所示:

static struct vb2_ops vivi_video_qops = {.queue_setup        = queue_setup, //队列初始化//对buffer的操作.buf_init        = buffer_init,.buf_prepare        = buffer_prepare,.buf_finish        = buffer_finish,.buf_cleanup        = buffer_cleanup,//把vb传递给驱动.buf_queue        = buffer_queue,// 开始/停止视频流.start_streaming    = start_streaming,.stop_streaming        = stop_streaming,//释放和获取设备操作锁.wait_prepare        = vivi_unlock,.wait_finish        = vivi_lock,
};

4.3.3、struct vb2_buffer

在 vb2_queue 中 有一个 struct vb2_buffer *bufs[VIDEO_MAX_FRAME];其中这个 vb2_buffer 是缓存队列的基本单位,内嵌在其中的 v4l2_buffer 是核心成员。当开始流 IO 时,帧以 v4l2_buffer 的格式在应用和驱动之前传输。

struct vb2_buffer {struct v4l2_buffer v4l2_buf;struct v4l2_plane v4l2_planes[VIDEO_MAX_FRAME];struct vb2_queue *vb2_queue;unsigned int num_planes;/* Private: internal use only */enum vb2_buffer_state state;struct list_head queued_entry;struct list_head done_entry;struct vb2_plane planes[VIDEO_MAX_PALNES];
};

一个缓冲区有三种状态,我们在上面的 V4L2 框架分析中:流 IO 方式 一节分析了这三种状态:

  1. 在驱动的传入队列中,驱动程序将会对此队列中的缓冲区进行处理,用户空间通过 IOCTL:VIDIOC_QBUF 把缓冲区放入到队列。对于一个视频捕获设备,出入队列中的缓冲区是空的,驱动会往其中填充数据;
  2. 在驱动的传出队列中,这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领;
  3. 用户空间状态的队列中,已经通过 IOCTL:VIDIOC_DQBUF 传出到用户空间的缓冲区,此时缓冲区由用户空间拥有,驱动无法访问。

当用户空间拿到 v4l2_buffer,可以获取到缓冲区的相关信息。byteused 是图像数据所占的字节数,如果是 V4L2_MEMORY_MMAP 方式,m.offset 是内核空间图像数据存放的开始地址,会传递给 mmap 函数作为一个偏移,通过 mmap 映射返回一个缓冲区指针 p,p + byteused 是图像数据在进程的虚拟地址空间所占区域;如果是用户指针缓冲区的方式,可以获取的图像数据开始地址的指针 m.userptr,userptr 是一个用户空间的指针,userptr + byteused 便是所占的虚拟地址空间,应用可以直接访问。

4.3.4、下面来结合代码详细分析分析它的流程:

1)、当应用程序调用 ioctl:VIDIOC_REQBUFS 的时候,应用程序中一般是这样调用的:

struct v4l2_requestbuffers req;
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if(-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
{... ...
}

经过 v4l2 框架的一系列转换,最终调用到 vivi.c 驱动中我们自己实现的 vidioc_reqbufs 函数中:

static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
{struct vivi_dev *dev = video_drvdata(file);return vb2_reqbufs(&dev->vb_vidq, p);
}

它又调用了 vb2_reqbufs 这个函数,它就在 videobuf2-core.c 这个文件中,如下所示:

/*** vb2_reqbufs() - Initiate streaming* @q:        videobuf2 queue* @req:    struct passed from userspace to vidioc_reqbufs handler in driver** Should be called from vidioc_reqbufs ioctl handler of a driver.* This function:* 1) verifies streaming parameters passed from the userspace,* 2) sets up the queue,* 3) negotiates number of buffers and planes per buffer with the driver* to be used during streaming,* 4) allocates internal buffer structures (struct vb2_buffer), according to* the agreed parameters,* 5) for MMAP memory type, allocates actual video memory, using the* memory handling/allocation routines provided during queue initialization** If req->count is 0, all the memory will be freed instead.* If the queue has been allocated previously (by a previous vb2_reqbufs) call* and the queue is not busy, memory will be reallocated.** The return values from this function are intended to be directly returned* from vidioc_reqbufs handler in driver.*/
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{unsigned int num_buffers, allocated_buffers, num_planes = 0;int ret = 0;if (q->fileio) {dprintk(1, "reqbufs: file io in progress\n");return -EBUSY;}
/* 这个fileio是vb2_queue结构体中的一个成员,它是vb2_fileio_data类型的,它的意思大概是关于读写上下文之间的一个读写锁,应该用于8.5中IO访问中的read,write基本帧IO访问方式,这个我不是太懂,一般情况下为0。*/if (req->memory != V4L2_MEMORY_MMAP&& req->memory != V4L2_MEMORY_USERPTR) {dprintk(1, "reqbufs: unsupported memory type\n");return -EINVAL;}
/* 判断req中的memory字段,必须设置了V4L2_MEMORY_MMAP 和V4L2_MEMORY_USERPTR这两个字段,再看之前的应用程序设置中,它没有设置V4L2_MEMORY_USERPTR字段,可能是错误的,我们先分析,等到后面再测试这个应用程序。 */if (req->type != q->type) {dprintk(1, "reqbufs: requested type is incorrect\n");return -EINVAL;}
/* 判断想要申请的v4l2_requestbuffers与vb2_queue中type字段是否相同。不同就返回错误。*/if (q->streaming) {dprintk(1, "reqbufs: streaming active\n");return -EBUSY;}
/* streaming表示当前流状态,为1的话表示streaming active,返回 EBUSY *//** Make sure all the required memory ops for given memory type* are available.*/if (req->memory == V4L2_MEMORY_MMAP && __verify_mmap_ops(q)) {dprintk(1, "reqbufs: MMAP for current setup unsupported\n");return -EINVAL;}
/* 如果v4l2_requestbuffers中memory字段为V4L2_MEMORY_MMAP的话,判断 vb2_queue q队列中io_modes 是否为VB2_MMAP,判断是否提供了vb2_mem_ops操作函数集中的alloc,put,mmap函数,如果这些不符合的话,就返回EINVAL。 __verify_mmap_ops()函数也在videobuf2-core.c文件中提供,如下所示:
static int __verify_mmap_ops(struct vb2_queue *q)
{if (!(q->io_modes & VB2_MMAP) || !q->mem_ops->alloc ||!q->mem_ops->put || !q->mem_ops->mmap)return -EINVAL;return 0;
}
*/if (req->memory == V4L2_MEMORY_USERPTR && __verify_userptr_ops(q)) {dprintk(1, "reqbufs: USERPTR for current setup unsupported\n");return -EINVAL;}
/* 与上面的类似,__verify_userptr_ops(q)函数如下所示:
static int __verify_userptr_ops(struct vb2_queue *q)
{if (!(q->io_modes & VB2_USERPTR) || !q->mem_ops->get_userptr ||!q->mem_ops->put_userptr)return -EINVAL;return 0;
}*/if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {/** We already have buffers allocated, so first check if they* are not in use and can be freed.*/if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {dprintk(1, "reqbufs: memory in use, cannot free\n");return -EBUSY;}__vb2_queue_free(q, q->num_buffers);/** In case of REQBUFS(0) return immediately without calling* driver's queue_setup() callback and allocating resources.*/if (req->count == 0)return 0;}
/* 如果申请的v4l2_requestbuffers中count字段为0的话,就释放掉所有已经申请的内存,因为是允许应用程序这样使用ioctl的。如果 q->num_buffers != 0的话,同样释放掉已经申请的内存。因为,应用程序想要使用缓冲区的话,调用的第一个ioctl就是这个VIDIOC_REQBUFS,在它之前是没有申请内存的。然后调用__vb2_queue_free释放掉缓冲区。这个函数暂时先不具体分析代码。
如果 req->count == 0的话,意思是应用程序调用的VIDIOC_REQBUFS(0),这个函数就可以直接在这返回了。 *//** Make sure the requested values and current defaults are sane.*/num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);memset(q->plane_sizes, 0, sizeof(q->plane_sizes));memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));q->memory = req->memory;
/* 经过上面的判断语句以后,开始申请缓冲区的操作。首先将num_buffers设置为 req->count和 VIDEO_MAX_FRAME中较小的一个,然后将 q->plane_sizes和 q->alloc_ctx清零,将 q->memory和 req->memory设置成一致的形式。 *//** Ask the driver how many buffers and planes per buffer it requires.* Driver also sets the size and allocator context for each plane.*/ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,q->plane_sizes, q->alloc_ctx);if (ret)return ret;
/* 看注释应该可以明白,问问驱动一共需要申请几个buffers,并且每一个buffer的位面是多少。于是乎,就调用call_qop去问了,至于这个call_qop是怎么回事,其实在上面的几个函数里面有调用,可是我们没有分析,现在在这分析一下:
#define call_qop(q, op, args...) (((q)->ops->op) ? ((q)->ops->op(args)) : 0)
看这个宏定义,意思就是调用vb2_queue->vb2_ops->queue_setup函数,这个函数就是需要驱动实现的一个函数。在vivi.c中是这样的:
static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,unsigned int *nbuffers, unsigned int *nplanes,unsigned int sizes[], void *alloc_ctxs[])
{struct vivi_dev *dev = vb2_get_drv_priv(vq);unsigned long size;size = dev->width * dev->height * 2;if (0 == *nbuffers)*nbuffers = 32;while (size * *nbuffers > vid_limit * 1024 * 1024)(*nbuffers)--;*nplanes = 1;sizes[0] = size;//*//* videobuf2-vmalloc allocator is context-less so no need to set//* alloc_ctxs array.//*dprintk(dev, 1, "%s, count=%d, size=%ld\n", __func__,*nbuffers, size);return 0;
}
queue_setup 函数的目的是根据vb2_queue 中的 alloc_ctx和plane_sizes来填充 num_buffers和 num_planes,这两个变量以供后面使用。在来看vb2_queue结构体中这三个数组变量:
struct vb2_buffer        *bufs[VIDEO_MAX_FRAME];
void                *alloc_ctx[VIDEO_MAX_PLANES];
unsigned int        plane_sizes[VIDEO_MAX_PLANES];
bufs数组中每一项对应一个申请的buffer, plane_sizes数组中每一项代表相应buffer的位面大小, alloc_ctx数组的每一项代表相应buffer位面大小的memory type/allocator-specific。这三个数组中的每一项是一一对应关系。*//* Finally, allocate buffers and video memory */ret = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);if (ret == 0) {dprintk(1, "Memory allocation failed\n");return -ENOMEM;}
/* 最后,调用__vb2_queue_alloc函数来分配缓冲区和内存,这个函数返回值是成功分配的buffer个数。__vb2_queue_alloc函数如下所示:
static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory,unsigned int num_buffers, unsigned int num_planes)
{unsigned int buffer;struct vb2_buffer *vb;int ret;for (buffer = 0; buffer < num_buffers; ++buffer) {/* Allocate videobuf buffer structures */vb = kzalloc(q->buf_struct_size, GFP_KERNEL);if (!vb) {dprintk(1, "Memory alloc for buffer struct failed\n");break;}
/* 用kzalloc为结构体分配q->buf_struct_size大小的一块区域。 *//* Length stores number of planes for multiplanar buffers */if (V4L2_TYPE_IS_MULTIPLANAR(q->type))vb->v4l2_buf.length = num_planes;
/* 判断这个q的type类型是不是V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE和V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,在这里我们不是。 */vb->state = VB2_BUF_STATE_DEQUEUED;vb->vb2_queue = q;vb->num_planes = num_planes;vb->v4l2_buf.index = q->num_buffers + buffer;vb->v4l2_buf.type = q->type;vb->v4l2_buf.memory = memory;/* 设置vb2_buffer的一些属性,在前面说了,vb2_buffer是帧传输的单元,首先设置它的state状态,这个state状态很重要,它有以下几种类型,我连注释也一起复制在这里了,因为注释写的很清楚:/*** enum vb2_buffer_state - current video buffer state* @VB2_BUF_STATE_DEQUEUED:    buffer under userspace control* @VB2_BUF_STATE_PREPARED:    buffer prepared in videobuf and by the driver* @VB2_BUF_STATE_QUEUED:    buffer queued in videobuf, but not in driver* @VB2_BUF_STATE_ACTIVE:    buffer queued in driver and possibly used*                in a hardware operation* @VB2_BUF_STATE_DONE:    buffer returned from driver to                         videobuf, but not yet dequeued to userspace* @VB2_BUF_STATE_ERROR:    same as above, but the operation on the buffer*                has ended with an error, which will be reported*                to the userspace when it is dequeued*/
enum vb2_buffer_state {VB2_BUF_STATE_DEQUEUED,VB2_BUF_STATE_PREPARED,VB2_BUF_STATE_QUEUED,VB2_BUF_STATE_ACTIVE,VB2_BUF_STATE_DONE,VB2_BUF_STATE_ERROR,
};
可以根据缓冲区的三种状态来思考这几种状态,现在首先是reqbuf,现在缓冲区肯定是在用户空间,所以它的状态为 VB2_BUF_STATE_DEQUEUED,然后设置vb2_buffer的父队列为q,设置 num_planes,然后设置v4l2_buffer里面的index,type和memeory。
*//* Allocate video buffer memory for the MMAP type */if (memory == V4L2_MEMORY_MMAP) {ret = __vb2_buf_mem_alloc(vb);if (ret) {dprintk(1, "Failed allocating memory for ""buffer %d\n", buffer);kfree(vb);break;}
/* 调用__vb2_buf_mem_alloc函数,为video buffer分配空间。__vb2_buf_mem_alloc函数里面又调用了
call_memop(q, alloc, q->alloc_ctx[plane], q->plane_sizes[plane]);
来分配空间,但是为了文章的可读性,暂时先不分析。
*//** Call the driver-provided buffer initialization* callback, if given. An error in initialization* results in queue setup failure.*/ret = call_qop(q, buf_init, vb);if (ret) {dprintk(1, "Buffer %d %p initialization"" failed\n", buffer, vb);__vb2_buf_mem_free(vb);kfree(vb);break;}}
/* 调用 vb2_queue->ops->buf_init函数,即调用到vivi.c中的 buffer_init函数。*/q->bufs[q->num_buffers + buffer] = vb;}__setup_offsets(q, buffer);dprintk(1, "Allocated %d buffers, %d plane(s) each\n",buffer, num_planes);return buffer;
}*/allocated_buffers = ret;
/* allocated_buffers表示分配成功的缓冲区个数。 *//** Check if driver can handle the allocated number of buffers.*/if (allocated_buffers < num_buffers) {num_buffers = allocated_buffers;
/* 想要申请num_buffers个缓冲区,但是不一定分配那么多,成功的个数为allocated_buffers。 */ret = call_qop(q, queue_setup, q, NULL, &num_buffers,&num_planes, q->plane_sizes, q->alloc_ctx);
/* 再次调用 vb2_queue->ops->queue_setup函数 */if (!ret && allocated_buffers < num_buffers)ret = -ENOMEM;/** Either the driver has accepted a smaller number of buffers,* or .queue_setup() returned an error*/}q->num_buffers = allocated_buffers;
/* 根据成功分配的buffer个数来修改 vb2_queue里面保存的buffer个数。 */if (ret < 0) {__vb2_queue_free(q, allocated_buffers);return ret;}/** Return the number of successfully allocated buffers* to the userspace.*/req->count = allocated_buffers;return 0;
}
EXPORT_SYMBOL_GPL(vb2_reqbufs);

2)、当应用程序调用 ioctl:VIDIOC_QUERYBUFS 的时候,应用程序中一般是这样调用的:

for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {struct v4l2_buffer buf;buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = n_buffers;if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))errno_exit ("VIDIOC_QUERYBUF");buffers[n_buffers].length = buf.length;buffers[n_buffers].start = mmap (NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd, buf.m.offset);    //(没有分析这个mmap函数,以后补上)
}

经过 v4l2 框架的一系列转换,最终调用到 vivi.c 驱动中我们自己实现的 vidioc_querybuf 函数中:

static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{struct vivi_dev *dev = video_drvdata(file);return vb2_querybuf(&dev->vb_vidq, p);
}

它又调用了 vb2_querybuf 这个函数,它就在 videobuf2-core.c 这个文件中,如下所示:

/*** vb2_querybuf() - query video buffer information* @q:        videobuf queue* @b:        buffer struct passed from userspace to vidioc_querybuf handler*            in driver** Should be called from vidioc_querybuf ioctl handler in driver.* This function will verify the passed v4l2_buffer structure and fill the* relevant information for the userspace.** The return values from this function are intended to be directly returned* from vidioc_querybuf handler in driver.*/
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{struct vb2_buffer *vb;if (b->type != q->type) {dprintk(1, "querybuf: wrong buffer type\n");return -EINVAL;}if (b->index >= q->num_buffers) {dprintk(1, "querybuf: buffer index out of range\n");return -EINVAL;}vb = q->bufs[b->index];return __fill_v4l2_buffer(vb, b);
}
EXPORT_SYMBOL(vb2_querybuf);

这个函数先判断一些条件,之后将 q->bufs[b->index] 赋给 vb2_buffer *vb,此时 vb 的值是队列 q 中 bufs 中的某一项(具体哪一项,看应用程序的循环走到哪一步了),之后调用 __fill_v4l2_buffer 函数,将 bufs 里面的这一项的内容拷贝到 v4l2_buffer b 中,这个 __fill_v4l2_buffer() 函数就是讲怎么拷贝的,它在 videobuf2-core.c 中定义:

/*** __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be* returned to userspace*/
static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
{struct vb2_queue *q = vb->vb2_queue;int ret;
/* 从 vb2_buffer vb里面获取它的父队列。 *//* Copy back data such as timestamp, flags, input, etc. */memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));b->input = vb->v4l2_buf.input;b->reserved = vb->v4l2_buf.reserved;
/* 设置 v4l2_buffer b中的timestamp, flags, input,reserved等字段 */if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) {ret = __verify_planes_array(vb, b);if (ret)return ret;/** Fill in plane-related data if userspace provided an array* for it. The memory and size is verified above.*/memcpy(b->m.planes, vb->v4l2_planes,b->length * sizeof(struct v4l2_plane));} else {/** We use length and offset in v4l2_planes array even for* single-planar buffers, but userspace does not.*/b->length = vb->v4l2_planes[0].length;b->bytesused = vb->v4l2_planes[0].bytesused;if (q->memory == V4L2_MEMORY_MMAP)b->m.offset = vb->v4l2_planes[0].m.mem_offset;else if (q->memory == V4L2_MEMORY_USERPTR)b->m.userptr = vb->v4l2_planes[0].m.userptr;/* 设置v4l2_buffer b中的length,bytesused和m.offset/m.userptr字段。 */}/** Clear any buffer state related flags.*/b->flags &= ~V4L2_BUFFER_STATE_FLAGS;
/* 将v4l2_buffer b中的flags参数全部都清空。 */switch (vb->state) {case VB2_BUF_STATE_QUEUED:case VB2_BUF_STATE_ACTIVE:b->flags |= V4L2_BUF_FLAG_QUEUED;break;case VB2_BUF_STATE_ERROR:b->flags |= V4L2_BUF_FLAG_ERROR;/* fall through */case VB2_BUF_STATE_DONE:b->flags |= V4L2_BUF_FLAG_DONE;break;case VB2_BUF_STATE_PREPARED:b->flags |= V4L2_BUF_FLAG_PREPARED;break;case VB2_BUF_STATE_DEQUEUED:/* nothing */break;}
/* 根据vb2_buffer vb的flags参数来设置v4l2_buffer b的flags参数。 */if (__buffer_in_use(q, vb))b->flags |= V4L2_BUF_FLAG_MAPPED;return 0;
}

 3)、mmap

扩展:

应用访问内存映射缓冲区的唯一方法就是将其映射到它们自己的地址空间,所以 vidioc_querybuf() 调用后面通常会跟着一个驱动的 mmap() 方法 —— 要记住,这个方法指针是存储在相关设备的 video_device 结构体中的 fops 字段中的 ( .mmap )。设备如何处理 mmap() 依赖于内核缓冲区是如何设置的。

4)、当应用程序调用 ioctl:VIDIOC_QBUFS 的时候,应用程序中一般是这样调用的:

static void start_capturing(void)
{unsigned int i;enum v4l2_buf_type type;for(i=0; i<n_buffers; ++i){struct v4l2_buffer buf;buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;/* VIDIOC_QBUF 把数据从缓存中读取出来 */if(-1 == xioctl(fd, VIDIOC_QBUF, &buf))errno_exit("VIDIOC_QBUF");}type = V4L2_BUF_TYPE_VIDEO_CAPTURE;/* VIDIOC_STREAMON 开始视频显示函数 */if(-1 == xioctl(fd, VIDIOC_STREAMON, &type))errno_exit("VIDIOC_STREAMON");
}

经过 v4l2 框架的一系列转换,最终调用到 vivi.c 驱动中我们自己实现的 vidioc_qbuf 函数中:

static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{struct vivi_dev *dev = vidioc_drvdata(file);return vb2_qbuf(&dev->vb_vidq, p);
}

它有调用了 vb2_querybuf 这个函数,它就在 videobuf2-core.c 这个文件中,如下所示:

/*** vb2_qbuf() - Queue a buffer from userspace* @q:        videobuf2 queue* @b:        buffer structure passed from userspace to vidioc_qbuf handler*            in driver** Should be called from vidioc_qbuf ioctl handler of a driver.* This function:* 1) verifies the passed buffer,* 2) if necessary, calls buf_prepare callback in the driver (if provided), in* which driver-specific buffer initialization can be performed,* 3) if streaming is on, queues the buffer in driver by the means of buf_queue* callback for processing.** The return values from this function are intended to be directly returned* from vidioc_qbuf handler in driver.*/
int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
{struct rw_semaphore *mmap_sem = NULL;struct vb2_buffer *vb;int ret = 0;if (q->memory == V4L2_MEMORY_USERPTR) {mmap_sem = &current->mm->mmap_sem;call_qop(q, wait_prepare, q);down_read(mmap_sem);call_qop(q, wait_finish, q);}if (q->fileio) {dprintk(1, "qbuf: file io in progress\n");ret = -EBUSY;goto unlock;}if (b->type != q->type) {dprintk(1, "qbuf: invalid buffer type\n");ret = -EINVAL;goto unlock;}if (b->index >= q->num_buffers) {dprintk(1, "qbuf: buffer index out of range\n");ret = -EINVAL;goto unlock;}vb = q->bufs[b->index];if (NULL == vb) {/* Should never happen */dprintk(1, "qbuf: buffer is NULL\n");ret = -EINVAL;goto unlock;}
/* 从vb2_queue q队列的bufs中根据b->index选择一项,赋给vb。 */if (b->memory != q->memory) {dprintk(1, "qbuf: invalid memory type\n");ret = -EINVAL;goto unlock;}switch (vb->state) {case VB2_BUF_STATE_DEQUEUED:ret = __buf_prepare(vb, b);if (ret)goto unlock;case VB2_BUF_STATE_PREPARED:break;default:dprintk(1, "qbuf: buffer already in use\n");ret = -EINVAL;goto unlock;}
/* 根据不同的vb->state状态,如果为 VB2_BUF_STATE_DEQUEUED的话,就调用__buf_prepare函数,如果为 VB2_BUF_STATE_PREPARED的话,就直接break,__buf_prepare函数如下所示,他们的主要目的是:到这一步的 vb2_buffer vb该有的属性是VB2_BUF_STATE_PREPARED,如果它仅仅是VB2_BUF_STATE_DEQUEUED的话,就调用__buf_prepare函数来为它添加这一个属性,下面简单看看这个函数是不是这个意思:
static int __buf_prepare(struct vb2_buffer *vb, const struct v4l2_buffer *b)
{struct vb2_queue *q = vb->vb2_queue;int ret;switch (q->memory) {case V4L2_MEMORY_MMAP:ret = __qbuf_mmap(vb, b);break;case V4L2_MEMORY_USERPTR:ret = __qbuf_userptr(vb, b);break;default:WARN(1, "Invalid queue type\n");ret = -EINVAL;}if (!ret)ret = call_qop(q, buf_prepare, vb);if (ret)dprintk(1, "qbuf: buffer preparation failed: %d\n", ret);elsevb->state = VB2_BUF_STATE_PREPARED;return ret;
}
暂时先不分析__qbuf_mmap和__qbuf_userptr函数,看最后 vb->state = VB2_BUF_STATE_PREPARED,就是为vb2_buffer vb添加这个 VB2_BUF_STATE_PREPARED属性。
*//** Add to the queued buffers list, a buffer will stay on it until* dequeued in dqbuf.*/list_add_tail(&vb->queued_entry, &q->queued_list);vb->state = VB2_BUF_STATE_QUEUED;
/* 重要的就是这个list_add_tail链表操作了,通过这个操作,将vb2_buffer vb中的queued_entry链表头添加到vb2_queue q的queued_list中去,然后为vb2_buffer vb设置VB2_BUF_STATE_QUEUED的属性。 *//** If already streaming, give the buffer to driver for processing.* If not, the buffer will be given to driver on next streamon.*/if (q->streaming)__enqueue_in_driver(vb);/* Fill buffer information for the userspace */__fill_v4l2_buffer(vb, b);dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index);
unlock:if (mmap_sem)up_read(mmap_sem);return ret;
}
EXPORT_SYMBOL_GPL(vb2_qbuf);

5)、然后就是启动摄像头了, ioctl:VIDIOC_STREAMON,应用程序在 4) 节中包含了这一步,经过 v4l2 框架的一系列转换,最终调用到 vivi.c 驱动中我们自己实现的 vidioc_streamon 函数中:

static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{struct vivi_dev *dev = video_drvdata(file);return vb2_streamon(&dev->vb_vidq, i);
}

它有调用了 vb2_streamon 这个函数,它就在 videobuf2-core.c 这个文件中,如下所示:

/*** vb2_streamon - start streaming* @q:        videobuf2 queue* @type:    type argument passed from userspace to vidioc_streamon handler** Should be called from vidioc_streamon handler of a driver.* This function:* 1) verifies current state* 2) passes any previously queued buffers to the driver and starts streaming** The return values from this function are intended to be directly returned* from vidioc_streamon handler in the driver.*/
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{struct vb2_buffer *vb;int ret;if (q->fileio) {dprintk(1, "streamon: file io in progress\n");return -EBUSY;}if (type != q->type) {dprintk(1, "streamon: invalid stream type\n");return -EINVAL;}if (q->streaming) {dprintk(1, "streamon: already streaming\n");return -EBUSY;}/** If any buffers were queued before streamon,* we can now pass them to driver for processing.*/list_for_each_entry(vb, &q->queued_list, queued_entry)__enqueue_in_driver(vb);
/* 如果在启动stream之前,已经有buffer被放入队列的话,就调用__enqueue_in_driver函数来处理它。但是我们第一次执行到这里,就是按照先reqbuf,然后querybuf,qbuf的顺序来执行的,在执行qbuf的过程中,肯定有至少一个vb2_buffer是添加到vb2_queue中queued_list链表中的,所以肯定会执行这个__enqueue_in_driver函数。
下面来分析分析这个__enqueue_in_driver函数,它也是在这个videobuf2-core.c文件中:
static void __enqueue_in_driver(struct vb2_buffer *vb)
{struct vb2_queue *q = vb->vb2_queue;vb->state = VB2_BUF_STATE_ACTIVE;atomic_inc(&q->queued_count);q->ops->buf_queue(vb);
}
首先设置vb2_buffer的state状态为 VB2_BUF_STATE_ACTIVE,然后原子增加vb2_queue队列q的 queued_count数值,然后调用vb2_queue队列q->ops->buf_queue函数,即vivi.c中的buffer_queue函数:
static void buffer_queue(struct vb2_buffer *vb)
{struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb);struct vivi_dmaqueue *vidq = &dev->vidq;unsigned long flags = 0;dprintk(dev, 1, "%s\n", __func__);spin_lock_irqsave(&dev->slock, flags);list_add_tail(&buf->list, &vidq->active);spin_unlock_irqrestore(&dev->slock, flags);
}
它主要就是将vb2_buffer vb所在的vivi_buffer buf通过list_add_tail添加到vivi_dmaquue vidq结构体中的active链表中。
*//** Let driver notice that streaming state has been enabled.*/ret = call_qop(q, start_streaming, q, atomic_read(&q->queued_count));if (ret) {dprintk(1, "streamon: driver refused to start streaming\n");__vb2_queue_cancel(q);return ret;}/* 调用vb2_queue q中的ops->start_streaming函数,即vivi.c中的start_streaming函数:
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{struct vivi_dev *dev = vb2_get_drv_priv(vq);dprintk(dev, 1, "%s\n", __func__);return vivi_start_generating(dev);
}
它又调用了vivi.c中的vivi_start_generating函数:
static int vivi_start_generating(struct vivi_dev *dev)
{struct vivi_dmaqueue *dma_q = &dev->vidq;dprintk(dev, 1, "%s\n", __func__);/* Resets frame counters */dev->ms = 0;dev->mv_count = 0;dev->jiffies = jiffies;dma_q->frame = 0;dma_q->ini_jiffies = jiffies;dma_q->kthread = kthread_run(vivi_thread, dev, dev->v4l2_dev.name);if (IS_ERR(dma_q->kthread)) {v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");return PTR_ERR(dma_q->kthread);}/* Wakes thread */wake_up_interruptible(&dma_q->wq);dprintk(dev, 1, "returning from %s\n", __func__);return 0;
}
在这个vivi_start_generating函数中,它通过启动一个内核线程的方式,在内核线程vivi_thread中调用启动一个队列,收集到的信息后填充到队列中的第一个buf中,然后通过wake_up_interruptible来唤醒线程,这些都是vivi.c中对于收集信息所做的处理。我们现在分析的是v4l2框架的流程,知道在这进行了这些处理就行,我们后面再具体分析数据处理的过程。*/q->streaming = 1;
/* 设置vb2_queue q的streaming为1,表明已经启动了流处理。 */dprintk(3, "Streamon successful\n");return 0;
}
EXPORT_SYMBOL_GPL(vb2_streamon);

6)、分析 vivi.c 中开启 streamon 以后的数据处理过程:

上面提到一直调用到 vivi.c 中的 vivi_start_generating 函数,在这个函数中通过 dma_q->kthread = kthread_run(vivi_thread, dev, dev->v4l2_dev.name) 来启动一个 vivi_thread 的内核线程:

static int vivi_thread(void *data)
{struct vivi_dev *dev = data;dprintk(dev, 1, "thread started\n");set_freezable();for(;;) {vivi_sleep(dev);if(kthread_should_stop())break;}dprintk(dev, 1, "thread: exit\n");return 0;
}

这个线程进入一个死循环中,在这个死循环中是 vivi_sleep 函数:

static void vivi_sleep(struct vivi_dev *dev)
{struct vivi_dmaqueue *dma_q = &dev->vidq;int timeout;DECLARE_WAITQUEUE(wait, current);dprintk(dev, 1, "%s dma_q=0x%08lx\n", __func__, (unsigned long)dma_q);add_wait_queue(&dma_q->wq, &wait);if(kthread_should_stop())goto stop_task;/* Calculate time to wake up */timeout = msecs_to_jiffies(frames_to_ms(1));vivi_thread_tick(dev);schedule_timeout_interruptible(timeout);stop_task:remove_wait_queue(&dma_q->wq, &wait);try_to_freeze();
}

在这个 vivi_sleep 函数中,申请了一个等待队列:dma_q->wq,然后就进入 vivi_thread_tick(dev) 中等地队列被唤醒,vivi_thread_tick 函数如下所示:

static void vivi_thread_tick(struct vivi_dev *dev)
{struct vivi_dmaqueue *dma_q = &dev->vidq;struct vivi_buffer *buf;unsinged long flags = 0;dprintk(dev, 1, "Thread tick\n");spin_lock_irqsave(&dev->slock, flags);if(list_empty(&dma_q->active)) {dprintk(dev, 1, "No active queue to serve\n");spin_unlock_irqrestore(&dev->slock, flags);return;}buf = list_entry(dma_q->active.next, struct vivi_buffer, list);list_del(&buf->list);spin_unlock_irqrestore(&dev->slock, flags);do_gettimeofday(&buf->vb.v4l2_buf.timestamp);/* Fill buffer */vivi_fillbuf(dev, buf);dprintk(dev, 1, "filled buffer %p\n", buf);vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);dprintk(dev, 2, "[%p/%p]done\n", buf, buf->vb.v4l2_buf.index);
}

在这个函数中,调用 vivi_fillbuff 函数来填充数据,然后调用 vb2_buffer_done 函数,这个函数在 videobuf2-core.c 文件中,如下所示:

/*** vb2_buffer_done() - inform videobuf that an operation on a buffer is finished* @vb:        vb2_buffer returned from the driver* @state:    either VB2_BUF_STATE_DONE if the operation finished successfully*        or VB2_BUF_STATE_ERROR if the operation finished with an error** This function should be called by the driver after a hardware operation on* a buffer is finished and the buffer may be returned to userspace. The driver* cannot use this buffer anymore until it is queued back to it by videobuf* by the means of buf_queue callback. Only buffers previously queued to the* driver by buf_queue can be passed to this function.*/
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
{struct vb2_queue *q = vb->vb2_queue;unsigned long flags;if (vb->state != VB2_BUF_STATE_ACTIVE)return;
/* 因为在前面的步骤已经设置了vb2_buffer vb的属性为 VB2_BUF_STATE_ACTIVE,这时候再次检查这个属性,如果不为 VB2_BUF_STATE_ACTIVE的话就直接返回。 */if (state != VB2_BUF_STATE_DONE && state != VB2_BUF_STATE_ERROR)return;
/* 检查函数传入的属性,它代表vb2_buffer的下一个属性,只能是 VB2_BUF_STATE_DONE和 VB2_BUF_STATE_ERROR中的一个,如果不是这两个就直接返回。 */dprintk(4, "Done processing on buffer %d, state: %d\n",vb->v4l2_buf.index, vb->state);/* Add the buffer to the done buffers list */spin_lock_irqsave(&q->done_lock, flags);vb->state = state;
/* 设置vb2_buffer的属性为 VB2_BUF_STATE_DONE或VB2_BUF_STATE_ERROR。 */list_add_tail(&vb->done_entry, &q->done_list);
/* 将vb2_buffer里面的done_entry队列添加到 vb2_queue q的done_list链表中去。 */atomic_dec(&q->queued_count);
/* 原子减少这个计数*/spin_unlock_irqrestore(&q->done_lock, flags);/* Inform any processes that may be waiting for buffers */wake_up(&q->done_wq);
/* 唤醒vb2_buffer q里面的done_wq等待队列。 */
}
EXPORT_SYMBOL_GPL(vb2_buffer_done);

这一步的主要目的就是将 vivi.c 中的 vb2 加入 q->done_list 中,然后设置 vb->state 的属性为 VB2_BUF_STATE_DONE,上面的这两步非常重要,在后面会用到这,这两步的核心代码就是 videobuf2-core.c 中的 vb2_buffer_done 函数。

7)、应用程序这时候就开始收集摄像头数据了,当它收集到数据的时候,就调用到了 poll/select 机制,应用程序如下所示:

static void run(void)
{unsigned int count;int frames;frames = 30 * time_in_sec_capture;while(frames-- > 0){for(;;){fd_set fds;struct timeval tv;int r;FD_ZERO(&fds);FD_SET(fd, &fds);tv.tv_sec = 2;tv.tv_usec = 0;/* poll method */r = select(fd+1, &fds, NULL, NULL, &tv);if(read_frame())break;}}
}

经过 v4l2 框架的一系列转换,最终调用到 vivi.c 驱动中我们自己实现的 vivi_poll 函数中:

static unsigned int
vivi_poll(struct file *file, struct poll_table_struct *wait)
{struct vivi_dev *dev = video_drvdata(file);struct v4l2_fh *fh = file->private_data;struct vb2_queue *q = &dev->vb_vidq;unsigned int res;dprintk(dev, 1, "%s\n", __func__);res = vb2_poll(q, file, wait);if (v4l2_event_pending(fh))res |= POLLPRI;elsepoll_wait(file, &fh->wait, wait);return res;
}

它又调用到 vb2_poll 函数,如下所示:

/*** vb2_poll() - implements poll userspace operation* @q:        videobuf2 queue* @file:    file argument passed to the poll file operation handler* @wait:    wait argument passed to the poll file operation handler** This function implements poll file operation handler for a driver.* For CAPTURE queues, if a buffer is ready to be dequeued, the userspace will* be informed that the file descriptor of a video device is available for* reading.* For OUTPUT queues, if a buffer is ready to be dequeued, the file descriptor* will be reported as available for writing.** The return values from this function are intended to be directly returned* from poll handler in driver.*/
unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
{unsigned long flags;unsigned int ret;struct vb2_buffer *vb = NULL;/** Start file I/O emulator only if streaming API has not been used yet.*/if (q->num_buffers == 0 && q->fileio == NULL) {if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ)) {ret = __vb2_init_fileio(q, 1);if (ret)return POLLERR;}if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE)) {ret = __vb2_init_fileio(q, 0);if (ret)return POLLERR;/** Write to OUTPUT queue can be done immediately.*/return POLLOUT | POLLWRNORM;}}/** There is nothing to wait for if no buffers have already been queued.*/if (list_empty(&q->queued_list))return POLLERR;poll_wait(file, &q->done_wq, wait);
/* 调用poll_wait等待vb2_queue q的done_wq队列是否有数据填充,那么它是什么时候有数据填充的呢?就是我们上一步分析的。 *//** Take first buffer available for dequeuing.*/spin_lock_irqsave(&q->done_lock, flags);if (!list_empty(&q->done_list))vb = list_first_entry(&q->done_list, struct vb2_buffer,done_entry);spin_unlock_irqrestore(&q->done_lock, flags);
/* 从vb2_queue q队列的done_list中,取出第一个vb2_buffer,为dqbuf做准备。 */if (vb && (vb->state == VB2_BUF_STATE_DONE|| vb->state == VB2_BUF_STATE_ERROR)) {return (V4L2_TYPE_IS_OUTPUT(q->type)) ? POLLOUT | POLLWRNORM :POLLIN | POLLRDNORM;
/* poll机制返回值,根据q->type类型,返回可读还是可写。 */}return 0;
}
EXPORT_SYMBOL_GPL(vb2_poll);

8)、看到上面 7)步中的应用程序,它在 run 函数的死循环中,如果 poll 机制返回可读还是可写的话,就调用 read_frame 函数,这个函数如下所示:

static int read_frame(void)
{struct v4l2_buffer buf;unsigned int i;CLEAR(buf);buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;/* VIDIOC_DQBUF 把数据放回缓存队列 */if(-1 == xioctl(fd, VIDIOC_DQBUF, &buf)){switch(errno){case EAGAIN:return 0;case EIO:default:errno_exit("VIDIOC_DQBUF");}}assert(buf.index < n_buffers);printf("v4l2_pix_format->field(%d)\n", buf.field);//assert(buf.field == V4L2_FIELD_NONE);process_image(buffers[buf.index].start);/* VIDIOC_QBUF 把数据从缓冲区中读取出来 */if(-1 == xioctl(fd, VIDIOC_QBUF, &buf)){errno_exit("VIDIOC_QBUF");}return 1;
}

应用程序调用 ioctl:VIDIOC_DQBUF ,经过一系列的转换,最终调用到 vivi.c 中的 vidioc_dqbuf 函数:

static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{struct vivi_dev *dev = video_drvdata(file);return vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK);
}

它有调用到 videobuf2-core.c 中的 vb2_dqbuf 函数,如下所示:

/*** vb2_dqbuf() - Dequeue a buffer to the userspace* @q:        videobuf2 queue* @b:        buffer structure passed from userspace to vidioc_dqbuf handler*        in driver* @nonblocking: if true, this call will not sleep waiting for a buffer if no*         buffers ready for dequeuing are present. Normally the driver*         would be passing (file->f_flags & O_NONBLOCK) here** Should be called from vidioc_dqbuf ioctl handler of a driver.* This function:* 1) verifies the passed buffer,* 2) calls buf_finish callback in the driver (if provided), in which* driver can perform any additional operations that may be required before* returning the buffer to userspace, such as cache sync,* 3) the buffer struct members are filled with relevant information for* the userspace.** The return values from this function are intended to be directly returned* from vidioc_dqbuf handler in driver.*/
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{struct vb2_buffer *vb = NULL;int ret;if (q->fileio) {dprintk(1, "dqbuf: file io in progress\n");return -EBUSY;}if (b->type != q->type) {dprintk(1, "dqbuf: invalid buffer type\n");return -EINVAL;}ret = __vb2_get_done_vb(q, &vb, nonblocking);if (ret < 0) {dprintk(1, "dqbuf: error getting next done buffer\n");return ret;}
/*
/*** __vb2_get_done_vb() - get a buffer ready for dequeuing** Will sleep if required for nonblocking == false.*/
static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,int nonblocking)
{unsigned long flags;int ret;/** Wait for at least one buffer to become available on the done_list.*/ret = __vb2_wait_for_done_vb(q, nonblocking);if (ret)return ret;/* 至少等待在done_list链表里面有一个buffer *//** Driver's lock has been held since we last verified that done_list* is not empty, so no need for another list_empty(done_list) check.*/spin_lock_irqsave(&q->done_lock, flags);*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);list_del(&(*vb)->done_entry);spin_unlock_irqrestore(&q->done_lock, flags);
/* 从vb2_queue队列的done_lise链表中,把vb中done_entry链表删除 */return 0;
}
*/ret = call_qop(q, buf_finish, vb);if (ret) {dprintk(1, "dqbuf: buffer finish failed\n");return ret;}
/* 最终调用到vivi.c中的buffer_finish函数 */switch (vb->state) {case VB2_BUF_STATE_DONE:dprintk(3, "dqbuf: Returning done buffer\n");break;case VB2_BUF_STATE_ERROR:dprintk(3, "dqbuf: Returning done buffer with errors\n");break;default:dprintk(1, "dqbuf: Invalid buffer state\n");return -EINVAL;}
/* 这时候传过来的vb_state应该是 VB2_BUF_STATE_ACTIVE的,除此之外的直接返回。 *//* Fill buffer information for the userspace */__fill_v4l2_buffer(vb, b);
/* 填充v4l2_buffer结构体 *//* Remove from videobuf queue */list_del(&vb->queued_entry);
/* 把vb2_buffer vb中的queued_entry链表中的一项。 */dprintk(1, "dqbuf of buffer %d, with state %d\n",vb->v4l2_buf.index, vb->state);vb->state = VB2_BUF_STATE_DEQUEUED;
/* 设置vb2_buffer的状态为 VB2_BUF_STATE_DEQUEUED */return 0;
}
EXPORT_SYMBOL_GPL(vb2_dqbuf);

9)、看 8)中的 read_frame 应用程序函数,它执行完 ioctl:VIDIOC_DQBUF 后,执行 process_image 函数,来完成对图像的处理,处理完图像后,就直接再次调用 ioctl:VIDIOC_QBUF 调用,就这样一直循环执行下去,直到应用程序调用 ioctl:VIDIOC_STREAMOFF 调用

10)、下面将 ioctl:VIDIOC_STREAMOFF 调用,应用程序一般如下所示:

static void stop_capturing(void)
{enum v4l2_buf_type type;type = V4L2_BUF_TYPE_VIDEO_CAPTURE;/* VIDIOC_STREAMOFF 结束视频显示函数 */if(-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))errno_exit("VIDIOC_STREAMOFF");
}

经过一系列的转换,最终调用到 vivi.c 中的 stop_streaming 函数,如下所示:

/* abort streaming and wait for last buffer */
static int stop_streaming(struct vb2_queue *vq)
{struct vivi_dev *dev = vb2_get_drv_priv(vq);dprintk(dev, 1, "%s\n", __func__);vivi_stop_generating(dev);return 0;
}
static void vivi_stop_generating(struct vivi_dev *dev)
{struct vivi_dmaqueue *dma_q = &dev->vidq;dprintk(dev, 1, "%s\n", __func__);/* shutdown control thread */if (dma_q->kthread) {kthread_stop(dma_q->kthread);dma_q->kthread = NULL;}/* 把内核线程关闭了。 *//** Typical driver might need to wait here until dma engine stops.* In this case we can abort imiedetly, so it's just a noop.*//* Release all active buffers */while (!list_empty(&dma_q->active)) {struct vivi_buffer *buf;buf = list_entry(dma_q->active.next, struct vivi_buffer, list);list_del(&buf->list);vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index);}
/* 一直把所有的缓冲区都释放掉。 */
}

至此,基本所有的过程都分析完毕了,剩下的就是把上面做标记的位置填充了,把哪个 ioctl 控制的流程图画了,mmap 的分析, ioctl 的分析。

分析这么点东西花了一个星期了,先放一放 。。。。。。

[Linux 基础] -- V4L2 实例分析 —— vivi.c 源码详解(深度好文)相关推荐

  1. 【 卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10)】

    卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10) 本章主要进行卷积神经网络的相关数学原理和pytorch的对应模块进行推导分析 代码也是通过demo实 ...

  2. 【多输入模型 Multiple-Dimension 数学原理分析以及源码详解 深度学习 Pytorch笔记 B站刘二大人 (6/10)】

    多输入模型 Multiple-Dimension 数学原理分析以及源码源码详解 深度学习 Pytorch笔记 B站刘二大人(6/10) 数学推导 在之前实现的模型普遍都是单输入单输出模型,显然,在现实 ...

  3. 【卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10)】

    卷积神经网络CNN 实战案例 GoogleNet 实现手写数字识别 源码详解 深度学习 Pytorch笔记 B站刘二大人 (9.5/10) 在上一章已经完成了卷积神经网络的结构分析,并通过各个模块理解 ...

  4. 【分类器 Softmax-Classifier softmax数学原理与源码详解 深度学习 Pytorch笔记 B站刘二大人(8/10)】

    分类器 Softmax-Classifier softmax数学原理与源码详解 深度学习 Pytorch笔记 B站刘二大人 (8/10) 在进行本章的数学推导前,有必要先粗浅的介绍一下,笔者在广泛查找 ...

  5. 【 线性回归 Linear-Regression torch模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人(4/10)】

    torch模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人 深度学习 Pytorch笔记 B站刘二大人(4/10) 介绍 至此开始,深度学习模型构建的预备知识已经完全准备完毕. 从本章开 ...

  6. 【 反向传播算法 Back-Propagation 数学推导以及源码详解 深度学习 Pytorch笔记 B站刘二大人(3/10)】

    反向传播算法 Back-Propagation 数学推导以及源码详解 深度学习 Pytorch笔记 B站刘二大人(3/10) 数学推导 BP算法 BP神经网络可以说机器学习的最基础网络.对于普通的简单 ...

  7. 【 梯度下降算法 Gradient-Descend 数学推导与源码详解 深度学习 Pytorch笔记 B站刘二大人(2/10)】

    梯度下降算法 Gradient-Descend 数学推导与源码详解 深度学习 Pytorch笔记 B站刘二大人(2/10) 数学原理分析 在第一节中我们定义并构建了线性模型,即最简单的深度学习模型,但 ...

  8. 【 数据集加载 DatasetDataLoader 模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人 (7/10)】

    数据集加载 Dataset&DataLoader 模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人 (7/10) 模块介绍 在本节中没有关于数学原理的相关介绍,使用的数据集和类型 ...

  9. Linux 内核中RAID5源码详解之守护进程raid5d

    Linux 内核中RAID5源码详解之守护进程raid5d 对于一个人,大脑支配着他的一举一动:对于一支部队,指挥中心控制着它的所有活动:同样,对于内核中的RAID5,也需要一个像大脑一样的东西来支配 ...

最新文章

  1. JBL无所不能与IPhone4、IPad2的完美盛宴
  2. 动态代理之Rxjava/Retrofit应用实战
  3. 《基于张量网络的机器学习入门》学习笔记1
  4. android studio selector 插件,Android Studio 常用插件
  5. 门户网站运营的几个方法
  6. jzoj3844-统计损失【树形dp,换根法】
  7. Cocos2dx中零散知识点
  8. 迅雷加载项会导致IE9浏览器崩溃
  9. java j2se1.5_Java教程 用J2SE1.5建立多任务的Java应用程序
  10. 定义与声明c语言,c语言定义与声明.ppt
  11. 设计灵感|有趣优雅的弹窗消息设计!
  12. WPF中的附加行为简介
  13. c++ java setobjectarrayelement_java中jni的使用:C/C++操作java中的数组
  14. Spring Boot(十八):使用Spring Boot集成FastDFS
  15. nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
  16. 自动清理源计算机设备驱动,win10系统删除过期驱动程序设备的设置技巧
  17. 杭州电子科技大学程序设计竞赛(2016’12)
  18. python量化选股策略 源码_【一点资讯】Python实现行业轮动量化选股【附完整源码】...
  19. 期货开户线上线下开户流程
  20. draw.io 绘图软件导出png 图片的几个技巧

热门文章

  1. FIL WORLD算力众筹助推Filecoin生态落地
  2. Visual Studio 调试器中的转储文件
  3. Excel 删除重复值 一个不留
  4. iOS用户隐私政策(中英文)
  5. matlab 小数点取前两位_matlab里面如何保留小数特定位数
  6. 数据采集之测试数据的造数
  7. html中幻灯片代码怎样写,HTML代码简介幻灯片.ppt
  8. ELM:ELM基于近红外光谱的汽油测试集辛烷值含量预测结果对比—Jason niu
  9. 《火影忍者》中角斗场PK、提升战力
  10. 《社群经济:移动互联时代的生存哲学》读书笔记