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 supp...
本文通过介绍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注册流程分析,对流程了解以后,其他平台主要也都会包含这些步骤,可能会有些平台型的差异了。
转载请注明出处!
更多推荐
所有评论(0)