V4L2 学习总结文档

概述(Linux Media Subsystem Documentation — The Linux Kernel documentation

video4linux2(V4L2)是 Linux 内核中关于视频设备的内核驱动,它为 Linux中视频设备访问提供了通用接口,在 Linux 系统中,V4L2驱动的Video设备节点路径通常/dev/video/中的 videoX

基础调用流程的框架图如下:

整体框架

完整数据结构关系框图如下:

上面的图看不清,上局部图:

模块划分

通用API

一个完整的v4l2设备操作流程包含以下步骤:

  • 打开设备
  • 修改设备属性
  • 协商数据格式
  • 协商输入输出方法
  • 输入输出循环
  • 关闭设备

V4L2提供的接口:

  • 视频采集接口(video capture interface)
  • 视频输出接口(video output interface)
  • 直接传输视频接口(video overlay interface)
  • 视频间隔消隐信号接口(VBI interface)
  • 收音机接口(radio interface)

Streaming I/O

  • MMAP:内存映射模式,应用调用VIDIOC_REQBUFS ioctl分配设备buffers,参数标识需要的数目和类型。这个ioctl也可以用来改变buffers的数据以及释放分配的内存,当然这个内存空间一般也是连续的。在应用空间能够访问这些物理地址之前,必须调用mmap函数把这些物理空间映射为用户虚拟地址空间
  • USERPTR:用户空间指针,应用层负责分配需要的内存空间,以指针的形式传递给V4L2驱动层,V4L2驱动会把capture的内容保存到指针所指的空间,一般来说,应用层需要确保这个内存空间物理上是连续的
  • DMABUF:

MMAP

内存映射缓冲区是在内核开辟的缓冲区。应用通过mmap系统调用映射到用户地址空间,这些缓冲区可以是:

  • 大而连续的DMA缓冲区
  • vmalloc创建的虚拟缓冲区
  • 设备IO内存中开辟的缓冲区(如果硬件支持,比如显卡显存)

实现内存映射机制IO的驱动必须支持如下方法:

  • VIDIOC_REQBUFS
  • VIDIOC_QUERYBUF
  • VIDIOC_QBUF
  • VIDIOC_DQBUF
  • VIDIOC_STREAMON
  • VIDIOC_STREAMOFF
  • mmap
  • munmap
  • select
  • poll

实现demo如下:

struct buffer {
	void   *start;
	size_t  length;
};

struct buffer          *buffers;
static unsigned int     n_buffers;

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)) {
	if (EINVAL == errno) {
		fprintf(stderr, "%s does not support "
			"memory mapping\n", dev_name);
		exit(EXIT_FAILURE);
	} else {
		errno_exit("VIDIOC_REQBUFS");
	}
}

if (req.count < 2) {
	fprintf(stderr, "Insufficient buffer memory on %s\n",
		dev_name);
	exit(EXIT_FAILURE);
}

buffers = calloc(req.count, sizeof(*buffers));

if (!buffers) {
	fprintf(stderr, "Out of memory\n");
	exit(EXIT_FAILURE);
}

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 /* start anywhere */,
			  buf.length,
			  PROT_READ | PROT_WRITE /* required */,
			  MAP_SHARED /* recommended */,
			  fd, buf.m.offset);

	if (MAP_FAILED == buffers[n_buffers].start)
		errno_exit("mmap");
}

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;

	if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
		errno_exit("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
	errno_exit("VIDIOC_STREAMON");

USERPTR

用户空间缓冲区是在用户空间开辟的缓冲区,不需要mmap,必须支持如下方法:

  • VIDIOC_REQBUFS
  • VIDIOC_QBUF
  • VIDIOC_DQBUF
  • VIDIOC_STREAMON
  • VIDIOC_STREAMOFF
  • select
  • poll

实现demo如下:

struct buffer {
	void   *start;
	size_t  length;
};

struct buffer          *buffers;
static unsigned int     n_buffers;

struct v4l2_requestbuffers req;



req.count  = 4;
req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;

if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
	if (EINVAL == errno) {
		fprintf(stderr, "%s does not support "
			"user pointer i/o\n", dev_name);
		exit(EXIT_FAILURE);
	} else {
		errno_exit("VIDIOC_REQBUFS");
	}
}

buffers = calloc(4, sizeof(*buffers));

if (!buffers) {
	fprintf(stderr, "Out of memory\n");
	exit(EXIT_FAILURE);
}

for (n_buffers = 0; n_buffers < 4; ++n_buffers) {
	buffers[n_buffers].length = buffer_size;
	buffers[n_buffers].start = malloc(buffer_size);

	if (!buffers[n_buffers].start) {
		fprintf(stderr, "Out of memory\n");
		exit(EXIT_FAILURE);
	}
}

for (i = 0; i < n_buffers; ++i) {
	struct v4l2_buffer buf;

	CLEAR(buf);
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_USERPTR;
	buf.index = i;
	buf.m.userptr = (unsigned long)buffers[i].start;
	buf.length = buffers[i].length;

	if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
		errno_exit("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
	errno_exit("VIDIOC_STREAMON");

DMABUF

使用demo如下:

struct v4l2_requestbuffers reqbuf;

memset(&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_DMABUF;
reqbuf.count = 1;

if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
    if (errno == EINVAL)
        printf("Video capturing or DMABUF streaming is not supported\\n");
    else
        perror("VIDIOC_REQBUFS");

    exit(EXIT_FAILURE);
}

...

int buffer_queue(int v4lfd, int index, int dmafd)
{
    struct v4l2_buffer buf;

    memset(&buf, 0, sizeof buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_DMABUF;
    buf.index = index;
    buf.m.fd = dmafd;

    if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {
        perror("VIDIOC_QBUF");
        return -1;
    }

    return 0;
}

编码接口

自2.6内核起,该接口由于种种原因暂停

调试方式

节点

插入USB摄像头之后,video4linux目录中,马上就会出现新的device

fuqiang@fuqiang-virtual:/sys/class/video4linux$ ls -al
total 0
drwxr-xr-x  2 root root 0  8月 19 10:10 .
drwxr-xr-x 81 root root 0  8月 19 10:07 ..
lrwxrwxrwx  1 root root 0  8月 20 09:24 video0 -> ../../devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/video4linux/video0
lrwxrwxrwx  1 root root 0  8月 20 09:24 video1 -> ../../devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/video4linux/video1


fuqiang@fuqiang-virtual:/sys/class/video4linux$ ls -al /dev/video*
crw-rw----+ 1 root video 81, 0  8月 20 09:24 /dev/video0
crw-rw----+ 1 root video 81, 1  8月 20 09:24 /dev/video1

V4L2默认是编译为模块的

 

dumpstack

在驱动代码中添加dump_stack(),可以在打印中将堆栈打印出来,用于跟踪代码和调试

流程分析

代码框架

fuqiang@ubuntu:~/workspace/tina-d1-h/lichee/linux-5.4/drivers/media/v4l2-core$ tree
.
├── Kconfig
├── Makefile
├── tuner-core.c
├── v4l2-async.c
├── v4l2-clk.c
├── v4l2-common.c
├── v4l2-compat-ioctl32.c
├── v4l2-ctrls.c  //V4L2基础框架
├── v4l2-dev.c  //字符设备驱动模块,主要作用申请字符主设备号,注册class和提供video device注册注销等相关函数
├── v4l2-device.c  //V4L2基础框架
├── v4l2-dv-timings.c
├── v4l2-event.c
├── v4l2-fh.c  //V4L2基础框架
├── v4l2-flash-led-class.c
├── v4l2-fwnode.c
├── v4l2-i2c.c
├── v4l2-ioctl.c  //ioctl
├── v4l2-mc.c
├── v4l2-mem2mem.c  //内存管理
├── v4l2-spi.c
├── v4l2-subdev.c  //V4L2基础框架
├── v4l2-trace.c
├── videobuf-core.c  //内存管理
├── videobuf-dma-contig.c  //内存管理
├── videobuf-dma-sg.c  //内存管理
└── videobuf-vmalloc.c  //内存管理

0 directories, 26 files

v4l2-dev.c

该文件主要是负责创建/sys/class/video4linux目录 ,当有设备注册进来时,创建对应的 /dev/videox 、/dev/vbix、/dev/radiox、/dev/subdevx等节点

static int __init videodev_init(void)
{
	dev_t dev = MKDEV(VIDEO_MAJOR, 0);  //VIDEO_MAJOR:81
	int ret;

	pr_info("Linux video capture interface: v2.00\n");
	ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);  
                //VIDEO_NUM_DEVICES: 256  VIDEO_NAME:"video4linux"
                // 1. 将字符设备号(81,0) 到 (81,255) 这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
	if (ret < 0) {
		pr_warn("videodev: unable to get major %d\n",
				VIDEO_MAJOR);
		return ret;
	}

	ret = class_register(&video_class);
                // 2. 注册 /sys/classs/video4linux 目录
	if (ret < 0) {
		unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
		pr_warn("video_dev: class_register failed\n");
		return -EIO;
	}

	return 0;
}

subsys_initcall(videodev_init);

当用设备需要注册为 v4l2 subdev 时,会调用video_register_device()函数进行注册:

int __video_register_device(struct video_device *vdev,
			    enum vfl_devnode_type 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;

        ...

	/* v4l2_fh support */
	spin_lock_init(&vdev->fh_lock);
	INIT_LIST_HEAD(&vdev->fh_list);
        // 1. 初始化 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;
	case VFL_TYPE_SDR:
		/* Use device name 'swradio' because 'sdr' was already taken. */
		name_base = "swradio";
		break;
	case VFL_TYPE_TOUCH:
		name_base = "v4l-touch";
		break;
	default:
		pr_err("%s called with unknown type: %d\n",
		       __func__, type);
		return -EINVAL;
	}
        // 2. 检查设备类型

	vdev->vfl_type = type;
	vdev->cdev = NULL;
	if (vdev->dev_parent == NULL)
		vdev->dev_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;

	/* 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) {
		pr_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_devices[i] == NULL)
			break;
	if (i == VIDEO_NUM_DEVICES) {
		mutex_unlock(&videodev_lock);
		pr_err("could not get a free minor\n");
		return -ENFILE;
	}
#endif
	vdev->minor = i + minor_offset;
	vdev->num = nr;
        // 3. 寻找一个不在使用的 次设备号, 主设备号为 81,(0~63 为video)(128,191 为sub-dev)

	/* Should not happen since we thought this minor was free */
	if (WARN_ON(video_devices[vdev->minor])) {
		mutex_unlock(&videodev_lock);
		pr_err("video_device not empty!\n");
		return -ENFILE;
	}
	devnode_set(vdev);
	vdev->index = get_index(vdev);
	video_devices[vdev->minor] = vdev;
	mutex_unlock(&videodev_lock);

	if (vdev->ioctl_ops)
		determine_valid_ioctls(vdev);
        // 4. 获取 index,将当前需要注册的 video_device 设备保存在 video_device[]全局数组中

	/* Part 3: Initialize the character device */
	vdev->cdev = cdev_alloc();
	if (vdev->cdev == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		pr_err("%s: cdev_add failed\n", __func__);
		kfree(vdev->cdev);
		vdev->cdev = NULL;
		goto cleanup;
	}
        // 5. 分配对应的字符设备 /dev/video0,字符设备号,就是前面的 (81,minor)

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &video_class;
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
	vdev->dev.parent = vdev->dev_parent;
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);
	if (ret < 0) {
		pr_err("%s: device_register failed\n", __func__);
		goto cleanup;
	}
        // 6. 分配对应的sys节点 /sys/class/video4linux/video0

	/* 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)
		pr_warn("%s: requested %s%d, got %s\n", __func__,
			name_base, nr, video_device_node_name(vdev));

	/* Increase v4l2_device refcount */
	v4l2_device_get(vdev->v4l2_dev);
        // 7. 注册release 时调用的函数

	/* Part 5: Register the entity. */
	ret = video_register_media_controller(vdev);
        // 8. 将该 v4l2 subdevice 当成一个 entity 注册到 media device

	/* Part 6: Activate this minor. The char device can now be used. */
	set_bit(V4L2_FL_REGISTERED, &vdev->flags);

	return 0;

cleanup:
	mutex_lock(&videodev_lock);
	if (vdev->cdev)
		cdev_del(vdev->cdev);
	video_devices[vdev->minor] = NULL;
	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);

字符设备操作函数 v4l2_fops:

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,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

int __video_register_device(struct video_device *vdev,
			    enum vfl_devnode_type type,
			    int nr, int warn_if_nr_in_use,
			    struct module *owner)
    ...
    /* Part 3: Initialize the character device */
	vdev->cdev = cdev_alloc();
	if (vdev->cdev == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		pr_err("%s: cdev_add failed\n", __func__);
		kfree(vdev->cdev);
		vdev->cdev = NULL;
		goto cleanup;
	}
    ...
}

设备注册流程

以vivid虚拟驱动为例

static int vivid_probe(struct platform_device *pdev)
{
	const struct font_desc *font = find_font("VGA8x16");
	int ret = 0, i;

	if (font == NULL) {
		pr_err("vivid: could not find font\n");
		return -ENODEV;
	}

	tpg_set_font(font->data);

	n_devs = clamp_t(unsigned, n_devs, 1, VIVID_MAX_DEVS);

	for (i = 0; i < n_devs; i++) {
		ret = vivid_create_instance(pdev, i);
		if (ret) {
			/* If some instantiations succeeded, keep driver */
			if (i)
				ret = 0;
			break;
		}
	}

	if (ret < 0) {
		pr_err("vivid: error %d while loading driver\n", ret);
		return ret;
	}

	/* n_devs will reflect the actual number of allocated devices */
	n_devs = i;

	return ret;
}

static struct platform_device vivid_pdev = {
	.name		= "vivid",
	.dev.release	= vivid_pdev_release,
};

static struct platform_driver vivid_pdrv = {
	.probe		= vivid_probe,
	.remove		= vivid_remove,
	.driver		= {
		.name	= "vivid",
	},
};

static int __init vivid_init(void)
{
	int ret;

	ret = platform_device_register(&vivid_pdev);
	if (ret)
		return ret;

	ret = platform_driver_register(&vivid_pdrv);
	if (ret)
		platform_device_unregister(&vivid_pdev);

	return ret;
}

module_init(vivid_init);

vivid_create_instance完成了完整的注册工作:

static int vivid_create_instance(struct platform_device *pdev, int inst)
{
    ...
    struct vivid_dev *dev;
    struct video_device *vfd;
    ...
    /* allocate main vivid state structure */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    ...
    /* register v4l2_device */
    snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
			"%s-%03d", VIVID_MODULE_NAME, inst);
    ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
    ...
    //dev结构体的一些初始化
    ...
    if (dev->has_vid_cap) {
	vfd = &dev->vid_cap_dev;
	vfd->fops = &vivid_fops;
	vfd->ioctl_ops = &vivid_ioctl_ops;
	vfd->device_caps = dev->vid_cap_caps;
	vfd->release = video_device_release_empty;
	vfd->v4l2_dev = &dev->v4l2_dev;
	vfd->queue = &dev->vb_vid_cap_q;
	vfd->tvnorms = tvnorms_cap;

	/*
	* Provide a mutex to v4l2 core. It will be used to protect
	* all fops and v4l2 ioctls.
	*/
	vfd->lock = &dev->mutex;
	video_set_drvdata(vfd, dev);



	ret = video_register_device(vfd, VFL_TYPE_GRABBER, vid_cap_nr[inst]);
	if (ret < 0)
		goto unreg_dev;
	v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s\n",
				video_device_node_name(vfd));
    }

open流程

/* Override for the open function */
static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	int ret = 0;

	/* Check if the video device is available */
	mutex_lock(&videodev_lock);
	vdev = video_devdata(filp);
	/* return ENODEV if the video device has already been removed. */
	if (vdev == NULL || !video_is_registered(vdev)) {
		mutex_unlock(&videodev_lock);
		return -ENODEV;
	}
	/* and increase the device refcount */
	video_get(vdev);
	mutex_unlock(&videodev_lock);
	if (vdev->fops->open) {
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);
		else
			ret = -ENODEV;
	}

	if (vdev->dev_debug & V4L2_DEV_DEBUG_FOP)
		dprintk("%s: open (%d)\n",
			video_device_node_name(vdev), ret);
	/* decrease the refcount in case of an error */
	if (ret)
		video_put(vdev);
	return ret;
}

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,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

转到v4l2_device中

int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
	struct video_device *vdev;
	struct v4l2_subdev *sd;
	int err;

	/* Register a device node for every subdev marked with the
	 * V4L2_SUBDEV_FL_HAS_DEVNODE flag.
	 */
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
			continue;

		if (sd->devnode)
			continue;

		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
		if (!vdev) {
			err = -ENOMEM;
			goto clean_up;
		}

		video_set_drvdata(vdev, sd);
		strscpy(vdev->name, sd->name, sizeof(vdev->name));
		vdev->dev_parent = sd->dev;
		vdev->v4l2_dev = v4l2_dev;
		vdev->fops = &v4l2_subdev_fops;
		vdev->release = v4l2_device_release_subdev_node;
		vdev->ctrl_handler = sd->ctrl_handler;
		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
					      sd->owner);
		if (err < 0) {
			kfree(vdev);
			goto clean_up;
		}
		sd->devnode = vdev;
	}
	return 0;
}


static int subdev_open(struct file *file)
{
	struct video_device *vdev = video_devdata(file);
	struct v4l2_subdev *sd = vdev_to_v4l2_subdev(vdev);
	struct v4l2_subdev_fh *subdev_fh;
	int ret;

	subdev_fh = kzalloc(sizeof(*subdev_fh), GFP_KERNEL);
	if (subdev_fh == NULL)
		return -ENOMEM;

	ret = subdev_fh_init(subdev_fh, sd);
	if (ret) {
		kfree(subdev_fh);
		return ret;
	}

	v4l2_fh_init(&subdev_fh->vfh, vdev);
	v4l2_fh_add(&subdev_fh->vfh);
	file->private_data = &subdev_fh->vfh;


	if (sd->internal_ops && sd->internal_ops->open) {
		ret = sd->internal_ops->open(sd, subdev_fh);
		if (ret < 0)
			goto err;
	}
	return 0;
}

ioctl流程

static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(filp);
	int ret = -ENODEV;

	if (vdev->fops->unlocked_ioctl) {
		if (video_is_registered(vdev))
			ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
	} else
		ret = -ENOTTY;

	return ret;
}

转到vivid_core.c中

static const struct v4l2_file_operations vivid_radio_fops = {
	.owner		= THIS_MODULE,
	.open           = v4l2_fh_open,
	.release        = vivid_fop_release,
	.read           = vivid_radio_read,
	.write          = vivid_radio_write,
	.poll		= vivid_radio_poll,
	.unlocked_ioctl = video_ioctl2,
};


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);

转到v4L2-ioctl.c

static long __video_do_ioctl(struct file *file,
		unsigned int cmd, void *arg)
{
    ...
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    const struct v4l2_ioctl_info *info;
    ...
    if (info != &default_info) {
	ret = info->func(ops, file, fh, arg);
    } else if (!ops->vidioc_default) {
	ret = -ENOTTY;
    } else {
	ret = ops->vidioc_default(file, fh,
		vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
		cmd, arg);
}

static const struct v4l2_ioctl_info v4l2_ioctls[] = {
    IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
    IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
    IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
    IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
    IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
    IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
    IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
    ...
}

static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_format *p = arg;
	struct video_device *vfd = video_devdata(file);
	int ret = check_fmt(file, p->type);
	unsigned int i;

	if (ret)
		return ret;

	ret = v4l_enable_media_source(vfd);
	if (ret)
		return ret;
	v4l_sanitize_format(p);

	switch (p->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		if (unlikely(!ops->vidioc_s_fmt_vid_cap))
			break;
		CLEAR_AFTER_FIELD(p, fmt.pix);
		ret = ops->vidioc_s_fmt_vid_cap(file, fh, arg);
		/* just in case the driver zeroed it again */
		p->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC;
		if (vfd->vfl_type == VFL_TYPE_TOUCH)
			v4l_pix_format_touch(&p->fmt.pix);
		return ret;
...

回到vivid-core.c

static const struct v4l2_ioctl_ops vivid_ioctl_ops = {
	.vidioc_querycap		= vidioc_querycap,

	.vidioc_enum_fmt_vid_cap	= vivid_enum_fmt_vid,
	.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_g_fmt_vid_cap_mplane	= vidioc_g_fmt_vid_cap_mplane,
	.vidioc_try_fmt_vid_cap_mplane	= vidioc_try_fmt_vid_cap_mplane,
        ...
}

内存申请流程

前面ioctl流程是一样的,直接到v4l2-ioctl.c

static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_requestbuffers *p = arg;
	int ret = check_fmt(file, p->type);

	if (ret)
		return ret;

	CLEAR_AFTER_FIELD(p, capabilities);

	return ops->vidioc_reqbufs(file, fh, p);
}

这里调用到vivid驱动里面,然后转到videobuf2-v4l2.c

int vb2_ioctl_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	struct video_device *vdev = video_devdata(file);
	int res = vb2_verify_memory_type(vdev->queue, p->memory, p->type);

	fill_buf_caps(vdev->queue, &p->capabilities);
	if (res)
		return res;
	if (vb2_queue_is_busy(vdev, file))
		return -EBUSY;
	res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);
	/* If count == 0, then the owner has released all buffers and he
	   is no longer owner of the queue. Otherwise we have a new owner. */
	if (res == 0)
		vdev->queue->owner = p->count ? file->private_data : NULL;
	return res;
}

转到videobuf2-core.c

int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
		unsigned int *count)
{
	unsigned int num_buffers, allocated_buffers, num_planes = 0;
	unsigned plane_sizes[VB2_MAX_PLANES] = { };
	unsigned int i;
	int ret;

	if (*count == 0 || q->num_buffers != 0 ||
	    (q->memory != VB2_MEMORY_UNKNOWN && q->memory != memory)) {
                //如果vb2_queue中还有buffer。对这些buffer进行检查释放
		/*
		 * We already have buffers allocated, so first check if they
		 * are not in use and can be freed.
		 */
		mutex_lock(&q->mmap_lock);
		if (debug && q->memory == VB2_MEMORY_MMAP &&
		    __buffers_in_use(q))
			dprintk(1, "memory in use, orphaning buffers\n");

		/*
		 * Call queue_cancel to clean up any buffers in the
		 * QUEUED state which is possible if buffers were prepared or
		 * queued without ever calling STREAMON.
		 */
		__vb2_queue_cancel(q);
		ret = __vb2_queue_free(q, q->num_buffers);
		mutex_unlock(&q->mmap_lock);
		if (ret)
			return ret;

		/*
		 * In case of REQBUFS(0) return immediately without calling
		 * driver's queue_setup() callback and allocating resources.
		 */
		if (*count == 0)  //如果申请分配的count为0,不做任何操作
			return 0;
	}

	/*
	 * Make sure the requested values and current defaults are sane.
	 */
	WARN_ON(q->min_buffers_needed > VB2_MAX_FRAME);
	num_buffers = max_t(unsigned int, *count, q->min_buffers_needed);
	num_buffers = min_t(unsigned int, num_buffers, VB2_MAX_FRAME);
	memset(q->alloc_devs, 0, sizeof(q->alloc_devs));
	q->memory = 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, &num_buffers, &num_planes,
		       plane_sizes, q->alloc_devs);
	if (ret)
		return ret;

	/* Check that driver has set sane values */
	if (WARN_ON(!num_planes))
		return -EINVAL;

	for (i = 0; i < num_planes; i++)
		if (WARN_ON(!plane_sizes[i]))
			return -EINVAL;

	/* Finally, allocate buffers and video memory */
	allocated_buffers =
		__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}

	/*
	 * There is no point in continuing if we can't allocate the minimum
	 * number of buffers needed by this vb2_queue.
	 */
	if (allocated_buffers < q->min_buffers_needed)
		ret = -ENOMEM;

	/*
	 * Check if driver can handle the allocated number of buffers.
	 */
	if (!ret && allocated_buffers < num_buffers) {
		num_buffers = allocated_buffers;
		/*
		 * num_planes is set by the previous queue_setup(), but since it
		 * signals to queue_setup() whether it is called from create_bufs()
		 * vs reqbufs() we zero it here to signal that queue_setup() is
		 * called for the reqbufs() case.
		 */
		num_planes = 0;

		ret = call_qop(q, queue_setup, q, &num_buffers,
			       &num_planes, plane_sizes, q->alloc_devs);

		if (!ret && allocated_buffers < num_buffers)
			ret = -ENOMEM;

		/*
		 * Either the driver has accepted a smaller number of buffers,
		 * or .queue_setup() returned an error
		 */
	}

	mutex_lock(&q->mmap_lock);
	q->num_buffers = allocated_buffers;

	if (ret < 0) {
		/*
		 * Note: __vb2_queue_free() will subtract 'allocated_buffers'
		 * from q->num_buffers.
		 */
		__vb2_queue_free(q, allocated_buffers);
		mutex_unlock(&q->mmap_lock);
		return ret;
	}
	mutex_unlock(&q->mmap_lock);

	/*
	 * Return the number of successfully allocated buffers
	 * to the userspace.
	 */
	*count = allocated_buffers;
	q->waiting_for_buffers = !q->is_output;

	return 0;
}
EXPORT_SYMBOL_GPL(vb2_core_reqbufs);


static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
			     unsigned int num_buffers, unsigned int num_planes,
			     const unsigned plane_sizes[VB2_MAX_PLANES])
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;
	int ret;

	/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
	num_buffers = min_t(unsigned int, num_buffers,
			    VB2_MAX_FRAME - q->num_buffers);

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

		vb->state = VB2_BUF_STATE_DEQUEUED;
		vb->vb2_queue = q;
		vb->num_planes = num_planes;
		vb->index = q->num_buffers + buffer;
		vb->type = q->type;
		vb->memory = memory;
		for (plane = 0; plane < num_planes; ++plane) {
			vb->planes[plane].length = plane_sizes[plane];
			vb->planes[plane].min_length = plane_sizes[plane];
		}
		call_void_bufop(q, init_buffer, vb);

		q->bufs[vb->index] = vb;

		/* Allocate video buffer memory for the MMAP type */
		if (memory == VB2_MEMORY_MMAP) {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for buffer %d\n",
					buffer);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
			__setup_offsets(vb);
			/*
			 * Call the driver-provided buffer initialization
			 * callback, if given. An error in initialization
			 * results in queue setup failure.
			 */
			ret = call_vb_qop(vb, buf_init, vb);
			if (ret) {
				dprintk(1, "buffer %d %p initialization failed\n",
					buffer, vb);
				__vb2_buf_mem_free(vb);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
		}
	}

	dprintk(1, "allocated %d buffers, %d plane(s) each\n",
			buffer, num_planes);

	return buffer;
}



static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	void *mem_priv;
	int plane;
	int ret = -ENOMEM;

	/*
	 * Allocate memory for all planes in this buffer
	 * NOTE: mmapped areas should be page aligned
	 */
	for (plane = 0; plane < vb->num_planes; ++plane) {
		/* Memops alloc requires size to be page aligned. */
		unsigned long size = PAGE_ALIGN(vb->planes[plane].length);

		/* Did it wrap around? */
		if (size < vb->planes[plane].length)
			goto free;

		mem_priv = call_ptr_memop(vb, alloc,
				q->alloc_devs[plane] ? : q->dev,
				q->dma_attrs, size, q->dma_dir, q->gfp_flags);
		if (IS_ERR_OR_NULL(mem_priv)) {
			if (mem_priv)
				ret = PTR_ERR(mem_priv);
			goto free;
		}

		/* Associate allocator private data with this plane */
		vb->planes[plane].mem_priv = mem_priv;
	}

	return 0;
free:
	/* Free already allocated memory if one of the allocations failed */
	for (; plane > 0; --plane) {
		call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
		vb->planes[plane - 1].mem_priv = NULL;
	}

	return ret;
}

streaming on流程

细节

UVC-usb摄像头流程分析

  • UVC: USB Video Class
  • UVC驱动:drivers\media\video\uvc\
  • www.usb.org可以下载 uvc specification,UVC 1.5 Class specification.pdf : 有详细描述

驱动注册

--------------------------------------uvc_driver.c----------------------------------------
1. usb_register(&uvc_driver.driver);
2. uvc_probe
        uvc_register_video
            vdev = video_device_alloc();
            vdev->fops = &uvc_fops;
            video_register_device

struct uvc_driver uvc_driver = {
	.driver = {
		.name		= "uvcvideo",
		.probe		= uvc_probe,
		.disconnect	= uvc_disconnect,
		.suspend	= uvc_suspend,
		.resume		= uvc_resume,
		.reset_resume	= uvc_reset_resume,
		.id_table	= uvc_ids,
		.supports_autosuspend = 1,
	},
};

static int __init uvc_init(void)
{
	int ret;

	uvc_debugfs_init();

	ret = usb_register(&uvc_driver.driver);
	if (ret < 0) {
		uvc_debugfs_cleanup();
		return ret;
	}

	printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
	return 0;
}

设备操作

--------------------------------------uvc_v4l2.c----------------------------------------
const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.unlocked_ioctl	= video_ioctl2,
#ifdef CONFIG_COMPAT
	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU
	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

v4l-utils

下载(Index of /downloads/v4l-utils (linuxtv.org))

$ wget http://linuxtv.org/downloads/v4l-utils/v4l-utils-x.y.z.tar.bz2
$ tar xvfj v4l-utils-x.y.z.tar.bz2
$ cd v4l-utils-x.y.z

编译

./configure
make
sudo make install

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码论斤卖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值