Video for Linux 2,简称V4l2,是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
首先来看看所有的v4l2驱动都必须要有的几个组成部分:
– 用来描述每一个v4l2设备实例状态的结构(struct v4l2_device)。
– 用来初始化和控制子设备的方法(struct v4l2_subdev)。
– 要能创建设备节点(/dev/videoX、/dev/vbiX 和 /dev/radioX)并且能够对该节点所持有的数据进行跟踪(structvideo_device)。
– 为每一个被打开的节点维护一个文件句柄(structv4l2_fh)。
– 视频缓冲区的处理(videobuf或者videobuf2 framework)。
用一个比较粗糙的图来表现他们之间的关系,大致为:
设备实例(v4l2_device)
|__子设备实例(v4l2_subdev)
|__视频设备节点(video_device)
|__文件访问控制(v4l2_fh)
|__视频缓冲的处理(videobuf/videobuf2)
许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责 音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。
这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的 统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。
在别的文章看到的图,觉得还不错,贴一下:
可以看出:
每个子设备驱动都必须有一个 v4l2_subdev 结构体(实际的硬件设备都被抽象为v4l2_subdev),代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态 信息保存在一起。
v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设备。
因为子设备千差万别,所以v4l2-device又向上层提供一个标准的接口。所以可以认为v4l2-device就是一个中间层。
在说v4l2之前,先说下uvc吧:
USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机。
V4L2就是用来管理UVC设备的并且能够提供视频相关的一些API
我们以Linux kernel 4.8.17为例,分析下实现过程:
drivers\media\usb\uvc\uvc_driver.c文件:
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,//支持的video设备插入就会进入
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
当特定的usb设备被插入时,就会触发probe函数:
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct uvc_device *dev;
int ret;
/*省略部分内容*/
if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)//【1】
return -ENOMEM;
/*省略部分内容*/
dev->udev = usb_get_dev(udev);//【2】
dev->intf = usb_get_intf(intf);
dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
dev->quirks = (uvc_quirks_param == -1)
? id->driver_info : uvc_quirks_param;
/*省略部分内容*/
/* Parse the Video Class control descriptor. */
if (uvc_parse_control(dev) < 0) {//【3】
uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
"descriptors.\n");
goto error;
}
/*省略部分内容*/
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)//【4】
goto error;
/* Initialize controls. */
if (uvc_ctrl_init_device(dev) < 0)//【5】
goto error;
/* Scan the device for video chains. */
if (uvc_scan_device(dev) < 0)
goto error;
/* Register video device nodes. */
if (uvc_register_chains(dev) < 0)//【6】
goto error;
/*省略部分内容*/
/* Initialize the interrupt URB. */
if ((ret = uvc_status_init(dev)) < 0) {//【7】uvc状态的处理由中断端点来控制处理
/*省略部分内容*/
return 0;
error:
uvc_unregister_video(dev);
return -ENODEV;
}
函数太长了,省略了部分内容,但是可以看出,主要的就是做几件事情:
【1】分配一个dev
【2】给dev设置各种参数,如dev->udevudev
【3】调用uvc_parse_control函数分析设备的控制描述符
【4】调用v4l2_device_register函数初始化v4l2_dev
【5】调用uvc_ctrl_init_device函数初始化uvc控制设备
【6】调用uvc_register_chains函数注册所有通道
【7】调用uvc_status_init函数初始化uvc状态
我们来一个个分析下:
【3】:调用uvc_parse_control函数
看下调用关系:
uvc_parse_control(dev) uvc_parse_standard_control(dev, buffer, buflen) uvc_parse_streaming(dev, intf)
跟踪下uvc_parse_streaming函数:
static int uvc_parse_streaming(struct uvc_device *dev,
struct usb_interface *intf)
{
/*以下大部分内容省略,只显示重要的*/
struct uvc_streaming *streaming = NULL;
struct uvc_format *format;
struct uvc_frame *frame;
streaming = kzalloc(sizeof *streaming, GFP_KERNEL);
size = nformats * sizeof *format + nframes * sizeof *frame
+ nintervals * sizeof *interval;
format = kzalloc(size, GFP_KERNEL);//申请format数组存放格式
streaming->format = format;//设置格式
streaming->nformats = nformats;//最多支持nformats种格式
ret = uvc_parse_format(dev, streaming, format,
&interval, buffer, buflen);//分析格式
list_add_tail(&streaming->list, &dev->streams);
return 0;
}
这里面申请了streaming和format内存
streaming是uvc_streaming 结构体,视频流,很重要,大部分参数都是存在里面。这函数里申请了之后进行了很多设置,不过现在我省略了写。
format内存存放的是视频的格式,frame存放的是如分辨率
这里面都把他设置到了streaming里面(streaming->format = format;streaming->nformats = nformats;)
最后调用uvc_parse_format函数分析格式:
static int uvc_parse_format()
{
fmtdesc = uvc_format_by_guid(&buffer[5]);//通过GUID找到格式format
/*里面还会对frame进行各种分析和设置, *如设置format->nframes得出最多有多少种分辨率选择 *暂时忽略*/
}
里面uvc_format_by_guid函数会从uvc_fmts数组中通过匹配guid找到格式:
static struct uvc_format_desc uvc_fmts[] = {
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:0 (NV12)",
.guid = UVC_GUID_FORMAT_NV12,
.fcc = V4L2_PIX_FMT_NV12,
},
{
.name = "MJPEG",
.guid = UVC_GUID_FORMAT_MJPEG,
.fcc = V4L2_PIX_FMT_MJPEG,
},
/*后面省略......*/
}
.
这样【3】的工作就完成了,我们来看下【4】的:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
INIT_LIST_HEAD(&v4l2_dev->subdevs);//用来管理v4l2_device 下的subdevs实例
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
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->driver_data 域 为 NULL
dev_set_drvdata(dev, v4l2_dev);//就将其指向 v4l2_dev
return 0;
}
简单,没啥好讲的,就是初始化v4l2_dev->subdevs子设备实例的链表,然后设置名字和设置dev->driver_data
看下【5】调用uvc_ctrl_init_device
int uvc_ctrl_init_device(struct uvc_device *dev)
{
/*省略了部分内容*/
list_for_each_entry(entity, &dev->entities, list) {
bmControls = entity->extension.bmControls;//控制位图
bControlSize = entity->extension.bControlSize;//控制位域大小
entity->controls = kcalloc(ncontrols, sizeof(*ctrl),
GFP_KERNEL);//分配ncontrols个uvc控制内存
if (entity->controls == NULL)
return -ENOMEM;
entity->ncontrols = ncontrols;//设置uvc控制个数
/* Initialize all supported controls */
ctrl = entity->controls;//指向uvc控制数组
for (i = 0; i < bControlSize * 8; ++i) {
if (uvc_test_bit(bmControls, i) == 0)//跳过控制位域设置0的
continue;
ctrl->entity = entity;
ctrl->index = i;//设置控制位域索引
uvc_ctrl_init_ctrl(dev, ctrl);//初始化uvc控件
ctrl++;//uvc控制 指向下一个uvc控制数组项
}
}
}
uvc_ctrl_init_device主要就是初始化控制参数,里面就会遍历uvc设备实体entities链表,然后设置位图和位域大小
最后还会调用uvc_ctrl_init_ctrl函数设置背光,色温等等
接下来继续看【6】调用uvc_register_chains函数:
调用关系:
uvc_register_chains
uvc_register_terms(dev, chain)
uvc_stream_by_id
uvc_register_video
uvc_mc_register_entities(chain)
uvc_stream_by_id函数会通过函数传入的id和dev->streams链表的header.bTerminalLink匹配,寻找到stream
这不是重点,我们的重点是uvc_register_video函数,找到stream会就要注册:
static int uvc_register_video(struct uvc_device *dev,
struct uvc_streaming *stream)
{
/*部分内容省略......*/
struct video_device *vdev = &stream->vdev;
ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);//初始化队列
ret = uvc_video_init(stream);//初始化
uvc_debugfs_init_stream(stream);
vdev->v4l2_dev = &dev->vdev;
vdev->fops = &uvc_fops;//v4l2操作函数集
vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集
vdev->release = uvc_release;//释放方法
vdev->prio = &stream->chain->prio;
strlcpy(vdev->name, dev->name, sizeof vdev->name);
video_set_drvdata(vdev, stream);//将uvc视频流作为v4l2设备的驱动数据
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);//注册
return 0;
}
这是非常重要的函数,我们来一点一点分析:
看下uvc_queue_init函数,队列初始化,队列这东西,我们视频传输时会调用到,在ioctl里操作:
static struct vb2_ops uvc_queue_qops = {
.queue_setup = uvc_queue_setup,
.buf_prepare = uvc_buffer_prepare,
.buf_queue = uvc_buffer_queue,
.buf_finish = uvc_buffer_finish,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.start_streaming = uvc_start_streaming,
.stop_streaming = uvc_stop_streaming,
};
int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
int drop_corrupted)
{
queue->queue.type = type;
queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
queue->queue.drv_priv = queue;
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
queue->queue.ops = &uvc_queue_qops;//stream->queue->queue.ops
queue->queue.mem_ops = &vb2_vmalloc_memops;
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
| V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
queue->queue.lock = &queue->mutex;
ret = vb2_queue_init(&queue->queue);//初始化queue
mutex_init(&queue->mutex);
spin_lock_init(&queue->irqlock);
INIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue
queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
return 0;
}
里面先对队列进行初始化设置,如设置type和ops。
这里queue->queue.ops = &uvc_queue_qops非常重要,之后我们调用vidioc_streamon回调函数时就是调用到这里的uvc_queue_qops结构体里的.start_streaming函数
这函数里对各种队列进行了初始化:
vb2_queue_init(&queue->queue)
q->buf_ops = &v4l2_buf_ops;
vb2_core_queue_init(struct vb2_queue *q)
INIT_LIST_HEAD(&q->queued_list);//stream->queue->queue->queued_list
INIT_LIST_HEAD(&q->done_list);//stream->queue->done_list
INIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue
我们继续看回uvc_register_video函数,里面接着调用了uvc_video_init函数初始化UVC视频设备:
int uvc_video_init(struct uvc_streaming *stream)
{
/*省略部分内容*/
struct uvc_streaming_control *probe = &stream->ctrl;//获取uvc数据流的uvs数据流控制对象
if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)//先得到定义的控制参数
uvc_set_video_ctrl(stream, probe, 1);//再设置uvc视频控制
ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);//最后在get一次
for (i = stream->nformats; i > 0; --i) {
format = &stream->format[i-1];//获取对应的uvc格式
if (format->index == probe->bFormatIndex)
break;
}
probe->bFormatIndex = format->index;//设置uvc视频流控制的格式索引为uvc格式的索引
probe->bFrameIndex = frame->bFrameIndex;//设置uvc视频流控制的分辨率索引为uvc分辨率的索引
stream->def_format = format;
stream->cur_format = format;//设置uvc格式为uvc数据流的cur_format成员
stream->cur_frame = frame;//设置uvc帧为uvc数据流的cur_frame成员
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {//视频采集
if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)
stream->decode = uvc_video_decode_isight;
else if (stream->intf->num_altsetting > 1)
stream->decode = uvc_video_decode_isoc;//同步方式
else
stream->decode = uvc_video_decode_bulk;//bluk方式
}
return 0;
}
这里面内容就比较多了,先得到,然后设置uvc的控制参数,里面会操作urb发出usb数据。
然后通过probe->bFormatIndex索引找到使用的format格式和通过probe->bFrameIndex找到对应的frame分辨率,然后设置到stream里。
最后选择解码方式,如同步方式或者bluk方式,解码方式会在数据完成时被回调函数complete里调用。
再次回到uvc_register_video函数,没办法,这个函数太重要了:
里面继续:
vdev->fops = &uvc_fops;//v4l2操作函数集
vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集
vdev->release = uvc_release;//释放方法
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
里面就是vdev->v4l2_dev = &dev->vdev;这样v4l2_device就与video_device关联起来,也就是我们文章一开始那个图看到的。
然后设置fops操作函数vdev->fops = &uvc_fops,虽然这不是给用户空间使用的open、read、write函数,但是最后vdev->cdev->ops还是最调用到这个uvc_fops的,所以用户空间实际上的pen、read、write函数还是会在这调用。 然后ioctl操作函数最终是会调用到vdev->ioctl_ops = &uvc_ioctl_ops。可以说,V4L2最重要的就是各种形式的ioctl了,这里先不讲,下一节在分析看看。
然后最终就是我们的注册函数了:video_register_device里调用到__video_register_device函数:
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
/*省略部分函数*/
vdev->minor = -1;//-1表明这个video device从未被注册过
switch (type) {//根据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:
name_base = "swradio";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n", __func__, type);
return -EINVAL;
}
switch (type) {//选择得到次设备号偏移值
case VFL_TYPE_GRABBER://用于视频输入/输出设备的 videoX
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO://用于广播调谐器的 radioX
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI://用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
nr = devnode_find(vdev, 0, minor_cnt);//获取一个没有被使用的设备节点序号
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)//从video_device[]数组中选择一个空缺项,这个空缺项的索引值放到i中
break;
vdev->minor = i + minor_offset;//设备的次设备号
video_device[vdev->minor] = vdev;//注意:将设置好的video_device放入到video_device[]
vdev->cdev->ops = &v4l2_fops;//操作用户空间操作函数集
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);//添加字符设备到系统
ret = device_register(&vdev->dev);//设备注册
set_bit(V4L2_FL_REGISTERED, &vdev->flags);//将flags第0为设置为1,表示这个video_device是注册过的了
return 0;
}
我们梳理一下里面做的事情:
1.确定设备名称,也就是我们在/dev/下生成的video啊,radio之类的
2.得到次设备的偏移值
3.找到一个空的video_device数组,把vdev存进去
4.设置vdev->cdev,这里就设置了vdev->cdev->ops = &v4l2_fops;里面就是真正的用户空间操作集合
5.注册video_device设备
6.就是标志此video_device以注册
最后【6】调用uvc_register_chains函数里还会调用一个uvc_mc_register_entities函数,里面继续调用uvc_mc_init_entity函数,这就是v4l2_device_register_subdev函数,进行注册v4l2_subdev,同时初始化然后连接到v4l2_dev->subdevs管理。
好了,【6】调用uvc_register_chains函数:就分析完了,我们最后剩一个了:
【7】调用uvc_status_init函数
int uvc_status_init(struct uvc_device *dev)
{
/*省略部分函数*/
struct usb_host_endpoint *ep = dev->int_ep;//获取usb_host_endpoint
uvc_input_init(dev);//初始化uvc输入设备,里面注册input设备
dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);//分配urb设备状态内存
dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);//分配urb
pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress);//中断输入端点
usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,
dev, interval);//填充中断urb
return 0;
}
里面就是关于urb的一些东西了,看看就好。
最后,我们用户空间怎么才操作的?
看看__video_register_device函数里的:vdev->cdev->ops = &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,
};
static int v4l2_open(struct inode *inode, struct file *filp)
{
/*省略部分函数*/
struct video_device *vdev;
vdev = video_devdata(filp);//根据次设备号从video_devices[]数组中得到video_device
if (vdev->fops->open) {
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);//实际就是vdev->fops
else
ret = -ENODEV;
}
}
记得我们之前把video_device放入到video_device[]吗?就是这里取了出来
然后调用vdev->fops->open(filp)
vdev->fops就是我们在uvc_register_video函数里设置的:
vdev->fops = &uvc_fops
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
};
至于这个uvc_fops 里的回调函数,特别是ioctl,这是V4L2的重头,就在下一章试着分析吧,我对这个也是比较模糊……
下一章:嵌入式Linux驱动笔记(十八)——浅析V4L2框架之ioctl