Linux摄像驱动四:USB摄像驱动分析

xiaoxiao2021-02-28  98

五、写一个USB摄像头驱动程序

 

大体框架如下所示:

1.构造一个usb_driver

2.设置

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

 

-----------------------------------------------------   UVC: USB Video Class UVC驱动:drivers\media\video\uvc\   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);      // 应该是把应用程序提供的参数拷贝到内核态,然后调用uvc_v4l2_do_ioctl这个函数 1. open:         uvc_v4l2_open 2. VIDIOC_QUERYCAP   // video->streaming->type          // 是在设备被枚举时分析描述符时设置的         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                // format数组应是在设备被枚举时设置的         format = &video->streaming->format[fmt->index]; 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          // 应该从format[fmt->index];这个数组里面来对比是否支持用户提供的某种格式         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;     // frame是一些更细致的参数,比如分辨率什么 7. VIDIOC_REQBUFS          // 申请缓冲区          uvc_alloc_buffers         例如:ret = uvc_alloc_buffers(&video->queue, rb->count, bufsize);    // 应用程序传进来rb参数,里面含有你想分配多少个缓冲区                for (; nbuffers > 0; --nbuffers) {               // 传进来的count即是nbuffers,假如是4,函数调用vmalloc_32尝试分配4个缓冲区,如果失败,则--,以此类推                 mem = vmalloc_32(nbuffers * bufsize);                 if (mem != NULL)                     break;             } 8. VIDIOC_QUERYBUF      // 查询BUF参数         uvc_query_buffer             __uvc_query_buffer                 memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);       // 复制参数 9. mmap                        // 知道了BUF的大小等等之后,调用mmap进行映射         uvc_v4l2_mmap 10. VIDIOC_QBUF               // 是入队列         uvc_queue_buffer             list_add_tail(&buf->stream, &queue->mainqueue);          // 把BUF放入某些队列             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  /* 哪一个接口: 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               // 假如是实时端点调用uvc_video_decode_isoc/批量调用uvc_video_encode_bulk                                                       => uvc_queue_next_buffer => wake_up(&buf->wait);)          // 划重点!wake_up唤醒在这里出现了!收到数据之后肯定要唤醒应用程序了! 小结:USB驱动程序获得数据之后,每个urb(usb request block) complete函数调用完成之后,最终调用到wake_up,唤醒应用程序 usb_submit_urb  // 提交urb,提交urb之后就是启动usb传输了! 12. poll  //BUF放入队列,怎么知道有数据呢?答:用POLL,来获得BUF是否已经有数据         uvc_v4l2_poll                        uvc_queue_poll                 poll_wait(file, &buf->wait, wait);       // 休眠等待有数据,当USB数据获得数据之后,接上 13. VIDIOC_DQBUF          // 把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          // 设置             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);                             敲黑板:这里的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来获得    
转载请注明原文地址: https://www.6miu.com/read-83764.html

最新回复(0)