Linux系统中USB摄像头一般都采用的是UVC硬件架构设计,因此我们在分析USB摄像头驱动的重点在于分析UVC框架。它融合了usb驱动框架和v4l2驱动框架,我们可以从中抽象出一个通用的USB摄像头驱动框架。
uvc架构官方手册参考网上:uvc 1.5 specifications
http://www.usb.org/developers/docs/devclass_docs/ 的Video Class的Video Class 1.5 document set
只需查看UVC 1.5 Class specification 和 USB_Video_Example 1.5
在USB_Video_Example 1.5 里的12页USB Video Camera Topology 即可大致了解USB摄像头大致框架
Video functions are addressed through their video interfaces. Each video function has a single
VideoControl (VC) interface and can have several VideoStreaming (VS) interfaces. The
VideoControl (VC) interface is used to access the device controls of the function whereas the
VideoStreaming (VS) interfaces are used to transport data streams into and out of the function.
这个uvc框架包含两大接口:
1. VideoControl (VC) interface:
控制接口 包含 CT(Camera Terminal) IT(Input Terminal) OT(Output Terminal) SU(Selector Unit) PU (Process Unit)
2.VideoStreaming (VS)interface:
视频数据接口,主要产生视频数据
一、USB摄像头驱动框架:
1.构造一个usb_driver结构体
2.设置
在probe函数中:
分配video_device :video_device_alloc
设置:fops,ioctl_fops(里面有11项)
如果要用内核提供的缓冲区操作函数,还要构造一个video_buf_queue_ops
注册:video_register_device
id_table:表示支持哪些usb设备
3.注册
在Linux内核里自带usb摄像头驱动程序,它支持uvc规格的摄像头(uvc即usb video class),它在drivers/media/video/uvc 下的uvc_driver.c。
以下是uvc_driver.c 分析:
从入口函数开始
uvc_init
usb_register(&uvc_driver.driver);
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,
},
};
在uvc_probe中:
uvc_register_video
video_device_alloc
vdev->fops = &uvc_fops;
const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl= uvc_v4l2_ioctl,
#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
};
注意:在.unlocked_ioctl = uvc_v4l2_ioctl中肯定包含了11项ioctl(它并没有直接分配一个ioctl结构体,只是一个函数,在函数中用switch 语句包含至少11项ioctl。)
二、深入分析应用程序调用过程
1.open过程:
uvc_v4l2_open //一系列设置
2.ioctl:
video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl); //将用户提供的参数拷贝到内核态,调用uvc_v4l2_do_ioctl
uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
以下是一系列ioctl:
switch (cmd)
case VIDIOC_QUERYCAP:
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) //stream->type 根据设备被枚举时分析描述符时设置的
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_STREAMING;
else
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
| V4L2_CAP_STREAMING;
case VIDIOC_ENUM_FMT:
format = &video->streaming->format[fmt->index];
case VIDIOC_G_FMT:
return uvc_v4l2_get_format(video, arg);
case VIDIOC_TRY_FMT:
uvc_v4l2_try_format(video, arg, &probe, NULL, NULL);
/* Check if the hardware supports the requested format. */
/* Find the closest image size.*/找到最接近的图像格式
case VIDIOC_S_FMT:只是把参数保存起来,不涉及发给usb硬件的操作。
uvc_v4l2_set_format(video, arg);
//先调用uvc_v4l2_try_format(video, fmt, &probe, &format, &frame)上面那个函数来尝试打开。只不过参数不一样。
//把合适的格式等信息暂存起来,以后发给硬件。
memcpy(&video->streaming->ctrl, &probe, sizeof probe);
video->streaming->cur_format = format;
video->streaming->cur_frame = frame;
/* Buffers & streaming */
case VIDIOC_REQBUFS:
//bufsize已经由上面设置好了,alloc时只需传进来应用程序的rb->count数值
unsigned int bufsize =video->streaming->ctrl.dwMaxVideoFrameSize;
uvc_alloc_buffers(&video->queue, rb->count, bufsize);
/* 根据rb->count来分配多少个bufDecrement the number of buffers until allocation succeeds. */
for (; rb->count > 0; --rb->count) {
mem = vmalloc_32(nbuffers * bufsize);
if (mem != NULL)
break;
}
case VIDIOC_QUERYBUF: //查询buf参数
uvc_query_buffer(&video->queue, buf);
__uvc_query_buffer
memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);//复制参数
mmap: 查询buf参数后使用mmap把buf映射给应用程序
uvc_v4l2_mmap
case VIDIOC_QBUF: //把buf放入队列,当有数据之后把buf从队列里面取出来
uvc_queue_buffer(&video->queue, arg);
list_add_tail(&buf->stream, &queue->mainqueue);
list_add_tail(&buf->queue, &queue->irqqueue);
--------以上ioctl没有涉及发包操作,都是在查询设备被枚举时的信息---------------------------------------------
case VIDIOC_STREAMON: 启动,比较复杂,涉及发包操作
uvc_video_enable(video, 1) //把前面设置的信息发给硬件,启动摄像头。1表示打开摄像头
/* Commit the streaming parameters. 提交给硬件*/
if ((ret = uvc_commit_video(video, &video->streaming->ctrl)) < 0)
uvc_set_video_ctrl(video, probe, 0);//设置格式format、frame
ret = __uvc_query_ctrl(video->dev/*哪一个usb设备*/, SET_CUR, 0,
video->streaming->intfnum/*哪一个接口(VS接口)*/,
probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
uvc_timeout_param);
//启动摄像头
return uvc_init_video(video, GFP_KERNEL);
先是一系列初始化,然后提交URB
uvc_init_video_isoc(video, ep, gfp_flags);
uvc_alloc_urb_buffers
urb->complete = uvc_video_complete;//接收到数据之后此函数被调用
video->decode(urb, video, buf);
uvc_video_decode_isoc(实时传输)/uvc_video_decode_bulk(批量传输)
uvc_queue_next_buffer
wake_up(&buf->wait);//唤醒进程
/* Submit the URBs. */
for (i = 0; i < UVC_URBS; ++i) {
if ((ret = usb_submit_urb(video->urb[i], gfp_flags)) < 0) {
uvc_printk(KERN_ERR, "Failed to submit URB %u "
"(%d).\n", i, ret);
uvc_uninit_video(video, 1);
return ret;
}
}
poll: 有数据就调用poll函数
uvc_v4l2_poll
uvc_queue_poll(&video->queue, file, wait);
poll_wait(file, &buf->wait, wait);//休眠等待
case VIDIOC_DQBUF: //有数据时,把buf从队列里面取出来
uvc_dequeue_buffer(&video->queue, arg,
file->f_flags & O_NONBLOCK);
list_del(&buf->stream);
case VIDIOC_STREAMOFF://停止摄像头
uvc_video_enable(video, 0);//第二个参数是0表示停止
if (!enable) {
uvc_uninit_video(video, 1);
usb_set_interface(video->dev->udev,
video->streaming->intfnum, 0);
uvc_queue_enable(&video->queue, 0);
return 0;
}
--------------------------------------------------------------------------------------------------------------
以上分析只涉及Video Streaming Interface,不涉及Video Control Interface
分析设置亮度等信息的过程,它同样是调用ioctl,向Video Control Interface(VC)发包
case VIDIOC_S_CTRL:
uvc_ctrl_set(video, &xctrl);//设置
uvc_ctrl_commit(video); //提交
__uvc_ctrl_commit
uvc_ctrl_commit_entity
uvc_query_ctrl(dev(哪一个usb设备), SET_CUR, ctrl->entity->id(哪一个Unit/Terminal),
dev->intfnum(哪一个接口:VC), ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
trl->info->size);
__uvc_query_ctrl
usb_control_msg
四、总结:
1.usb设备有两个interface,video control interface 和 video streaming interface 。
2.video control interface(VC)用于控制,比如亮度、白平衡等功能。内部有多个实体(entity):unit/terminal,可以通过uvc_query_ctrl函数来访问它。
3.video streaming interface 用于获得视频数据,也可以用来设置format(格式)/frame(每种格式下有多种分辨率、位宽等信息),可以通过uvc_query_ctrl函数来访问它。
4.我们在设置format时只是使用某个结构体里面数据,这些数据是在设备被枚举时设置的,即分析描述符时确定的。
5.uvc 驱动的重点在于:
描述符的分析
属性的控制 :通过video control interface的设置
数据的获得 :通过video streaming interface的设置
格式的选择 :通过video streaming interface的urb包来获得