USB摄像头驱动框架分析

usb摄像头驱动程序,里面涉及硬件的操作。比如说,想设置亮度的时候,需要把亮度的参数发给硬件。去得到真正视频数据的时候,需要访问硬件得到数据。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设备

当将usb设备接到板子上后,如果该设备能够被driver所支持,也就是说吻合这个id_table,此时probe函数就会被调用。在probe函数里面就可以做自己想做的事情了。

linux内核已经自带有usb摄像头的驱动程序,它支持UVC规格的摄像头

UVC:usb  video  class

基本上对于Windows下面,那种即插即用的usb摄像头,就吻合UVC规范的,不需要自己再去安装驱动程序。

UVC驱动所在路径:drivers\media\video\uvc\(3.4.2内核),该路径下的所有文件都是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 uvc_debugfs.o
ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
uvcvideo-objs  += uvc_entity.o
endif
obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o

这个驱动程序含有多个.c,最终会编译成一个uvcvideo.ko

uvc_driver.c分析:

1、usb_register(&uvc_driver.driver)

2、

  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,
    //假设我们接上了一个usb摄像头,这个摄像头在uvc_ids里面,那么内核就会调用uvc_probe函数
    .id_table = uvc_ids,
    .supports_autosuspend = 1,
    },
  };

  uvc_probe

    uvc_register_chains(dev)

      uvc_register_terms(dev, chain);

        uvc_register_video(dev, stream);

                                  ret = uvc_video_init(stream);

          video = video_device_alloc();

          vdev->v4l2_dev = &dev->vdev;

          video->fops = &uvc_fops;

          vdev->release = uvc_release;

          strlcpy(vdev->name, dev->name, sizeof vdev->name);

          stream->vdev = vdev;

          video_set_drvdata(vdev, stream);

          video_register_device(video, VFL_TYPE_GRABBER, -1); //到这个地方又回到了以前我们分析的video_register_device这个函数了。

-----------------------------------------------------------------------------------------------

分析UVC驱动程序的调用过程:

/drivers/media/video/uvc/Uvc_v4l2.c

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
};

1. open

  uvc_v4l2_open

2.应用程序打开设备之后,就是一系列的ioctl,在上篇博客中已经分析了11个必须的ioctl

uvc_v4l2_ioctl

  //首先将应用程序提供的参数拷贝到内核态,然后条用uvc_v4l2_do_ioctl

  video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);

  uvc_v4l2_do_ioctl  //在该函数里面就有一系列的ioctl的调用了。接下来就一一进行跟踪

2.1 VIDIOC_QUERYCAP

//根据stream->type来设置cap->capabilities
    if (stream->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;
    //问题:stream->type是在什么地方被初始化的呢?应该是在usb摄像头被枚举时分析它的描述符时被设置的

2.2 VIDIOC_ENUM_FMT  //列举出usb摄像头所支持的格式

format = &stream->format[fmt->index]; //format数组应该是在设备被枚举时设置的

2.3 VIDIOC_G_FMT

uvc_v4l2_get_format(stream, arg)   //USB摄像头支持多种格式format,每种格式下有多种frame(比如分辨率,每个像素占据多少位),
        struct uvc_format *format = stream->cur_format;  //当前使用的是哪种格式,某种格式下面有不同的分辨率
        struct uvc_frame *frame = stream->cur_frame      //这些不同的分辨率就是存在frame中

2.4 VIDIOC_TRY_FMT

uvc_v4l2_try_format(stream, arg, &probe, NULL, NULL);
        /* Check if the hardware supports the requested format. */   //这就是所谓的format
        
        /* Find the closest image size. The distance between image sizes is    //这就是所谓的frame
        * the size in pixels of the non-overlapping regions between the
        * requested size and the frame-specified size.
        */

2.5 VIDIOC_S_FMT  //只是把参数保存下来,还没有发给usb摄像头

    uvc_v4l2_set_format(stream, arg);
        uvc_v4l2_try_format
        stream->cur_format = format;
        stream->cur_frame = frame;

2.6 VIDIOC_REQBUFS

uvc_alloc_buffers(&stream->queue, arg);
        /* Decrement the number of buffers until allocation succeeds. */
        for (; nbuffers > 0; --nbuffers) {
            mem = vmalloc_32(nbuffers * bufsize);
            if (mem != NULL)
                break;
        }

2.7 VIDIOC_QUERYBUF

    uvc_query_buffer(&stream->queue, buf);
        __uvc_query_buffer(&queue->buffer[v4l2_buf->index], v4l2_buf)

2.8 知道了buf的大小之后,需要将buf映射到用户空间。此时应该调用mmap

mmap
    uvc_v4l2_mmap

2.9 VIDIOC_QBUF   //映射完之后,将buf放入队列中

uvc_queue_buffer(&stream->queue, arg);
        list_add_tail(&buf->stream, &queue->mainqueue);
        list_add_tail(&buf->queue, &queue->irqqueue);

2.10 VIDIOC_STREAMON

uvc_video_enable(stream, 1); //之前设置了很多参数,比如说格式、分辨率等参数,但只不过是将这些格式存起来了而已,并没有发送给硬件。
                                 //stream on 就会把所设置的参数发给硬件,然后启动摄像头
        /* Commit the streaming parameters. */
        uvc_commit_video(stream, &stream->ctrl);
             uvc_set_video_ctrl(stream, probe, 0);   //设置format和frame。这都是VS中的参数。注意这里并没有涉及VC
                ret = __uvc_query_ctrl(stream->dev/*指代哪一个usb设备*/, UVC_SET_CUR, 0, stream->intfnum /*usb设备中有很多接口,指代哪个接口,在这里指vs接口*/,
                                        probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
                                        size, uvc_timeout_param);
        /*启动:Initialize isochronous/bulk URBs and allocate transfer buffers*/     
        uvc_init_video(stream, GFP_KERNEL);
            uvc_init_video_isoc(stream, best_ep, gfp_flags);  //初始化实时传输
                urb->complete = uvc_video_complete; //收到数据后此函数被调用,它又调用stream->decode(urb, stream, buf); 
                                                    //decode又是啥玩意呢?它是在哪初始化的?
                                                    uvc_video_init
                                                        /* Select the video decoding function */
                                                        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;
                                                        } else {
                                                            if (stream->intf->num_altsetting == 1)
                                                                stream->decode = uvc_video_encode_bulk;
                                                            else {
                                                                uvc_printk(KERN_INFO, "Isochronous endpoints are not "
                                                                    "supported for video output devices.\n");
                                                                return -EINVAL;
                                                            }
                                                        }
                                                        在videoStreaming Interface中有USB In Endpoint,它有分为实时端点和批量端点。以uvc_video_decode_isoc进行分析
                                                        uvc_video_decode_isoc
                                                            uvc_queue_next_buffer(&stream->queue, buf);
                                                                vb2_buffer_done(&buf->buf, VB2_BUF_STATE_DONE);
                                                                    /* Inform any processes that may be waiting for buffers */
                                                                    wake_up(&q->done_wq);
                                                        当应用程序把一个buf放入队列之后,调用StreamOn来启动数据传输,在这里做了一些初始化。当我们的应用程序接下来调用了poll函数来查询数据是否已经就绪,就使用poll_wait在那里休眠等待。当我们的usb驱动程序获得了数据之后,每一个urb(usb request block)完成之后,它的complete函数就会被调用
                                                        最终会调用到wake_up,将应用程序唤醒。
            uvc_init_video_bulk(stream, ep, gfp_flags); //初始化批量传输
            /* Submit the URBs. */
            usb_submit_urb(stream->urb[i], gfp_flags);  //提交了之后,就启动usb传输了

2.11 从应用程序的角度来说,把buf放入队列之后,如何知道buf有没有数据。此处就应该调用poll函数了

poll
    uvc_v4l2_poll
        uvc_queue_poll(&stream->queue, file, wait);
            poll_wait(file, &buf->wait, wait);//休眠等待有数据

2.12 VIDIOC_DQBUF

uvc_dequeue_buffer(&stream->queue, arg,file->f_flags & O_NONBLOCK);
        vb2_dqbuf(&queue->queue, buf, nonblocking);
            /* Remove from videobuf queue */
            list_del(&vb->queued_entry);

2.13 VIDIOC_STREAMOFF //关闭摄像头

uvc_video_enable(stream, 0);
        uvc_uninit_video
            usb_kill_urb(urb);
            usb_free_urb(urb);
            stream->urb[i] = NULL

整个调用过程已经基本分析完毕,但是这个地方并没有涉及VC相关的操作。接下来进行分析,设置亮度的过程

ioctl:VIDIOC_S_CTRL
        uvc_ctrl_set(chain, &xctrl);
        uvc_ctrl_commit(chain);
            __uvc_ctrl_commit(chain, 0);
                uvc_ctrl_commit_entity(chain->dev, entity, rollback);
                    ret = uvc_query_ctrl(dev/*哪一个usb设备*/, UVC_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设备*/, UVC_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 用于获得视频数据,也可以用来选择format和frame。(VS 可能有多个format,一个format支持多种frame,frame用来表示分辨率等信息)

可以通过类似的函数来访问:

ret = __uvc_query_ctrl(stream->dev/*指代哪一个usb设备*/, UVC_SET_CUR, 0, stream->intfnum /*usb设备中有很多借口,指代哪个接口,在这里指vs接口*/,
                        probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
                        size, uvc_timeout_param);

4.在分析ioctl的过程中,比如想知道它的format时,直接从某个结构体中得到了这些信息(format = &stream->format[fmt->index];)那么这些结构体是在哪里被初始化的呢?
应是设备被枚举时设置的,也就是分析它的描述符时设置的

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

 

 

 

posted @ 2019-01-31 22:23  一代枭雄  阅读(1442)  评论(0编辑  收藏  举报