USB摄像头驱动框架分析

时间:2022-07-09 17:26:40

怎样构造一个摄像头驱动程序:

1. 分配video_device:video_device_alloc
2. 设置
.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
3. 注册: video_register_device

怎样构造USB摄像头驱动:

1.构造一个usb_driver
2.设置
probe:
2.1. 分配video_device:video_device_alloc
2.2. 设置
.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
2.3. 注册: video_register_device
id_table: 表示支持哪些USB设备
3.注册: usb_register

usb设备接到板子上,能被usb_driver识别(即吻合id_table),将调用probe函数,分配video_device_alloc,设置它,然后注册它。

Linux内核自带USB摄像头的驱动程序,支持UVC格式的摄像头。
UVC: USB Video Class,是某一类usb设备
UVC驱动程序在内核中所在位置:linux-2.6.31.14\drivers\media\video\uvc\

linux-2.6.31.14\drivers\media\video\uvc\Makefile

uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
uvc_status.o uvc_isight.o
obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o

分析内核框架:
uvc_driver.c(drivers\media\video\uvc)分析:
1.入口函数uvc_init:result = usb_register(&uvc_driver.driver);
uvc_driver结构体:

struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,//接上能识别的设备,调用uvc_probe
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};

2.uvc_probe函数:

uvc_probe
uvc_register_video//注册
vdev = 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,
.ioctl = uvc_v4l2_ioctl,
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll };
video_register_device//注册

USB摄像头驱动框架分析

VideoControl Interface视频控制接口,VideoStreaming Interface视频流控制接口。通过VideoControl Interface来控制,通VideoStreaming Interface来读视频数据,VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度。

CT:Camera Terminal          IT:Input  Terminal  
SU:Select Unit PU:Process Unit
OT:Out Terminal

具体功能查手册:UVC 1.5 Class specification.pdf。

USB摄像头驱动框架分析
Terminal用于内外链接,Unit 用于内部处理。

分析驱动程序的方法:通过分析应用程序对它的调用过程。

分析UVC驱动调用过程:uvc_v412.c

1.open:
uvc_v4l2_open

应用程序调用open时,就会进入驱动程序,这里调用uvc_driver.c的 uvc_fops 结构体里面的uvc_v4l2_open。

const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.ioctl = uvc_v4l2_ioctl,
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
};
2. ioctl:
uvc_v4l2_ioctl
return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
//将应用程度提供的参数拷贝到内核,然后调用uvc_v4l2_do_ioctl
uvc_v4l2_do_ioctl
/* Query capabilities */
VIDIOC_QUERYCAP//查询驱动功能
if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)//*video->streaming->type 应该是在usb摄像头设备被枚举时分析描述符时设置的
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_STREAMING;
else
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
| V4L2_CAP_STREAMING;

同理应用程序调用ioctl时,也会进入驱动程序,这里调用uvc_driver.c的
uvc_fops 结构体里面的uvc_v4l2_ioctl。

3. VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式
format = &video->streaming->format[fmt->index];// format数组应是在设备被枚举时设置的
4. VIDIOC_G_FMT //读取当前驱动的视频捕获格式
uvc_v4l2_get_format // USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)
struct uvc_format *format = video->streaming->cur_format;//当前的格式
struct uvc_frame *frame = video->streaming->cur_frame;//当前的帧
5. VIDIOC_TRY_FMT //验证当前驱动的显示格式
uvc_v4l2_try_format
/* Check if the hardware supports the requested format. */
/*检查硬件是否支持请求的格式*/
/* Find the closest image size. The distance between image
sizes is the size in pixels of the non-overlapping regions
between the requested size and the frame-specified size */
/*找到最接近的图像大小。 图像尺寸之间的距离是大小以像素之间的非重叠区域为单位请求的大小和帧指定的大小。*/
6. VIDIOC_S_FMT  // 设置当前驱动的视频捕获格式,只是把参数保存起来,还没有发给USB摄像头
uvc_v4l2_set_format
uvc_v4l2_try_format
video->streaming->cur_format = format;
video->streaming->cur_frame = frame;
7. VIDIOC_REQBUFS //申请缓冲区 
unsigned int bufsize =
video->streaming->ctrl.dwMaxVideoFrameSize;//缓冲区大小
uvc_alloc_buffers
for (; nbuffers > 0; --nbuffers) {
mem = vmalloc_32(nbuffers * bufsize);//分配nbuffers个缓冲区
if (mem != NULL)
break;
}
8. VIDIOC_QUERYBUF //查询缓冲区

uvc_query_buffer
__uvc_query_buffer
memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf); // 复制参数
9. mmap //映射缓冲区
uvc_v4l2_mmap
10. VIDIOC_QBUF//把buffer放入缓存队列,当有数据时将buffer从队列中取出
uvc_queue_buffer
list_add_tail(&buf->stream, &queue->mainqueue);
list_add_tail(&buf->queue, &queue->irqqueue);
11. VIDIOC_STREAMON
uvc_video_enable(video, 1) // 把所设置的参数发给硬件,然后启动摄像头
/* Commit the streaming parameters. */
uvc_commit_video //把参数提交给硬件
uvc_set_video_ctrl /* 设置格式fromat, 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);
/* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
uvc_init_video(video, GFP_KERNEL);
uvc_init_video_isoc / uvc_init_video_bulk
urb->complete = uvc_video_complete; (收到数据后此函数被调用,它又调用video->decode(urb, video, buf); ==> uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)

usb_submit_urb

应用程序将一个buffer放入队列,然后调用STREAMON启动传输,调用poll函数查询数据是否已经就绪,当usb驱动程序获取了数据后,每个urb(usb request block)完成后,调用 urb->complete = uvc_video_complete,最终调用wake_up

12. poll //查看buffer是否已有数据
uvc_v4l2_poll
uvc_queue_poll
poll_wait(file, &buf->wait, wait); // 休眠等待有数据
13. VIDIOC_DQBUF //将数据从队列中取出
uvc_dequeue_buffer
list_del(&buf->stream);
14. VIDIOC_STREAMOFF //关闭传输 
uvc_video_enable(video, 0);
usb_kill_urb(urb);
usb_free_urb(urb);

以上分析不包括VideoControl Interface ,用分析设置亮度过程来说明:

ioctl: VIDIOC_S_CTRL (uvc_v4l2.c)
uvc_ctrl_set //设置
uvc_ctrl_commit //提交
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
ret = uvc_query_ctrl(dev /* 哪一个USB设备 */, SET_CUR, ctrl->entity->id /* 哪一个unit/terminal */,
dev->intfnum /* 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);

总结:
1. UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface
2. VideoControl Interface用于控制,比如设置亮度。它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
可以通过类似的函数来访问:

    ret = uvc_query_ctrl(dev  /* 哪一个USB设备 */, SET_CUR, ctrl->entity->id  /* 哪一个unit/terminal */,
dev->intfnum /* 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);
  1. VideoStreaming Interface用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format, 一个format支持多种frame, 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);
  1. 我们在设置FORMAT时只是简单的使用video->streaming->format[fmt->index]等数据, 这些数据哪来的?
    应是设备被枚举时设置的,也就是分析它的描述符时设置的。

5.UVC驱动的重点在于:
描述符的分析
属性的控制: 通过VideoControl Interface来设置
格式的选择:通过VideoStreaming Interface来设置
数据的获得:通过VideoStreaming Interface的URB来获得