本文通过介绍Linux内核自带的vivid代码,解析Linux camera框架,vivid(virtual video driver)是Linux内核中一个基于v4l2的虚拟video驱动,介绍如下:

    This driver emulates a webcam, TV, S-Video and HDMI capture hardware, including VBI support for the SDTV inputs. 
Also video output, VBI output, radio receivers, transmitters and software defined radio capture is emulated.

    现在的vivid虚拟驱动已经非常完善,包含了camera、TV、S-Video和HDMI驱动,接下来将主要介绍Linux camera框架中,设备节点注册(/dev/videoX),以及在应用层如何通过v4l2的ioctl操作到sensor的硬件,其他类型的设备将会一笔带过。文中讲述的源码版本是linux-4.16.13,而vivid的源码位于drivers/media/platform/vivid。

 

驱动的加载过程

    根据drivers/media/platform/vivid目录下的Makefile可以了解到,该目录源码最终应该是生成vivid模块,假设是生成vivid.ko,设备端加在vivid.ko的时候,将会调用到该模块中通过module_init修饰的函数,这里是调用vivid-core.c中的vivid_init()函数,源码如下:

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

而vivid_pdev以及vivid_pdrv的定义如下:

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

    通过以上代码可以了解到,vivid模块加载的时候,主要就是注册了一个name为vivid的platform_device设备以及加载name为vivid的platform_driver。在Linux中,platform总线在加载设备或者驱动的时候,都将会有一个探测过程,探测是否有匹配的驱动或者设备,匹配成功将会执行其中的probe函数,其中就有可以通过name来匹配的,所以,在这里注册了name为vivid的platform_device和platform_driver,在注册过程,他们将会通过name匹配上,然后调用到platform_driver的probe函数。而vivid驱动的probe函数将会完成相应资源的申请以及video节点的注册等操作。

 

video节点注册

    上面讲到,platform设备与驱动一旦匹配成功,将会执行驱动的probe函数,而vivid驱动的probe函数定义如下:

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

	...

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

	...

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

	return ret;
}

    通过以上看,主要就是通过vivid_create_instance()函数尽可能多的注册video设备节点。vivid_create_instance()函数有点长,下面将一起来分析该函数,看看Linux系统是如何通过v4l2搭建camera框架的。

    在vivid_create_instance()函数中,将会先注册一个v4l2_device设备,

	/* allocate main vivid state structure */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	dev->inst = inst;

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

可以看到,将会通过调用v4l2_device_register()函数,注册一个名为vivid-X的v4l2设备,由于在Linux中,主要是通过v4l2管理camera的众多操作,所以先创建一个v4l2_device,通过v4l2_device,可以创建多个v4l2_device。

 

参数的设置

    接下来,将会通过vivid-core.c中的静态全局变量multiplanar、num_inputs、input_types、num_outputs和output_types决定这将会注册什么类型的设备等,我们主要分析camera相关的。

	if (dev->has_vid_cap) {
		/* set up the capabilities of the video capture device */
		dev->vid_cap_caps = dev->multiplanar ?
			V4L2_CAP_VIDEO_CAPTURE_MPLANE :
			V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY;
		dev->vid_cap_caps |= V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
                ...
	}

    通过以上代码,了解到,将会根据multiplanar的值决定video数据的捕获方式,V4L2_CAP_VIDEO_CAPTURE_MPLANE为将捕捉的数据分为多片存放,分别提供YUV分量的地址(假设视频数据的输出类型为YUV),如果是V4L2_CAP_VIDEO_CAPTURE类型,则将只提供一个数据分量的地址,数据的存放方式是连续的,可以通过分辨率等信息来获取其他分量的地址。

    当设置的数据的存放方式之后,将会设置video Capture的数据格式,代码如下:

/* configure internal data */
dev->fmt_cap = &vivid_formats[0];

而vivid_formats的定义如下:

struct vivid_fmt vivid_formats[] = {
	{
		.fourcc   = V4L2_PIX_FMT_YUYV,
		.vdownsampling = { 1 },
		.bit_depth = { 16 },
		.color_enc = TGP_COLOR_ENC_YCBCR,
		.planes   = 1,
		.buffers = 1,
		.data_offset = { PLANE0_DATA_OFFSET },
	},
        ...
}

这样就定义了这个vivid模块的Capture video支持的输出格式。

 

video之controls

    大家都知道,我们在使用相机的时候,可以动态的调节曝光、白平衡和进行镜像操作等,所以,接下来,vivid的probe函数将通过调用vivid_create_controls()函数,完成这些controls的设置,vivid_create_controls()函数的具体实现是在vivid-ctrls.c文件。

int vivid_create_controls(struct vivid_dev *dev, bool show_ccs_cap,
		bool show_ccs_out, bool no_error_inj,
		bool has_sdtv, bool has_hdmi)
{
    ...
    dev->brightness = v4l2_ctrl_new_std(hdl_user_vid, &vivid_user_vid_ctrl_ops,
        V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
    for (i = 0; i < MAX_INPUTS; i++)
        dev->input_brightness[i] = 128;
    dev->contrast = v4l2_ctrl_new_std(hdl_user_vid, &vivid_user_vid_ctrl_ops,
        V4L2_CID_CONTRAST, 0, 255, 1, 128);
    dev->saturation = v4l2_ctrl_new_std(hdl_user_vid, &vivid_user_vid_ctrl_ops,
        V4L2_CID_SATURATION, 0, 255, 1, 128);
    dev->hue = v4l2_ctrl_new_std(hdl_user_vid, &vivid_user_vid_ctrl_ops,
        V4L2_CID_HUE, -128, 128, 1, 0);
    ...
}

    在vivid_create_controls()函数中,有很多通过v4l2_ctrl_new_std()函数将相应的一些调节操作添加到hdl_user_vid,而hdl_user_vid最后也都将通过v4l2_ctrl_add_handler(hdl_vid_cap, hdl_user_vid, NULL),将自身与dev->ctrl_hdl_vid_cap绑定在一起。

   

video的buf queue管理以及buf内存管理

     完成controls设置的操作之后,在sensor捕捉到的数据buf也需要管理起来,而Linux的camera框架,buf管理一般使用vb2,这个也是基于v4l2的一套buf队列管理机制。vivid的vb2初始化如下:

/* start creating the vb2 queues */
if (dev->has_vid_cap) {
	/* initialize vid_cap queue */
	q = &dev->vb_vid_cap_q;
	q->type = dev->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
		V4L2_BUF_TYPE_VIDEO_CAPTURE;
	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
	q->drv_priv = dev;
	q->buf_struct_size = sizeof(struct vivid_buffer);
	q->ops = &vivid_vid_cap_qops;
	q->mem_ops = vivid_mem_ops[allocator];
	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	q->min_buffers_needed = 2;
	q->lock = &dev->mutex;
	q->dev = dev->v4l2_dev.dev;

	ret = vb2_queue_init(q);
	if (ret)
		goto unreg_dev;
}

    其实在这里也主要是初始化了queue的一些操作集,重要的是queue的管理ops以及内存管理的mem_ops,同时通过drv_priv成员将dev与之相连。

    queue的ops设置为vivid_vid_cap_qops,它的定义如下:

const struct vb2_ops vivid_vid_cap_qops = {
	.queue_setup		= vid_cap_queue_setup,
	.buf_prepare		= vid_cap_buf_prepare,
	.buf_finish		= vid_cap_buf_finish,
	.buf_queue		= vid_cap_buf_queue,
	.start_streaming	= vid_cap_start_streaming,
	.stop_streaming		= vid_cap_stop_streaming,
	.wait_prepare		= vb2_ops_wait_prepare,
	.wait_finish		= vb2_ops_wait_finish,
};

    通过它的定义,我们大概可以猜测到,它主要就是负责buf queue的管理,而queue的mem_ops赋值为vb2_vmalloc_memops,它将负责queue buf的内存管理操作。

   

video的文件操作集合与video节点注册

    在Linux系统操作camera,都是通过操作/dev/videoX节点完成的,以上已经说明了注册一个video具备的输出格式、数据输出类型、controls操作,以及buf queue的管理以及内存等,但是我们一般不会直接操作到这些。video设备,说到底,实际上也是一个字符设备,在Linux系统中,我们操作一个设备,一般都会在注册驱动或者设备时,决定了它支持的操作,而这些,都是通过文件操作集合完成的,所以,接下来,将会真正的video节点注册以及文件操作集合、ioctl与节点的绑定,代码如下:

	/* finally start creating the device nodes */
	if (dev->has_vid_cap) {
		vfd = &dev->vid_cap_dev;
		snprintf(vfd->name, sizeof(vfd->name),
			 "vivid-%03d-vid-cap", inst);
		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;

                ...

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

    从上面可以看到,在设置video_device的fops以及ioctl_ops、v4l2_dev、queue等参数之后,通过video_register_device()函数,注册一个video设备节点,以后用户层对camera的操作,都将会是通过该节点完成。video_register_device()函数主要就是根据传进来的TYPE,分别创建不同名称的设备节点,VFL_TYPE_GRABBER是创建video节点,这样,将通过video_register_device()函数,完成/dev/videoX的设备注册过程。

    vivid注册的video节点的fops定义如下:

static const struct v4l2_file_operations vivid_fops = {
	.owner		= THIS_MODULE,
	.open           = v4l2_fh_open,
	.release        = vivid_fop_release,
	.read           = vb2_fop_read,
	.write          = vb2_fop_write,
	.poll		= vb2_fop_poll,
	.unlocked_ioctl = video_ioctl2,
	.mmap           = vb2_fop_mmap,
};

    当用户层对/dev/videoX进行open、read、write等操作,都经过Linux的系统转换之后,调用到这里来。

    同样的,当用户层对/dev/videoX进行ioctl操作时,也都将会调用到注册video节点时,赋值给ioctl_ops的vivid_ioctl_ops变量中,vivid_ioctl_ops的定义如下:

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

	.vidioc_enum_fmt_vid_cap	= vidioc_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_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane,
	.vidioc_g_fmt_vid_cap_mplane	= vidioc_g_fmt_vid_cap_mplane,
	.vidioc_try_fmt_vid_cap_mplane	= vidioc_try_fmt_vid_cap_mplane,
	.vidioc_s_fmt_vid_cap_mplane	= vidioc_s_fmt_vid_cap_mplane,
        ...
	.vidioc_overlay			= vidioc_overlay,
	.vidioc_enum_framesizes		= vidioc_enum_framesizes,
	.vidioc_enum_frameintervals	= vidioc_enum_frameintervals,
	.vidioc_g_parm			= vidioc_g_parm,
	.vidioc_s_parm			= vidioc_s_parm,
        ...
	.vidioc_enum_fmt_vid_overlay	= vidioc_enum_fmt_vid_overlay,
	.vidioc_g_fmt_vid_overlay	= vidioc_g_fmt_vid_overlay,
	.vidioc_try_fmt_vid_overlay	= vidioc_try_fmt_vid_overlay,
	.vidioc_s_fmt_vid_overlay	= vidioc_s_fmt_vid_overlay,

	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
	.vidioc_querybuf		= vb2_ioctl_querybuf,
	.vidioc_qbuf			= vb2_ioctl_qbuf,
	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
	.vidioc_streamon		= vb2_ioctl_streamon,
	.vidioc_streamoff		= vb2_ioctl_streamoff,

	.vidioc_enum_input		= vidioc_enum_input,
	.vidioc_g_input			= vidioc_g_input,
	.vidioc_s_input			= vidioc_s_input,
        ...
};

    以上,完成了Linux系统的camera video设备的注册流程,vivid模块成功加载之后,将会看到机器端的/dev目录下存在video节点,之后就可以通过应用程序操作/dev/videoX节点完成摄像头的各类操作了。

    由于各个平台存在具体差异,所以使用Linux内核自带的vivid这样一个虚拟的video模块对camera注册流程分析,对流程了解以后,其他平台主要也都会包含这些步骤,可能会有些平台型的差异了。

 

 

转载请注明出处!

Logo

更多推荐