2.1. 分配video_device:video_device_alloc
2.2. 设置
2.3. 注册: video_register_device
uvc_driver.c分析:
1. usb_register(&uvc_driver.driver);
2. uvc_probe
uvc_register_video
vdev = video_device_alloc();
vdev->fops = &uvc_fops;
video_register_device
分析一个驱动程序最好的方法就是跟踪应用程序对它的调用过程
在www.usb.org下载 uvc specification, UVC规格书
UVC 1.5 Class specification.pdf : 有详细描述
USB_Video_Example 1.5.pdf : 有示例
12页拓扑图可看出:
USB摄像头内部分成两部分,一部分叫
VideoContro Interface:视频控制接口,一部分为:
VideoSteaming Interface:视频流接口
通过VideoControl Interface来
控制,
通过VideoStreaming Interface来读
视频数据,
VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度
UC内部抽象出了2个概念:
Unit:里面的连接
Termiral:用于“内”“外”的连接
拓扑图可见的即是:
SU: Select Unit 多路选择器,选择两路输入IT和OT其中之一;选择哪一路信号
PU: Process Unit 处理单元: 应该是亮度、白平衡等等的设置
IT: input Termiral 用于连接内外的一个功能模块
OT: Out Termiral
CT: Camera Termiral
------------------------------------------------------------------------------
分析UVC驱动调用过程:
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,
};
static long uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
Garmen:
应该是把应用程序提供的参数拷贝到内核态,然后调用uvc_v4l2_do_ioctl这个函数
1. open:
uvc_v4l2_open
2. VIDIOC_QUERYCAP // video->streaming->type Garmen:应该是在设备被枚举时分析描述符时设置的
if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_STREAMING;
else
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
| V4L2_CAP_STREAMING;
3. VIDIOC_ENUM_FMT //Garmen: format数组应是在设备被枚举时设置的
format = &video->streaming->
format[fmt->index];
4. VIDIOC_G_FMT
uvc_v4l2_get_format // Garmen:USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)
struct uvc_format *format = video->streaming->cur_format;
struct uvc_frame *frame = video->streaming->cur_frame;
5. VIDIOC_TRY_FMT //Garmen:应该从
format[fmt->index];这个数组里面来对比是否支持用户提供的某种格式
uvc_v4l2_try_format
/* Check if the hardware supports the requested format. */
//Garmen:找到最接近的分辨率
/* 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 // Garmen:
只是把参数保存起来,还没有发给USB摄像头
uvc_v4l2_set_format
uvc_v4l2_try_format //Garmen:测试成功之后
video->streaming->cur_format = format;
video->streaming->cur_frame = frame; //Garmen:frame是一些更细致的参数,比如分辨率什么
7. VIDIOC_REQBUFS //Garmen:申请缓冲区
uvc_alloc_buffers
例如:ret = uvc_alloc_buffers(&video->queue, rb->count, bufsize); //Garmen:应用程序传进来rb参数,里面含有你想分配多少个缓冲区
for (; nbuffers > 0; --nbuffers) { //Garmen:传进来的count即是nbuffers,假如是4,函数调用vmalloc_32尝试分配4个缓冲区,如果失败,则--,以此类推
mem = vmalloc_32(nbuffers * bufsize);
if (mem != NULL)
break;
}
8. VIDIOC_QUERYBUF //Garmen:查询BUF参数
uvc_query_buffer
__uvc_query_buffer
memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf); // Garmen:复制参数
9. mmap //Garmen:知道了BUF的大小等等之后,调用mmap进行映射
uvc_v4l2_mmap
10. VIDIOC_QBUF //Garmen:是入队列
uvc_queue_buffer
list_add_tail(&buf->stream, &queue->mainqueue); //Garmen 把BUF放入某些队列
list_add_tail(&buf->queue, &queue->irqqueue);
11. VIDIOC_STREAMON //Garmen:启动摄像头
uvc_video_enable(video, 1) // Garmen: 把所设置的参数发给硬件,然后启动摄像头
/* Commit the streaming parameters. */ //Garmen:提交参数
uvc_commit_video
uvc_set_video_ctrl //Garmen:设置格式fromat, frame
ret = __uvc_query_ctrl(video->dev /* Garmen:哪一个USB设备 */, SET_CUR, 0,
video->streaming->intfnum /*Garmen: 哪一个接口: video steaming接口 */,
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; (接收到数据后此函数被调用
uvc_video_complete 又调用video->decode(urb, video, buf);
==> uvc_video_decode_isoc/uvc_video_encode_bulk //Garmen: 假如是实时端点调用uvc_video_decode_isoc/批量调用uvc_video_encode_bulk
=> uvc_queue_next_buffer => wake_up(&buf->wait);) //Garmen:划重点!wake_up唤醒在这里出现了!收到数据之后肯定要唤醒应用程序了!
Garmen:小结:
USB驱动程序获得数据之后,每个urb(usb request block) complete函数调用完成之后,最终调用到wake_up,唤醒应用程序
usb_submit_urb //Garmen:
提交urb,提交urb之后就是启动usb传输了!
12. poll //BUF放入队列,怎么知道有数据呢?答:用POLL,来获得BUF是否已经有数据
uvc_v4l2_poll
uvc_queue_poll
poll_wait(file, &buf->wait, wait); // Garmen:休眠等待有数据,
当USB数据获得数据之后,接上
13. VIDIOC_DQBUF //Garmen:把BUF从队列里面取出
uvc_dequeue_buffer
list_del(&buf->stream);
14. VIDIOC_STREAMOFF //关闭摄像头
uvc_video_enable(video, 0);
usb_kill_urb(urb);
usb_free_urb(urb);
分析设置亮度过程:
ioctl: VIDIOC_S_CTRL
uvc_ctrl_set //Garmen:设置
uvc_ctrl_commit //Garmen:设置好了之后进行提交
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
ret = uvc_query_ctrl(dev /*
<==哪一个USB设备 */, SET_CUR, ctrl->entity->id /*
<==Garmen:哪一个unit/terminal */,
dev->intfnum /*
<==哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);
敲黑板:这里的uvc_query_ctrl和VIDIOC_STREAMON都同一个函数,但是参数不同
第三个参数:
ON函数用到的是:video->streaming->intfnum video steaming接口是流接口
设置属性(例如本函数的亮度)用的是:dev->intfnum VC interface接口是控制接口
总结:
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);
3. 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);
4. 我们在设置FORMAT时只是简单的使用video->streaming->format[fmt->index]等数据,
这些数据哪来的?
应是设备被枚举时设置的,也就是分析它的描述符时设置的。
5. UVC驱动的重点在于:
描述符的分析
属性的控制: 通过VideoControl Interface来设置
格式的选择:通过VideoStreaming Interface来设置
数据的获得:通过VideoStreaming Interface的URB来获得