Linux camera注册流程分析

本文通过介绍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注册流程分析,对流程了解以后,其他平台主要也都会包含这些步骤,可能会有些平台型的差异了。

作者:只为道
来源:CSDN
原文:https://blog.csdn.net/weixin_41944449/article/details/80556222
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值