转自:http://blog.chinaunix.net/uid-30254565-id-5637600.html
- V4L2学习记录
- 这个还没有分析完,先在这放着,防止电脑坏掉丢了,以后再完善
- V4L2的全称是video for linux two。
- V4L2 驱动核心
- V4L2 驱动源码在 drivers/media/video目录下,主要核心代码有:
- v4l2-dev.c //linux版本2视频捕捉接口,主要结构体 video_device 的注册
- v4l2-common.c //在Linux操作系统体系采用低级别的操作一套设备structures/vectors的通用视频设备接口。
- v4l2-device.c //V4L2的设备支持。注册v4l2_device。
- v4l22-ioctl.c //处理V4L2的ioctl命令的一个通用的框架。
- v4l2-subdev.c //v4l2子设备
- v4l2-mem2mem.c //内存到内存为Linux和videobuf视频设备的框架。设备的辅助函数,使用其源和目的地videobuf缓冲区。
- 直接来看驱动源码的话,还是对驱动的框架没有一个感性的认识,尤其这个V4L2的框架非常复杂,我们先从内核源码中提供的虚拟视频驱动程序vivi.c来分析。内核版本3.4.2。
- 虚拟视频驱动程序vivi.c源码分析
- 16年1月18日09:35:42
- (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_PARM_DESC(n_devs, "number of video devices to create");
- 写的很清楚了, n_devs表示想要创建的 video devices个数。
- 去掉其他的判断语句,发现重要的函数只有一个vivi_create_instance(i)函数,我们下面就来分析这个函数。
- (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 device
- as is the case with e.g. ISA devices. */
- struct device *dev;
- #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 driver 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 mutex ioctl_lock;
- /* Keep track of the references to this struct. */
- struct kref ref;
- /* Release function that is called when the ref count goes to 0. */
- void (*release)(struct v4l2_device *v4l2_dev);
- };
- 可以看到这个结构体里面包含一个device父设备成员,一个subdevs子设备链表头,一个自旋锁,一个notify函数指针,v4l2_ctrl_handler控制句柄,prio优先级,ref引用计数,还有一个release函数指针。暂时先不对这个结构体进行具体的分析,在以后分析V4L2框架的时候再分析。
- 2. 它通过v4l2_device_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;
- }
- EXPORT_SYMBOL(v4l2_ctrl_handler_init);
- 通过nr_of_controls_hint变量的大小,计算出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(structv4l2_ctrl_handler *hdl, conststruct v4l2_ctrl_ops *ops, u32id, 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_video_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 standard vars */
- v4l2_std_id tvnorms; /* Supported tv norms */
- v4l2_std_id current_norm; /* Current tvnorm */
- /* 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, vidioc_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_operations成员,这个成员又包含一个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_QUERYCAP宏,真正调用的是vivi_ioctl_ops中的.vidioc_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_warn(
- 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 never
- having 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_device
- prio 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, so
- we 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;
- }
- #endif
- vdev->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 last
- reference 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中执行:
- video_set_drvdata(vfd, dev);
- /* 将vivi_dev dev添加到video_device vfd中,为什么要这样做呢?是为了以后字符设备驱动接口的使用。*/
- list_add_tail(&dev->vivi_devlist, &vivi_devlist);
- /* 添加到device list链表中 */
- if (video_nr != -1)
- video_nr++;
- /* 用于计数 */
- dev->vfd = vfd;
- /* 关联video_device 和vivi_dev。 */
- (三)到这里我们就分析完了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中那些具体代码的实现。
- V4L2框架分析
- 16年1月18日19:12:43
- (一)概述
- Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
- V4L2支持三类设备:GRABBER视频输入输出设备、VBI设备和RADIO设备(其实还支持更多类型的设备,暂不讨论),分别会在/dev目录下产生videoX、radioX和vbiX设备节点。我们常见的视频输入设备主要是摄像头,也是本文主要分析对象。下图V4L2在Linux系统中的结构图:
- Linux系统中视频输入设备主要包括以下四个部分:
- (1)字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
- (2)V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
- (3)平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册 video_device和v4l2_dev。
- (4)具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并 注册v4l2_subdev 。
- V4L2的核心源码位于drivers/media/video中,在4.4版本的内核中位于drivers/media/v4l2-core,源码以实现的功能可以划分为四类:
- 核心模块实现:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video_device注册注销和file_operations等相关函数;
- V4L2框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件实现,构建V4L2框架;
- Videobuf管理:由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、 videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
- ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2 ioctl的框架。
- (二)V4L2框架
- 结构体v4l2_device、video_device、v4l2_subdev和v4l2_fh是搭建框架的主要元素。下图是V4L2框架的结构 图:
- 从上图可以看出V4L2框架是一个标准的树形结构,v4l2_device充当了父设备,通过链表把所有注册到其下的子设备管理起来,这些设备可以是GRABBER、VBI或RADIO。v4l2_subdev是子设备,v4l2_subdev结构体包含了对设备操作的ops和ctrls,这部分代码和硬件相关,需要驱动工程师根据硬件实现,像摄像头设备需要实现控制上下电、读取ID、饱和度、对比度和视频数据流打开关闭的接口函数。video_device用于创建子设备节点,把操作设备的接口暴露给用户空间。V4l2_fh是每个子设备的文件句柄,在打开设备节点文件时设置,方便上层索引到v4l2_ctrl_handler,v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等。
- (三)v4l2_device结构体
- v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设备。 它在v4l2-device.h中定义,如下所示:
- struct v4l2_device {
- /* dev->driver_data points to this struct.
- Note: dev might be NULL if there is no parent device
- as is the case with e.g. ISA devices. */
- struct device *dev;
- /* used to keep track of the registered subdevs */
- struct list_head subdevs; //它所管理的子设备链表头
- /* lock this struct; can be used by the driver 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]; //device名字
- /* 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 mutex ioctl_lock;
- /* Keep track of the references to this struct. */
- struct kref ref; //引用计数
- /* Release function that is called when the ref count goes to 0. */
- void (*release)(struct v4l2_device *v4l2_dev);
- };
- 可以看出v4l2_device的主要作用是管理注册在其下的子设备,方便系统查找引用到。
- V4l2_device的注册和注销:
- int v4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev)
- static void v4l2_device_release(struct kref *ref)
- 暂时不对这两个函数进行具体分析,再后面会详细分析这个c文件。
- (四)V4l2_subdev
- V4l2_subdev代表子设备,包含了子设备的相关属性和操作。先来看下结构体原型,在v4l2-subdev.h中:
- struct v4l2_subdev {
- #if defined(CONFIG_MEDIA_CONTROLLER)
- struct media_entity entity;
- #endif
- struct list_head list;
- struct module *owner;
- u32 flags;
- struct v4l2_device *v4l2_dev; //指向它的父设备
- const struct v4l2_subdev_ops *ops; //提供一些控制v4l2设备的接口
- /* Never call these internal ops from within a */
- const struct v4l2_subdev_internal_ops *internal_ops; //向V4L2框架提供的接口函数
- /* The control handler of this subdev. May be NULL. */
- struct v4l2_ctrl_handler *ctrl_handler; //subdev控制接口
- /* name must be unique */
- char name[V4L2_SUBDEV_NAME_SIZE]; //subdev名字
- /* can be used to group similar subdevs, value is driver-specific */
- u32 grp_id;
- /* pointer to private data */
- void *dev_priv;
- void *host_priv;
- /* subdev device node */
- struct video_device *devnode;
- };
- 每个子设备驱动都需要实现一个v4l2_subdev结构体,v4l2_subdev可以内嵌到其它结构体中,也可以独立使用。结构体中包含了对子设备操作的成员v4l2_subdev_ops和v4l2_subdev_internal_ops。
- v4l2_subdev_ops结构体原型如下:
- struct v4l2_subdev_ops {
- const struct v4l2_subdev_core_ops *core;
- const struct v4l2_subdev_tuner_ops *tuner;
- const struct v4l2_subdev_audio_ops *audio;
- const struct v4l2_subdev_video_ops *video;
- const struct v4l2_subdev_vbi_ops *vbi;
- const struct v4l2_subdev_ir_ops *ir;
- const struct v4l2_subdev_sensor_ops *sensor;
- const struct v4l2_subdev_pad_ops *pad;
- };
- 视频设备通常需要实现core和video成员,这两个ops中的操作都是可选的,但是对于视频流设备
- video->s_stream(开启或关闭流IO)必须要实现。
- v4l2_subdev_internal_ops结构体原型如下:
- struct v4l2_subdev_internal_ops {
- int (*registered)(struct v4l2_subdev *sd);
- void (*unregistered)(struct v4l2_subdev *sd);
- int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
- int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
- };
- v4l2_subdev_internal_ops是向V4L2框架提供的接口,只能被V4L2框架层调用。在注册或打开子设备时,进行一些辅助性操作。
- subdev的注册和注销
- 当我们把v4l2_subdev需要实现的成员都已经实现,就可以调用以下函数把子设备注册到V4L2核心层:
- int v4l2_device_register_subdev(struct v4l2_device*v4l2_dev, struct v4l2_subdev *sd)
- 当卸载子设备时,可以调用以下函数进行注销:
- void v4l2_device_unregister_subdev(struct v4l2_subdev*sd)
- (五)video_device
- 1. video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间。所以这个结构体是我们操作的重点,它直接与用户空间相联系。
- struct video_device
- {
- #if defined(CONFIG_MEDIA_CONTROLLER)
- struct media_entity entity;
- #endif
- /* device ops */
- const struct v4l2_file_operations *fops; //V4L2设备操作函数集合
- /* 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 standard vars */
- v4l2_std_id tvnorms; /* Supported tv norms */
- v4l2_std_id current_norm; /* Current tvnorm */
- /* callbacks */
- void (*release)(struct video_device *vdev);
- /* ioctl callbacks */
- const struct v4l2_ioctl_ops *ioctl_ops; /*ioctl回调函数集,提供 * file_operations中的ioctl调用 */
- /* serialization lock */
- struct mutex *lock;
- };
- 2. video_device结构体的name字段是这类设备的名字,它将出现在内核日志和 sysfs中出现。这个名字 通常与驱动名称相同。
- 3. vfl_type字段可以是下列5个值之一,他们在v4l2-dev.h中定义:
- #define VFL_TYPE_GRABBER 0
- #define VFL_TYPE_VBI 1
- #define VFL_TYPE_RADIO 2
- #define VFL_TYPE_SUBDEV 3
- #define VFL_TYPE_MAX 4
- 4. V4L2驱动还要初始化的一个字段是 minor,它是你想要的子设备号。通常这个值都设为-1,这样会让video4linux子系统在注册时自动分配一个空的子设备号。
- 5. 在video_device结构体中,一共包含三组不同的函数指针集。
- 5.1第一个函数指针集只包含一个函数,就是
- void (*release)(struct video_device *vdev);
- 这个函数通常只包含一个简单的kfree调用,V4L2驱动框架中v4l2-dev.c中帮我们实现了video_device_release,一般让这个函数指向video_device_release即可。
- 5.2 第二个函数指针集是const struct v4l2_file_operations *fops; 它与file_operations结构体大致相同,在前面我们就说了,V4L2框架它就是一个字符设备驱动,怎么体现呢?以这个vivi.c为例:
- 首先,它作为一个字符设备驱动,肯定有对应的file_operations结构,它就在v4l2-dev.c中定义了:
- static const struct file_operations v4l2_fops = {
- .owner = THIS_MODULE,
- .read = v4l2_read,
- .write = v4l2_write,
- .open = v4l2_open,
- .get_unmapped_area = v4l2_get_unmapped_area,
- .mmap = v4l2_mmap,
- .unlocked_ioctl = v4l2_ioctl,
- .release = v4l2_release,
- .poll = v4l2_poll,
- .llseek = no_llseek,
- };
- 然后,在vivi.c中,我们注册申请video_device vivi_template的时候,需要提供对应的v4l2_file_operations vivi_fops,如下所示:
- 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_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,
- };
- 以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, realease等,比较特别的是ioctl函数,在后面分析它。他们都是应用程序调用,通过V4L2框架中转到对应的驱动程序中,然后驱动程序根据不同的调用,选择调用videobuf或ioctl中的函数。
- 5.3 第三个函数指针集是const struct v4l2_ioctl_ops *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更麻烦,首先作为字符设备驱动,当应用程序调用ioctl的时候,就调用到了file_operations v4l2_fops中的.unlocked_ioctl = v4l2_ioctl,这个v4l2_ioctl同样通过ret = vdev->fops->ioctl(filp, cmd, arg);就调用到了vivi.c中申请注册的video_device vivi_template结构体里面的fops->ioctl函数,即v4l2_file_operations 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函数又调用了video_device vivi_template 结构体里面的.ioctl_ops = &vivi_ioctl_ops,然后根据宏的名字来选择struct v4l2_ioctl_ops vivi_ioctl_ops中对应的函数,即vidioc_querycap函数。
- 这些调用太麻烦了,我决定在后面画一张图表来表示这些调用关系。
- 5.4 video_device分配和释放,用于分配和释放video_device结构体:
- struct video_device *video_device_alloc(void)
- void video_device_release(struct video_device *vdev)
- video_device注册和注销,实现video_device结构体的相关成员后,就可以调用下面的接口进行注册:
- static inline int __must_check video_register_device(struct video_device *vdev,
- inttype, int nr)
- void video_unregister_device(struct video_device*vdev);
- vdev:需要注册和注销的video_device;
- type:设备类型,包括VFL_TYPE_GRABBER、VFL_TYPE_VBI、VFL_TYPE_RADIO和VFL_TYPE_SUBDEV。
- nr:设备节点名编号,如/dev/video[nr]。
- (六)v4l2_fh
- v4l2_fh是用来保存子设备的特有操作方法,也就是下面要分析到的v4l2_ctrl_handler,内核提供一组v4l2_fh的操作方法,通常在打开设备节点时进行v4l2_fh注册。
- 初始化v4l2_fh,添加v4l2_ctrl_handler到v4l2_fh:
- void v4l2_fh_init(struct v4l2_fh *fh, structvideo_device *vdev)
- 添加v4l2_fh到video_device,方便核心层调用到:
- void v4l2_fh_add(struct v4l2_fh *fh)
- (七)v4l2_ctrl_handler
- v4l2_ctrl_handler是用于保存子设备控制方法集的结构体,对于视频设备这些ctrls包括设置亮度、饱和度、对比度和 清晰度等,用链表的方式来保存ctrls,可以通过v4l2_ctrl_new_std函数向链表添加ctrls。
- 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参数传递。
- (八)ioctl框架
- 你可能观察到用户空间对V4L2设备的操作基本都是ioctl来实现的,V4L2设备都有大量可操作的功能(配置寄存器),所以V4L2的ioctl也是十分庞大的。它是一个怎样的框架,是怎么实现的呢?
- ioctl函数在v4l2-ioctl.h中定义,一共包含79个回调函数,如果在这列举的话,就会显得太冗长了,我们会在下面用到的部分再列举,现显示部分如下所示:
- struct v4l2_ioctl_ops {
- /* ioctl callbacks */
- /* VIDIOC_QUERYCAP handler */
- int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
- /* Priority handling */
- 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);
- };
- 8.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(cap->bus_info));来填充。
- capabilities 成员是一个位掩码用来描述驱动能做的不同事情,也在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;
- }
- 它就是完成上述我们所说的事情。
- 8.2 输入和输出
- 8.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;
- }
- 它就什么都没做,因为在设置video_device结构体的时候设置了:
- 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,
- };
- 下面就简单说一些视频标准,这些标准描述的是视频如何为传输 而进行格式化---分辨率、帧率等。 现在世界上使用的 标准主要有三个: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)
- 8.2.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 *index);
- 这里驱动把*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;
- }
- 8.2.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:输出类型,支持的类型如下:
- /* Values for the 'type' field */
- #define V4L2_OUTPUT_TYPE_MODULATOR 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 应用就可以知道有哪些输入和输入,并在它们间进行选择。
- 8.3 颜色与格式
- 应用在视频设备可以工作之前,它必须与驱动达成一致,知道视频数据是何种格式。这种协商将是一
- 个非常复杂的过程,其原因有二:
- 1、 视频硬件所支持的视频格各不相同。
- 2、 在内核的格式转换是令人难以接受的。
- 所以应用要找出一种硬件支持的格式,并做出一种大家都可以接受的配置。 这篇文章将会讲述格式的
- 基本描述方式,下篇文章则会讲述 V4L2 驱动与应用协商格式时所实现的 API。
- 8.3.1 色域
- 色域从广义上来讲,就是系统在描述色彩时所使用的坐标系。V4L2 规范中定义了好几个,但只有两个
- 的使用最为广泛。它们是:
- ● V4L2_COLORSPACE_SRGB
- 多数开发者所熟悉的[red、green、blue]数组就包含在这个色域中。它为每一种颜色提供了一个简单的
- 强度值,把它们混合在一起,从而产生了丰富的颜色。表示 RGB 值的方法有很多,我们在下面将会介绍。 这个色域也包含 YUV 和 YCbCr 的表示方法,这个表示方法最早是为了早期的彩色电视信号可以在黑 白电视中的播放,所以 Y(或“亮度” )值只是一个简单的亮度值,单独显示时可以产生灰度图像。U 和 V (或 Cb 和 Cr)色度值描述的是色彩中蓝色和红色的分量。绿色可以通过从亮度中减去这些分量而得到。
- YUV 和 RGB 之间的转换并不那么直接,但是我们有一些公式可用。
- 注意:YUV 和 YCbCr 并非完全一样,虽然有时他们的名字会替代使用。
- ● V4L2_COLORSPACE_SMPTE170M
- 这个是 NTSC 或 PAL 等电视信号的模拟色彩表示方法,电视调谐器通常产生的色域都属于这个色域。 还存在很多其他的色域,他们多数都是电视相关标准的变种。
- 8.3.2 密集存储和平面存储
- 如上所述,像素值是以数组的方式表示的,通常由 RGB 或 YUV 值组成。要把这数组组织成图像,通 常有两种常用的方法。
- ● Packed 格式把一个像素的所有分量值连续存放在一起。
- ● Planar 格式把每一个分量单独存储成一个阵列。例如在 YUV 格式中,所有 Y 值都连续地一起存 储在一个阵列中,U 值存储在另一个中,V 值存在第三个中。这些平面常常都存储在一个缓冲区 中,但并不一定非要这样。
- 紧密型存储方式可能使用更为广泛,特别是 RGB 格式,但这两种存储方式都可以由硬件产生并由应用 程序请求。如果设备可以产生紧密型和平面型两种,那么驱动就要让两种都在用户空间可见。
- 8.3.3 四字符码 (four Charactor Code : FourCC )
- V4L2 API 中表示色彩格式采用的是广受好评的四字符码(fourcc)机制。这些编码都是 32 位的值,由四
- 个 ASCII 码产生。 如此一来, 它就有一个优点就是, 易于传递, 对人可读。 例如, 当一个色彩格式读作“RGB4” 就没有必要去查表了。
- 注意:
- 四字符码在很多不同的配置中都会使用, 有些还是早于linux。 Mplayer 内部使用它们, 然而, fourcc 只是说明一种编码机制,并不说明使用何种编码。Mplayer有一个转换函数,用于在它自己的fourcc码和v4l2 用的fourcc码之间做出转换。
- 8.3.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 */
- 8.3.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 */
- 8.3.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 */
- 等等。。。。。。
- 8.3.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,
- };
- 至此,对于视频数据缓冲区的描述就算完成了,驱动程序需要和应用程序协商,以使硬件设备支持的图像格式能够满足应用程序的使用。下面再来讲驱动程序和应用程序协商的过程。
- 8.4 格式协商
- 在存储器中表示图像的方法有很多种。 市场几乎找不到可以处理所有V4L2所理解的视频格式的设备。驱动不应支持底层硬件不理解的视频格式。实际上在内核中进行格式转换是令人难以接受的。 所以驱动必须能让应用选择一个硬件可以支持的格式。
- 8.4.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 开始递 增,至最大允许值为止。应用可以通过一直递增索引值直到返回-EINVAL 的方式枚举所有支持的 格式。
- 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;
- }
- 8.4.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;
- else
- f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
- return 0;
- }
- 8.4.3多数应用都想最终对硬件进行配置以使其为应用提供一种合适的格式。改变视频格式有两个函数接口,
- 一个是 VIDIOC_TRY_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;
- else
- f->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;
- }
- 8.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访问方式,我们暂时不探讨,因为写到这,我已经腰酸背痛腿抽筋了!_!
- 8.6 流IO方式
- 关于流IO的方式,内核中有一个更高层次的API,它能够帮助驱动作者完成流驱动。当底层设备可以支持分散/聚集I/O的时候,这一层(称为 video-buf)可以使工作变得容易。关于video-buf API我们将在后面讨论。
- 支持流 I/O 的驱动应通过在 vidioc_querycap()方法中设置 V4L2_CAP_STREAMING 标签通知应用。注意:我们无法描述支持哪种缓冲区,那是后话。
- 8.6.1 v4l2_buffer 结构体
- 当使用流 I/O 时,帧以v4l2_buffer的格式在应用和驱动之间传输。
- 首先大家要知道一个缓冲区可以有三种基本状态:
- (1)在驱动的传入队列中。如果驱动不用它做什么有用事的话,应用就可以把缓冲区放在这个队列里。对于一个视频捕获设备,传入队列中的缓冲区是空的,等待驱动向其中填入视频数据;对于输出设备来讲,这些缓冲区是要设备发送的帧数据。
- (2)在驱动的传出队列中。这些缓冲区已由驱动处理过,正等待应用来认领。对于捕获设备而言,传出缓冲区内是新的帧数据;对输出设备而言,这个缓冲区是空的。
- (3)不在上述两个队列里。在这种状态时,缓冲区由用户空间拥有,驱动无法访问。这是应用可对缓冲区进行操作的唯一时间。我们称其为用户空间状态。
- 将这些状态和他们之间传输的操作放在一起,如下图所示:
- 其实缓冲区的三种状态是以驱动为中心的,可以理解为传入队列为要给驱动处理的缓冲区,传出队列为驱动处理完毕的缓冲区,而第三种状态是脱离驱动控制的缓冲区。
- 他们操作的缓冲区核心就是这个v4l2_buffer结构体,它在videodev2.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+byteused是图像数据在进程的虚拟地址空间所占区域;如果是用户指针缓冲区的方式,可以获取的图像数据开始地址的指针m.userptr,userptr是一个用户空间的指针,userptr+byteused便是所占的虚拟地址空间,应用可以直接访问。
- input:可以用来快速切换捕获设备的输入——如果设备支持帧间的快速切换。
- /* 以下几个函数是参考Tekkaman Ninja 整理的V4L2 驱动编写指南写的,在实际中对于这几个宏的ioctl回调函数已经转移到videobuf2-core.c里面,通过调用vb2_xxx函数来实现了,但是它对于缓冲区的操作过程还是非常重要的,同时希望能够了解更多的底层知识,为后面写videobuf2做准备。 */
- 8.6.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_MEMORY_MMAP,count置为期望使用的缓冲区数。如果驱动不支持内存映射,它应返回-EINVAL。否则它将在内部开辟请求的缓冲区并返回0。返回后,应用就会认为缓冲区是存在的,所以任何可能失败的操作都应在这个阶段处理 (比如说内存申请)。
- 注意:驱动并不一定要开辟与请求数目一样的缓冲区。在多数情况下,只有最小缓冲区数是有意义的。
- 如果应用请求的比这个最小值小,它可能得到比实际申请的多一些。
- 应用可以通过设置 count 字段为 0 的方式来释放掉所有已存在的缓冲区。在这种情况下, 驱动必须在释放缓冲前停止所有的 DMA 操作,否则会发生非常严重问题。如果缓冲区已映射到用户空间,则释放缓冲区是不可能的。
- 相反,如果使用用户空间缓冲区,则有意义的字段只有缓冲区的type以及仅 V4L2_MEMORY_USERPTR 这个可用的 memory 字段。应用无须指定它想用的缓冲区的数目。因为内存是在用户空间开辟的,驱动无须操心。如果驱动支持用户空间缓冲区,它只须注意应用会使用这一特性, 返回0就可以了,否则返回-EINVAL。
- VIDIOC_REQBUFS 命令是应用得知驱动所支持的流 I/O 缓冲区类型的唯一方法。
- 8.6.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 操作,因为没有进程需要它。
- 8.6.4 流IO
- 到现为止,我们己经看了很多设置,却没有传输过一帧的数据,我们离这步己经很近了,但在此之前还有一个步骤要做。当应用通过 VIDIOC_REQBUFS 获得了缓冲区后,那个缓冲区处于用户空间状态。如果他们是用户空间缓冲区,他们甚至还不存在。在应用开始流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 开始,驱动就要从它的传入队列里获取缓冲区,让设备更快地实现转送请求,然后把缓冲区移动到传出队列。转输开始时,缓冲区标签也要相应调整。像序号和时间戳这样的字段必需在这个时候填充。最后,应用会在传出队列中认领缓冲区,让它变回为用户态。这是 VIDIOC_DQBUF 的工作,它最终变为如下调用:
- int (*vidioc_dqbuf) (struct file *file, void *priv, struct v4l2_buffer *buf);
- 这里,驱动会从传出队列中移除第一个缓冲区,把相关的信息存入 buf。通常,传出队列是空的,这个调用会处于阻塞状态直到有缓冲区可用。然而V4L2是用来处理非阻塞I/O的,所以如果视频设备是以O_NONBLOCK 方式打开的,在队列为空的情况下驱动就该返回-EAGAIN。当然,这个要求也暗示驱动必须为流I/O支持poll()。
- 8.6.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()调用,此调用要停止设备。驱动还应从传入和传出队列 中移除所有的缓冲区,使它们都处于用户空间状态。当然,驱动必须意识到:应用可能在没有停止流传输的情况下关闭设备。
- (九)控制
- (十)linux内核v4l2框架中videobuf2分析
- 16年1月19日20:44:18
- 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结构体。
- 2.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中的设置,它都对这两个函数集设置了初始值,那么下面我们就来看看这两个函数集。
- 2.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-contig.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.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,
- };
- 3. 在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_PLANES];
- 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_PLANES];
- };
- 一个缓冲区有三种状态,我们在上面的V4L2框架分析中:8.6流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. 下面来结合代码详细分析分析它的流程:
- 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);
- 4.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 (没有分析这个mmap函数,以后补上) ,MAP_SHARED,fd, buf.m.offset);
- }
- 经过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;
- }
- 4.3 mmap
- 4.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 = video_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 = ¤t->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);
- else
- vb->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);
- 4.5 然后就是启动摄像头了,ioctl:VIDIOC_STREAMON ,应用程序在4.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);
- 4.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;
- unsigned 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_fillbuff(dev, buf);
- dprintk(dev, 1, "filled buffer %p\n", buf);
- vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
- dprintk(dev, 2, "[%p/%d] 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函数。
- 4.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;
- else
- poll_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);
- 4.8 看上面4.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);
- 4.9 看4.8中的read_frame应用程序函数,它执行完ioctl:VIDIOC_DQBUF后,执行process_image函数,来完成对图像的处理,处理完图像以后,就直接再次调用ioctl:VIDIOC_QBUF调用,就这样一直循环执行,直到应用程序调用ioctl:VIDIOC_STREAMOFF 调用。
- 4.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的分析。
- 分析这么点东西花了一个星期时间了,先放一放。。。。。。
V4L2学习记录【转】的更多相关文章
-
Quartz 学习记录1
原因 公司有一些批量定时任务可能需要在夜间执行,用的是quartz和spring batch两个框架.quartz是个定时任务框架,spring batch是个批处理框架. 虽然我自己的小玩意儿平时不 ...
-
Java 静态内部类与非静态内部类 学习记录.
目的 为什么会有这篇文章呢,是因为我在学习各种框架的时候发现很多框架都用到了这些内部类的小技巧,虽然我平时写代码的时候基本不用,但是看别人代码的话至少要了解基本知识吧,另外到底内部类应该应用在哪些场合 ...
-
Apache Shiro 学习记录4
今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了.... ...
-
UWP学习记录12-应用到应用的通信
UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...
-
UWP学习记录11-设计和UI
UWP学习记录11-设计和UI 1.输入和设备 通用 Windows 平台 (UWP) 中的用户交互组合了输入和输出源(例如鼠标.键盘.笔.触摸.触摸板.语音.Cortana.控制器.手势.注视等)以 ...
-
UWP学习记录10-设计和UI之控件和模式7
UWP学习记录10-设计和UI之控件和模式7 1.导航控件 Hub,中心控件,利用它你可以将应用内容整理到不同但又相关的区域或类别中. 中心的各个区域可按首选顺序遍历,并且可用作更具体体验的起始点. ...
-
UWP学习记录9-设计和UI之控件和模式6
UWP学习记录9-设计和UI之控件和模式6 1.图形和墨迹 InkCanvas是接收和显示墨迹笔划的控件,是新增的比较复杂的控件,这里先不深入. 而形状(Shape)则是可以显示的各种保留模式图形对象 ...
-
UWP学习记录8-设计和UI之控件和模式5
UWP学习记录8-设计和UI之控件和模式5 1.日历.日期和时间控件 日期和时间控件提供了标准的本地化方法,可供用户在应用中查看并设置日期和时间值. 有四个日期和时间控件可供选择,选择的依据如下: 日 ...
-
UWP学习记录7-设计和UI之控件和模式4
UWP学习记录7-设计和UI之控件和模式4 1.翻转视图 使用翻转视图浏览集合中的图像或其他项目(例如相册中的照片或产品详细信息页中的项目),一次显示一个项目. 对于触摸设备,轻扫某个项将在整个集合中 ...
随机推荐
-
JavaScript的“原型甘露”
今天跟朋友讨论JS的面向对象编程问题,想起了原来曾经看过一篇文章,但是看过很久想不起来了,用了很多关键词,终于用“悟透JavaScript 面向对象”这两个关键词找到了“原文”,原文地址:http: ...
-
C#网络编程数据传输中封装数据帧头的方法
在C/S端编程的时候,经常要在C端和S端之间传数据时自定义一下报文的帧头,如果是在C/C++,封装帧头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没 ...
- 推荐两本学习linux的经典书籍
-
[NOIP2011] 提高组 洛谷P1003 铺地毯
题目描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯.一共有 n 张地毯,编号从 1 到n .现在将这些地毯按照编号从小到大的顺序平行于 ...
-
CSS一些总结
1. display block:块元素,默认宽度为100%,可以设置元素的宽高,默认占满一行.块元素包括div,h1-h6,form,table,ul,ol等: inline:行内元素,默认宽度为内 ...
-
hdu 4742 Pinball Game 3D 分治+树状数组
离散化x然后用树状数组解决,排序y然后分治解决,z在分治的时候排序解决. 具体:先对y排序,solve(l,r)分成solve(l,mid),solve(mid+1,r), 然后因为是按照y排序,所以 ...
-
解决VS2010使用comboBox死机问题
今天,在10下使用combobox总是不响应,原来是和翻译软件冲突,关掉有道立即解决.
-
svn 客户端安装 windows
windows使用的 https://tortoisesvn.net/ 下载中文语言包 安装 安装完安装语言包 看到这个代表svn客户端可以用了 windows客户端下载TortoiseSVN软件进行 ...
-
TFS二次开发10——分组(Group)和成员(Member)
TFS SDK 10 ——分组(Group)和成员(Member) 这篇来介绍怎样读取TFS服务器上的用户信息 首先TFS默认有如下分组(Group): SharePoint Web Applicat ...
-
wordpress后台进去空白怎么办?
最近博客换成了用wordpress程序搭建,内容和版面也重新设计.经常使用FTP工具,更改模板或者其他程序文件.由于对wordpress不太了解,竟然出现了wordpress后台进去空白的问题,而前台 ...