V4L2 + UVC驱动分析一条龙

#v4l2 + uvc 驱动情景分析

#1. 使用仅支持yuv422的usb摄像头.
#2. 执行简单的v4l2应用程序, 只获取一帧,160x120的视频数据.

#3. 基于LINUX KERNEL 4.4.194 , 开发板 RK3328.

#注意:

#1. <tagX> 表示该结果对应的函数.
#2. <headX> 表示该队列,以及其队员.
#3. gm_xxxx(), gm为个人调试前缀, 去掉gm, xxxx()即为原函数名.

#4. 将文章文本保存为*.c文件后,再打开. 会有语法高亮,看起来舒服一些...

#资源:

#1. 精简后的v4l2, https://gitee.com/suiren/rk3328_v4l2/tree/master/drivers/media/v4l2-core

#2. 精简后的uvc驱动, https://gitee.com/suiren/rk3328_v4l2/tree/master/drivers/media/usb/uvc

#3. 所使用的v4l2应用例程源码:https://gitee.com/suiren/rk3328_v4l2/blob/master/drivers/media/usb/uvc/uvc_app.c

## v4l2 + uvc 驱动初始化流程

#1. videodev_init()
    /* 圈用主设备号为VIDEO_MAJOR, 的256个字符设备 */
#2. uvc_probe
    /*  根据usb配置描述符, 解析摄像头所支持的视频格式(format),
     *  所支持的视频分辨率(frame).
     *  根据以上信息, 填充 stream, stream->format, stream->format->frame*/
    gm_uvc_parse_control

    /* 生成video0设备节点, 关联 ops供v4l2调用. */
    gm_uvc_register_chains


##结合应用程序,来分析v4l2 + uvc驱动的open 后的执行流程.
##我所使用的应用程序代码, 执行流程为以下:

    fd = open("/dev/video0",O_RDWR);

    /* 进行usb通信, 设置摄像头的视频格式,分辨率. */
    ioctl(fd,VIDIOC_S_FMT,&format);

    /* 申请用于mmap的buffer , 其指针保存之vb2_buffer,
     * 而vb2_buffer 属于 uvc_buffer的成员.*/
    ioctl(fd,VIDIOC_REQBUFS,&reqbufs);

    /* 查询所申请的buffer的信息, buffer的索引值,
     * 将这些信息保存至输入参数bufs*/
    ioctl(fd,VIDIOC_QUERYBUF,&bufs);

    /* 使用buffer的索引值, 选择需要mmap的buffer,
     * 进行mmap*/
    mmap

    /* 将buffer的地址复制到 uvc_buffer->mem.
     * 将vb2_buffer 加入 stream->queue->queue->queued_list.*/
    ioctl(fd,VIDIOC_QBUF,&bufs);

    /* 1. 选择 摄像头的 拥有端点的视频流接口, 即可以进行视频流传输了.
     * 2. 从 stream->queue->queue->queued_list 取出 vb2_buffer, 并转为uvc_buffer.
     * 2. 将uvc_buffer 加入 queue->irqqueue队列.
     * 3. 设置uvc_urb->urb , 用于usb视频流传输.
     * 4. 为uvc_urb->buffer 分配内存, 用于保存接收到的USB视频流数据.
     * 5. 提交uvc_urb->urb, 进行usb同步传输, 以获得视频数据.*/
    ioctl(fd,VIDIOC_STREAMON,&type);

    /* 1. 等待有vb2_buffer不再使用, 加入stream->queue->queue->done_list.
     * 2. 等待被唤醒后, 从done_list取出vb2_buffer, 将其reset..*/
    ioctl(fd,VIDIOC_DQBUF,&readbuffer);

    /* 此时, 等待urb被处理完成, 从摄像头获得视频数据.
     * 则执行 gm_uvc_video_complete().
     *
     * gm_uvc_video_complete()
     * 1. 从irqqueue队列里取出一个uvc_buffer,
     * 2. uvc_video_copy_data_work()负责将uvc_urb所保存的usb视频数据复制到uvc_buffer->mem的mmap内存里.
     * 3. 将vb2_buffer 加入 stream->queue->queue->done_list, 唤醒 dqbuf 的等待.
     *     */

     /* 将vb2_buffer 加入 stream->queue->queue->queued_list.
      * 被 dqbuf reset 的 vb2_buffer可以重新加入队列了. */
    ioctl(fd,VIDIOC_QBUF,&readbuffer);

    /* 选择 摄像头的 无端点的视频流接口, 即关闭视频流传输. */
    ioctl(fd,VIDIOC_STREAMOFF,&type);
    close(fd);

#v4l2 + uvc 相关结构体关系.

struct uvc_streaming *stream
    |===>struct uvc_video_queue *queue <实体2>
            |===>struct list_head irqqueue <head2>
            |===>struct vb2_queue *queue <实体3>
                    |===>struct list_head queued_list <head1>  <tag1>
                    |===>struct vb2_buffer *bufs[0] <指向 实体1>
                    |===>void *drv_priv; <指向 实体2> /* gm_uvc_queue_init()里设置 */
                    |===>struct list_head done_list  <head3>/* 完成队列, 使用完成的vb2_buffer将入队.. */

/* 在gm1__vb2_queue_alloc()函数
 * 对vb2_buffer *bufs[0]分配uvc_buffer大小的内存.
 * 且 vb2_buffer vb2_buf 是vb2_v4l2_buffer buf的第一个成员,
 * vb2_v4l2_buffer buf 又是uvc_buffer的第一个成员.*/
struct uvc_buffer
    |===>void *mem <指向 实体5> <tag5>
    |===>struct list_head queue <加入队尾 head2> <tag7>
    |===>struct vb2_v4l2_buffer buf
            |===>struct vb2_buffer vb2_buf <实体1>
                    |===>struct list_head done_entry <加入队尾 head3> <tag8>
                    |===>struct list_head queued_entry <加入队尾 head1> <tag6>
                    |===>struct vb2_queue *vb2_queue <指向 实体3>  <tag3>
                    |===>struct vb2_plane planes[0]
                            |===>void *mem_priv <指向 实体4>  <tag2>

struct vb2_vmalloc_buf *buf <实体4>
    |===>void *vaddr <实体5> /* gm1_vb2_vmalloc_alloc()进行内存分配,vmalloc_user(buf->size) */
    |===>struct vb2_vmarea_handler   handler
            |===>void *arg <指向 实体4>  <tag4>


## 驱动初始化流程

@v4l2-dev.c
videodev_init
    /* 圈用主设备号为VIDEO_MAJOR, 的256个字符设备 */
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);
    register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);

@uvc_driver.c
uvc_probe
    /* 填充 stream, stream->format, stream->format->frame*/
    gm_uvc_parse_control

    /* 生成video0设备节点, 关联 ops供v4l2调用. */
    gm_uvc_register_chains

@uvc_driver.c
gm_uvc_parse_control
    /* while循环处理配置描述符之后的信息.
     * 1. 首先是VC Interface Header Descriptor;
     * 2. 第二为VC Interface Input Terminal Descriptor
     * 3. VC Interface Processing Unit Descriptor
     * 4. VC Interface Output Terminal Descriptor
     *
     * 实际流程根据描述符顺序而定.*/
    while() gm1_uvc_parse_standard_control;

     /* 这里以处理VC Interface Header Descriptor 为例;
      * 因为头部描述符是必须的, 其他的输入,输出,处理终端描述符,
      * 都是不必要的,可以省略的....
      *
      * 而且处理这个比较特殊,会顺带将 以下描述符也一并处理.
      * 1. VS Interface Input Header Descriptor
      * 2. VS Video Format Descriptor
      * 3. VS Uncompressed Frame Type Descriptor*/
    gm1_uvc_parse_standard_control
        /*  */
        case UVC_VC_HEADER:
            /* 数据获得方式:VC Interface Header Descriptor->bcdUVC */
            dev->uvc_version = 0x100;
            /*48Mhz
             * 数据获得方式: VC Interface Header Descriptor->dwClockFrequency */
            dev->clock_frequency = 0x2dc6c00;
            /* 获得设备的接口1描述符,
             * 即是 视频流接口描述符. 根据设备而定.
             * 调试时我将接口号写死了...*/
            intf = usb_ifnum_to_if(udev, 1);
            /* Parse all USB Video Streaming interfaces. */
            gm1_uvc_parse_streaming(dev, intf);

@uvc_driver.c
gm1_uvc_parse_streaming
    /* 将接口与驱动关联, 说明该接口已被驱动中了.
     * 即一个driver处理两个接口, 因为这两个接口为关联接口.
     * IAD Descriptor 声明 视频控制描述符和视频流描述符为关联接口.*/
    usb_driver_claim_interface(&uvc_driver.driver, intf, dev);

    /* streaming 结构体, 关联视频流接口. */
    streaming->intf = usb_get_intf(intf);
    /* GM: 0号接口为视频控制接口;1号接口为视频流接口,视实际设备而定.. */
    streaming->intfnum = 1;

    /* 以下为对视频流输入头部信息的处理 */
    case UVC_VS_INPUT_HEADER:
    /* 只要是有VS_INPUT_HEADER, 那么type的值就是 V4L2_BUF_TYPE_VIDEO_CAPTURE... */
    streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* 视频流接口input_header 相关信息.
     * 根据VS Interface Input Header Descriptor  进行填充*/
    streaming->header.bNumFormats = 1;
    streaming->header.bEndpointAddress = 0x82; /* GM: 端点2 */

    nframes = 5;  /* VIDEO_FORMAT_DESCRIPTOR->bNumberFrameDescriptor */
    nintervals = 10; /* VIDEO_FRAME_DESCRIPTOR->dwFrameInterval[x]
                        dwFrameInterval 为不定数组.
                        nintervals 即为不定数组的大小.
                        此摄像头的160x120 frame的dwFrameInterval数组的大小为2.
                        在未解析VIDEO_FRAME_DESCRIPTOR 前,不知其数组大小.
                        因此预先分配个大的.*/

    streaming->format = format;
    streaming->nformats = nformats;

    /* Parse the format descriptors. */
    format->frame = frame;
    gm1_uvc_parse_format(dev, streaming, format, &interval, buffer, buflen);

    /* GM: 值为 视频流接口(非视频控制接口)的端点的wMaxPacketSize */
    psize = 5120; /*le16_to_cpu(ep->desc.wMaxPacketSize); */
    psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
    streaming->maxpsize = psize; /* 3072 */

    /* 将已填充好数据的streaming 将入 列表.
     * streaming 所包含的主要信息为
     *      ->header  1. 进行视频流数据传送的 端点
     *                2 . 所支持的视频格式 数量.
     *
     *      ->format  1. 所支持格式的具体信息. 即是VS Video Format Descriptor 的内容
     *          format-> frame  1. 所支持的 分辨率信息. 即是VS Frame Type Descriptor 的内容.
     *
     *      ->nformats  1. 所支持的视频格式 数量.
     *
     *      ->maxpsize  1. 端点最大传送大小. 如果格式为压缩格式(例如yuv2),
     *                   则该值会不使用,而由get_cur(vc_probe)所得的dwMaxPayloadTransferSize来替代.
     *
     *                   其实该值只要往大里取就可以了, 这样就能保证每次IN传输都能完整地
     *                   接受设备发来的视频数据.
     *                   很多时候,设备每次IN传输发来的数据长度可能只有300~1024. 远远小于
     *                   dwMaxPayloadTransferSize的3072, 和端点的wMaxPacketSize的5120.*/
    list_add_tail(&streaming->list, &dev->streams);

@uvc_driver.c
gm1_uvc_parse_format
    /* 根据VS Video Format Descriptor->bDescriptorSubtype 决定*/
    format->type = UVC_VS_FORMAT_UNCOMPRESSED;
    /* 第1个格式, 从1开始计数. 我的设备只有yuv2 一种格式..*/
    format->index = 0x1;

    /* 根据VS Video Format Descriptor->guidFormat 决定 */
    format->fcc = V4L2_PIX_FMT_YUV422P;

    /* 根据VS Video Format Descriptor->bBitsPerPixel 决定*/
    format->bpp = 16;

     /* 填充frame的信息. 从VS Frame Type Descriptor 获得 */
    frame->bFrameIndex = 0x5; /* 第5个frame的信息. 从1开始计数. */
    frame->bmCapabilities = 0x0;
    frame->wWidth = 160;
    frame->wHeight = 120;
    frame->dwDefaultFrameInterval = 333333;


@uvc_driver.c
gm_uvc_register_chains
    gm_uvc_register_terms
        /* 获得stream结构, 从dev的stream列表中.
         * 当前列表仅加入了一个stream, 由gm1_uvc_parse_streaming() 函数.*/
        stream = gm_uvc_stream_by_id(dev, 0);
        gm_uvc_register_video
            gm_uvc_video_init
            /* 两个ops由此被关联, 被应用层和v4l2调用. */
            gm_uvc_register_video_device(xxx,..., &uvc_fops, &uvc_ioctl_ops)


gm_uvc_video_init
    struct uvc_streaming_control *probe = &stream->ctrl;

    /* GM: 设置视频流接口为0号设置.  即当前是关闭视频流的
     * stream->intfnum 的值,是在gm1_uvc_parse_streaming() 函数里, 被设置.*/
    usb_set_interface(stream->dev->udev, stream->intfnum, 0);

    /* probe 结构将作为urb的buffer发送给摄像头,
     * 在Video Request Set Cur (Video Stream Interface Control)
     * 的设置请求里.*/
    /* 这里的format->index原本是通过get_cur获得摄像头的默认format来决定的,但我直接就写死了.. */
    probe->bFormatIndex = format->index;
    probe->bFrameIndex = frame->bFrameIndex; /* 值为5,即第5个frame. 从1开始计数. */

    stream->def_format = format;
    /* 在gm_uvc_v4l2_set_format()函数内, 也会设置以下的2个值,根据用户的输入参数决定.
     * 而在gm_uvc_video_init()里, 这2个值是根据usb请求get_cur获得摄像头的默认参数而决定的
     * 显然, 即使不执行gm_uvc_v4l2_set_format(), 直接取读取视频数据,
     * 那么将获得摄像头默认视频格式以及分辨率的视频数据 */
    stream->cur_format = format;
    stream->cur_frame = frame;

    /* 当urb完成时, 将调用stream->decode() 进行视频数据处理. */
    stream->decode = gm_uvc_video_decode_isoc;

@uvc_driver.c
gm_uvc_register_video_device
    /* 设置queue的许多成员, 在不了解v4l2的情况下,也没有什么好分析的. */
    gm_uvc_queue_init
        /* uvc_queue_qops 将被使用到. */
        queue->queue.ops = &uvc_queue_qops;
        /* rmmod 驱动时, 将执行 uvc_release */
        vdev->release = uvc_release;
        /* queue 各种队列初始化 */
        vb2_queue_init(&queue->queue);
            q->buf_ops = &v4l2_buf_ops;
            gm_vb2_core_queue_init(q);
                INIT_LIST_HEAD(&q->queued_list);
                INIT_LIST_HEAD(&q->done_list);
                init_waitqueue_head(&q->done_wq);
                q->dma_dir = DMA_FROM_DEVICE;
            INIT_LIST_HEAD(&queue->irqqueue);
    /* 生成设备节点, video0 */
    /* 注册videox, 作为字符设备. */
    gm_video_register_device

@uvc_driver.c
gm_video_register_device
    gm__video_register_device
        name_base = "video";
        /* 标记可用的ioctl 方法. */
        gm_determine_valid_ioctls(vdev);
        /* 字符设备的fops */
        vdev->cdev->ops = &v4l2_fops;
        cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
        /* 设备名 */
        dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
        device_register(&vdev->dev);
        /* 释放设备时的函数. */
        vdev->dev.release = v4l2_device_release;


##接下来, 结合应用程序,来分析uvc驱动的执行流程.

##我所使用的应用程序代码, 执行流程为以下:

    fd = open("/dev/video0",O_RDWR);
    ioctl(fd,VIDIOC_S_FMT,&format);
    ioctl(fd,VIDIOC_REQBUFS,&reqbufs);
    ioctl(fd,VIDIOC_QUERYBUF,&bufs);
    mmap
    ioctl(fd,VIDIOC_QBUF,&bufs);
    ioctl(fd,VIDIOC_STREAMON,&type);
    ioctl(fd,VIDIOC_DQBUF,&readbuffer);
    ioctl(fd,VIDIOC_QBUF,&readbuffer);
    ioctl(fd,VIDIOC_STREAMOFF,&type);
    close(fd);

@uvc_v4l2.c
#open("/dev/video0",O_RDWR); => v4l2_fops->open
gm_v4l2_open
    vdev->fops->open(filp) => gm_uvc_v4l2_open

gm_uvc_v4l2_open

    /* 形成 file->private_data->stream 的关联
     * 每一个的应用层调用,包括ioctl, 都有fie作为输入参数,
     * 因此,stream则一直伴随着我们...*/
    handle->stream = stream;
    file->private_data = handle;

IOCTRL

#下面的所有ioctl的入口, 通过video0字符设备的fops->compat_ioctl进入.
#设备的fops在gm__video_register_device()函数内被设置, 指向v4l2_fops.
#即 v4l2_fops->compat_ioctl ==>  gm_v4l2_compat_ioctl32()

#应用程序执行一次ioctl(fd,VIDIOC_S_FMT,&format), 引发驱动的执行流程.
video0设备节点
    /* video0 设备的作为字符设备的fops */
    v4l2_fops.compat_ioctl => gm_v4l2_compat_ioctl32()
        
@v4l2-compat-ioctl32.c
gm_v4l2_compat_ioctl32
    gm_do_video_ioctl
        /* up 为应用传来的保存参数的指针. */
        void __user *up = compat_ptr(arg);
        /* 1. alloc_userspace() 为up_native分配空间;
         * 2. get_v4l2_format32() 将up的内容复制到up_native;
         * 3. native_ioctl()调用 video_usercopy(),接着调用__video_do_ioctl(),
         * 处理ioctl时,将处理得出的数据写入up_native, 最后将up_native返回给应用,
         * 相当于up_native为up的副本. 不直接修改up的内容.
         * 4.put_v4l2_buffer32() 将up_native的内容复制到up. */
        void __user *up_native = NULL;
        case VIDIOC_S_FMT:
            alloc_userspace(sizeof(struct v4l2_format), aux_space, &up_native);
            get_v4l2_format32(up_native, up, NULL, aux_space);
            native_ioctl()
                gm_v4l2_ioctl()
                    video_usercopy()
                        __video_do_ioctl()
                            /* 根据cmd进行方法的索引;
                             * 这里就只举s_fmt为例.
                             * 其他如querybuf也是如此的调用流程*/
                            info = &v4l2_ioctls[_IOC_NR(cmd)];
                            info->u.func(ops, file, fh, arg);
                                gm_v4l_s_fmt()
                                    ops->vidioc_s_fmt_vid_cap(file, fh, arg);
                                        /* 最终调用至uvc驱动的方法. */
                                        gm_uvc_ioctl_s_fmt_vid_cap()
            put_v4l2_buffer32

@uvc_v4l2.c
#ioctl(fd,VIDIOC_S_FMT,&format);  =>  uvc_ioctl_ops->vidioc_s_fmt_vid_cap
gm_uvc_ioctl_s_fmt_vid_cap
    gm_uvc_v4l2_set_format
        gm_uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);

        uvc_queue_allocated(&stream->queue);

        /* probe 在gm_uvc_v4l2_try_format() 里被设置. */
        stream->ctrl = probe;
        /* 在gm_uvc_video_init()函数内, 也会设置以下的2个值.
         * 而在gm_uvc_video_init()里, 这2个值是根据usb请求get_cur获得摄像头的默认参数而决定的
         **/
        stream->cur_format = format;
        stream->cur_frame = frame;

@uvc_v4l2.c
gm_uvc_v4l2_try_format
    /* 在gm_uvc_video_init()设置过一次probe, 使用的是摄像头的默认参数.
     * 但其实我将probe的值写死了, 在gm_uvc_video_init()函数里.
     * 这里直接将probe清零, 根据用户给参数, 重新设置...*/
    memset(probe, 0, sizeof *probe);
    probe->bFormatIndex = 1;  /* 格式1, yuv2. 从1开始计数 */
    probe->bFrameIndex = 5;

    gm_uvc_probe_video(stream, probe);
        /* GM: 为什么, 设置后又读?
         * 因为设置的参数可能有一些是错误的,或者我们不知道该设置什么值.
         * 而这些不知道的值,也会是我们需要的.
         * 譬如dwMaxVideoFrameSize, dwMaxPayloadTransferSize.
         *
         * 设置时, 这两个值都是0. 需要靠get_cur获得他们的值. 摄像头会返回正确的值给我们.
         *
         * 这里的步骤属于 uvc规范里的 流协商 流程中的 协商环节.
         * 使用的是VS_PROBE_CONTROL请求.
         *
         * 接下来还有一个步骤为确认, 使用VS_COMMIT_CONTROL请求.
         * 在gm_uvc_video_enable() => uvc_commit_video() 函数里进行.*/
        /* gm_uvc_get_video_ctrl()将摄像头返回的参数保存到 probe */
        gm_uvc_set_video_ctrl(stream, probe, 1);
        gm_uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);


@uvc_v4l2.c
#ioctl(fd,VIDIOC_REQBUFS,&reqbufs);  => uvc_ioctl_ops->vidioc_reqbufs
gm_uvc_ioctl_reqbufs
    /* 这里的rb 就是应用程序执行ioctl(fd,VIDIOC_REQBUFS,&reqbufs);
     * 的reqbufs*/
    gm_uvc_request_buffers(&stream->queue, rb);
        gm1_vb2_reqbufs
            gm1_vb2_core_reqbufs
                /* gm_uvc_queue_setup()
                 * 询问需要多少个plane(num_planes), 需要多少帧? 固定为1帧
                 * 询问需要plane的大小(q->plane_sizes[x]), 即一帧的大小?
                 * plane_size[x]值为 stream->ctrl.dwMaxVideoFrameSize (38400) */
                call_qop(q, queue_setup,xxx)
                /* 分配 num_buffers 个vb2_buffer, 返回成功分配的vb2_buffer的个数.
                 * 每个vb2_buffer 包含num_planes 个帧内存, 每个帧内存大小为q->plane_sizes[x]*/
                gm1__vb2_queue_alloc(q, memory, num_buffers, num_planes);

@videobuf2-core.c
/* 会拥有 num_buffers 个 vb2_buffer,  其指针保存到q->bufs[x].
 *
 * vb2_buffer
 *      planes[0].mem_priv  指向一帧大小的内存.
 *
 *      plane[]数组的元素个数固定为1. gm_uvc_queue_setup()里设置 */
gm1__vb2_queue_alloc(struct vb2_queue *q, xxx)
    /* q->buf_struct_size 的值在gm_uvc_queue_init()里被设置.
     * 分配vb2_buffer 的内存 */
    vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
    vb->vb2_queue = q   <tag3>
    /* 分配vb的plane的内存. */
    gm1__vb2_buf_mem_alloc(vb)
        /* vb2_vmalloc_alloc */
        /* 分配一帧的内存, 返回保存内存指针的数据结构的指针. */
        mem_priv = call_ptr_memop(vb, alloc,xxx)
            /* 分配映射到用户空间的内存 */
            buf->vaddr = vmalloc_user(buf->size);
            buf->handler.arg = buf;  <tag4>
        vb->planes[plane].mem_priv = mem_priv;  <tag2>
    /* 统一管理, 所有分配的vb2_buffer, 都将其指针保存至q->bufs[].
     * 以后就可以从q->bufs取出一个vb,来保存一帧的视频数据, 返回给应用程序.*/
    q->bufs[q->num_buffers + buffer] = vb  <tag1>
    /* 设置plane[x].length的值, 即一帧的大小. */
    gm1__setup_lengths(q, buffer)
    /* 设置vb->plane的唯一索引号(offset),
     * 其他函数 通过offset 来找到此vb.
     *
     * 譬如gm__find_plane_by_offset() */
    gm1__setup_offsets(q, buffer)
        vb->planes[plane].m.offset = 0


@uvc_v4l2.c
#ioctl(fd,VIDIOC_QUERYBUF,&bufs); => uvc_ioctl_ops->vidioc_querybuf
gm_uvc_ioctl_querybuf
    /* 这里的buf, 就是应用程序执行ioctl(fd,VIDIOC_QUERYBUF,&bufs);
     * 的bufs*/
    gm_uvc_query_buffer(&stream->queue, buf)
        gm_vb2_querybuf
            gm_vb2_core_querybuf
                /* gm1__fill_v4l2_buffer
                 * 使用vb2_buffer的关于帧内存大小的信息填充pb, 返回给应用程序.*/
                q->buf_ops->fill_user_buffer(q->bufs[index], pb);
                    struct v4l2_buffer *b = pb;
                    /* 在gm1__vb2_queue_alloc()里设置. */
                    b->index = vb->index
                    /* 在gm1__setup_lengths() 里设置 */
                    b->length = vb->planes[0].length;
                    /* 在gm1__setup_offsets() 里设置. */
                    b->m.offset = vb->planes[0].m.offset;


@uvc_v4l2.c
#video0->v4l2_fops.mmap => v4l2_mmap
v4l2_mmap
    vdev->fops->mmap => gm_uvc_v4l2_mmap
#uvc_fops->mmap => gm_uvc_v4l2_mmap
gm_uvc_v4l2_mmap
    gm_uvc_queue_mmap(&stream->queue, vma)
        gm_vb2_mmap
            /* 根据off, 遍历q->bufs[],来找到对应的vb 和 vb->plane.
             * 这里的off是 vb->planes[plane].m.offset,
             * 在gm1__setup_offsets()里设置.
             *
             * 应用程序 执行mmap(NULL, ..., bufs.m.offset);
             * 来传入offset*/
            gm__find_plane_by_offset(q, off, &buffer, &plane);
            vb = q->bufs[buffer];
            /* vb2_vmalloc_mmap */
            /* 内存映射到用户空间 */
            call_memop(vb, mmap,xxx)

@uvc_v4l2.c
#ioctl(fd,VIDIOC_QBUF,&bufs); => uvc_ioctl_ops->vidioc_qbuf
gm_uvc_ioctl_qbuf
    gm1_uvc_queue_buffer
        gm1_vb2_qbuf
            gm1_vb2_internal_qbuf
                /* 这里的输入参数b, 即为应用程序ioctl(fd,VIDIOC_QBUF,&bufs)
                 * 中的bufs. */
                gm1_vb2_core_qbuf(q, b->index, b)
                    vb = q->bufs[index];
                    /* 利用vb,设置uvc_buffer指向的内存指针, 和内存大小*/
                    gm1__buf_prepare()
                    list_add_tail(&vb->queued_entry, &q->queued_list); <tag6>

@videobuf2-core.c
gm1__buf_prepare
    gm1__qbuf_mmap(vb, pb)
        /* gm_uvc_buffer_prepare */
        /* 利用vb,设置uvc_buffer指向的内存指针, 和内存大小*/
        call_vb_qop(vb, buf_prepare, vb)
            /* 由于vb是 vbuf的第一个成员, 且vbuf是buf的第一个成员.
             * 所以转换可以.
             * 在gm1__vb2_queue_alloc()函数对vb分配内存,分配struct uvc_buffer大小的内存.*/
            struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
            struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf);
            /* 将vb->plane所包含的帧内存的指针地址, 复制到uvc_buf->mem. */
            buf->mem = vb2_plane_vaddr(vb, 0); <tag5>
            /* 将vb->plane所包含的帧内存的长度, 复制到uvc_buf->length. */
            buf->length = vb2_plane_size(vb, 0);


@uvc_v4l2.c
#ioctl(fd,VIDIOC_STREAMON,&type); => uvc_ioctl_ops->vidioc_streamon
uvc_queue_streamon
    gm_vb2_streamon
        /* 这里的type, 即为应用程序ioctl(fd,VIDIOC_STREAMON,&type)
         * 中的type*/
        gm_vb2_core_streamon(q, type)
            gm_vb2_start_streaming(q)
                /* 在gm1_vb2_core_qbuf()函数里, 将vb加入queued_list.
                 * 这里将vb取出.*/
                list_for_each_entry(vb, &q->queued_list, queued_entry) {
                    /* 将vb所在的uvc_buffer, 加入stream->queue->irqqueue链表 */
                    gm__enqueue_in_driver(vb) }
                    /* gm_uvc_buffer_queue */
                    call_void_vb_qop(vb, buf_queue, vb)
                        list_add_tail(&buf->queue, &queue->irqqueue) <tag7>
                call_qop(q, start_streaming,xxx) ==> gm_uvc_start_streaming

@uvc_queue.c
gm_uvc_start_streaming
    /* 表示当前queue的buffer是空的. */
    queue->buf_used = 0;
    gm_uvc_video_enable
        /* Commit the streaming parameters. */
        /* GM: 又来一次设置摄像头参数. 不可省略的步骤! */
        /* stream->ctrl 在gm_uvc_ioctl_s_fmt_vid_cap()里被设置.
         *
         * gm_uvc_ioctl_s_fmt_vid_cap() => gm_uvc_v4l2_try_format()的执行流程为:
         *  1. gm_uvc_v4l2_try_format
         *          => gm_uvc_set_video_ctrl(xxxx, probe) VS_PROBE_CONTROL
         *          => gm_uvc_get_video_ctrl(xxx, probe)  VS_PROBE_CONTROL
         *  2. stream->ctrl = probe;
         *
         *  gm_uvc_get_video_ctrl()将当前设置的正确配置保存至probe
         *  在这里使用probe里的正确配置, 再次对摄像头进行设置.*/

        /* GM: 根据uvc规范, 对视频流设置, 先进行VS_PROBE_CONTROL 协商,
         * 然后再确认VS_COMMIT_CONTROL. 仅协商不确认,摄像头无法工作..*/
        uvc_commit_video(stream, &stream->ctrl); /* VS_COMMIT_CONTROL */
        gm_uvc_init_video(stream, GFP_KERNEL);

@uvc_video.c
gm_uvc_init_video(stream, GFP_KERNEL);
    /* fid,即帧的ID号.  
     * Video Stream Payload Header->bmHeaderInfo的第一个位即为fid.
     * 同一帧内, 每次urb的数据的payload_header的fid是相同的.
     * 且fid在每一帧中, 是0,1,0,1地切换.*/                                                                
    /* 这里是fid的初始化. */
    stream->last_fid = -1;
        
    /* 该工作队列将在 */
    stream->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI, 0);

    /* GM: 在该视频流接口的1号设置里,才包含端点.
     * 0号设置, 只是包含一些视频格式, frame 的描述符信息.
     * */
    alts = &intf->altsetting[1];
    /* 找到该接口下的端点. */
    ep = uvc_find_endpoint(alts, stream->header.bEndpointAddress);

    altsetting = alts->desc.bAlternateSetting;

    /* GM: 设置视频流接口为1号设置. 终于要开始传输视频数据了! */
    usb_set_interface(stream->dev->udev, intfnum, altsetting);

    /* 分配并填充同步传输用的urb.(stream->uvc_urb) */
    gm_uvc_init_video_isoc(stream, ep, gfp_flags);

    /* 这些urb就是用来传输视频数据的了. */
    for (i = 0; i < UVC_URBS; ++i) /* UVC_URBS == 5 */
    {
        uvc_urb = &stream->uvc_urb[i];
        usb_submit_urb(uvc_urb->urb, gfp_flags);
    }

@uvc_video.c
gm_uvc_init_video_isoc
    psize = uvc_endpoint_max_bpi(stream->dev->udev, ep); /* 3072 */
    /* stream->ctrl 的值 最终是在 gm_uvc_video_enable() 内被确定. */
    size = stream->ctrl.dwMaxVideoFrameSize; /* 38400 */

    /* size 为一帧图像的大小, psize为每次USB的IN传输的大小.
     * npackets 为所需IN传输的次数, 根据size 和 psize计算. */
    npackets = gm_uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
        struct uvc_urb *uvc_urb = &stream->uvc_urb[i]
        uvc_urb->buffer = usb_alloc_coherent(xxx)

    /* 再次计算所需buffer的大小, 得出的size>= 图像大小. */
    size = npackets * psize;

    for (i = 0; i < UVC_URBS; ++i) { /* UVC_URBS == 5 */                                   
        /* 将分配5个urb */
        struct uvc_urb *uvc_urb = &stream->uvc_urb[i];
        /* 每个urb将重复利用13(npackets)次. */
        urb = usb_alloc_urb(npackets, gfp_flags);
        urb->number_of_packets = npackets;
        /* 在gm_uvc_alloc_urb_buffers() 函数里设置. */
        urb->transfer_buffer = uvc_urb->buffer;
        /* urb传输完成后,即被重复传输13次后, 执行complete函数. */
        urb->complete = gm_uvc_video_complete;
        for (j = 0; j < npackets; ++j) { /* npackets == 13 */
            /* urb重复传输13, 每次重复传输的buffer的地址偏移,和大小. */
            urb->iso_frame_desc[j].offset = j * psize;
            urb->iso_frame_desc[j].length = psize; /* 3072 */
        }
    }

## urb 处理完成后, 执行 gm_uvc_video_complete()
@uvc_video.c
gm_uvc_video_complete
    /* 从queue->irqqueue中取出一个uvc_buffer,
     * 这个uvc_buffer 内所指向的帧内存,
     * 与应用程序的是mmap关系.*/
    buf = uvc_queue_get_current_buffer(queue); //
            __uvc_queue_get_current_buffer(queue)
                list_first_entry(&queue->irqqueue, struct uvc_buffer, queue)

    /* 将urb的buffer内的图像数据指针,
     * 进行Video Stream Payload Header大小的地址偏移,
     * 并保存偏移后的指针.
     *
     * urb的buffer数据分布:
     *      0~x  Video Stream Payload Header
     *      x~y  图像数据
     *
     * 因此,在之后的uvc_video_copy_data_work()函数里,
     * 就可以方便地使用偏移后的buffer地址, 直接复制里面的图像数据了.
     * */
    stream->decode(uvc_urb, buf, buf_meta); /* decode=>gm_uvc_video_decode_isoc */

    /* If no async work is needed, resubmit the URB immediately. */
    /* 当此次urb没有数据接收, 则无需async_work(uvc_video_copy_data_work).
     * uvc_video_copy_data_work()的作用为:
     * 将urb的视频数据,复制到v4l2提供的buf内.
     * 就立即将此urb再此提交.*/
    if (!uvc_urb->async_operations) {
        ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
        return;
    }
    INIT_WORK(&uvc_urb->work, uvc_video_copy_data_work);
    queue_work(stream->async_wq, &uvc_urb->work); /* 调度该工作队列.执行uvc_video_copy_data_work() */

@uvc_video.c
gm_uvc_video_decode_isoc
    /* urb->number_of_packets 在gm_uvc_init_video_isoc()内,
     * 被设置.*/
    for (i = 0; i < urb->number_of_packets; ++i) {
        /* 每次IN传输共用一个buffer,只是偏移不同而已.*/
        mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset; /* offset = offset*i, offset=3072 */
        /* GM: 获得stream_payload_header的长度而已. */
        ret = gm_uvc_video_decode_start(stream, buf, mem, urb->iso_frame_desc[i].actual_length);

        gm_uvc_video_decode_data(uvc_urb, buf, mem + ret, urb->iso_frame_desc[i].actual_length - ret);

        gm_uvc_video_decode_end(stream, buf, mem, urb->iso_frame_desc[i].actual_length);

         /* 当一帧已保存完成, 或buf的空间用完了, 就申请下一个buf.
          * buf->state 在 gm_uvc_video_decode_end()内被设置.*/
        if (buf->state == UVC_BUF_STATE_READY) {
            /* 从queue获取下一个空buf. */
            gm_uvc_video_next_buffers(stream, &buf, &meta_buf);
        }
    }


@uvc_video.c
gm_uvc_video_decode_data
    /* decode结构 将在 uvc_video_copy_data_work()内使用.
     * 将decode->scr的数据复制到 decode->dst, 长度为decode->len*/
    /* buf 即为uvc_buffer. 其所指向的内存与应用程序是mmap关系.
     * data, 为uvc_urb 保存的从usb接收的视频数据.
     * 这里将目的地指向uvc_buffer的mmap内存, 源头为uvc_urb的usb数据.*/
    decode->buf = buf;
    decode->src = data; /* 去掉头部(stream_payload_header)之后的视频数据. */
    decode->dst = buf->mem + buf->bytesused; /* 目的地:将urb的视频数据 复制 到这里. */
    decode->len = min_t(unsigned int, len, maxlen); /* 当前urb的IN包的数据流size不能大于剩余空间 */

    /* 增加queue的buffer的已用空间 */
    buf->bytesused += decode->len;

@uvc_video.c
gm_uvc_video_decode_end
    /* 什么情况下设置 buf->state:
     * 1. 当视频数据到末尾了,说明一帧已传送完毕, 则当前buf就是准备好的了.
     * 2. 当该buf已无剩余空间了,则该buf也是准备好了, 要申请下一个buf,继续接收当前一帧的数据.*/
    if (data[1] & UVC_STREAM_EOF && buf->bytesused != 0) {
        uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
        buf->state = UVC_BUF_STATE_READY;
    }

@uvc_video.c
uvc_video_copy_data_work
    /* 就是将urb的数据复制到queue的buf内.
     * 这里的uvc_urb->copy_operations 的成员的值,
     * 在gm_uvc_video_decode_data() 内被设置.*/
    for (i = 0; i < uvc_urb->async_operations; i++) {
        struct uvc_copy_op *op = &uvc_urb->copy_operations[i];
        memcpy(op->dst, op->src, op->len);
        /* 唤醒gm_uvc_ioctl_dqbuf(), 有vb2_buffer不用,
         * 可以释放了.*/
        uvc_queue_buffer_release(op->buf);
    }
    /* 重新提交这个已完成的urb */
    usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);

@uvc_queue.c
uvc_queue_buffer_release
    kref_put(&buf->ref, gm_uvc_queue_buffer_complete);

gm_uvc_queue_buffer_complete
    gm_vb2_buffer_done <tag8>
        /* 不再使用的vb, 加入完成队列. */
        list_add_tail(&vb->done_entry, &q->done_list);
        /* 唤醒在 gm__vb2_wait_for_done_vb() 函数的等待.
         * 即表明有vb2_buffer不再使用了, 可以将其释放.*/
        wake_up(&q->done_wq);

@uvc_v4l2.c
#ioctl(fd,VIDIOC_DQBUF,&readbuffer); => uvc_ioctl_ops->vidioc_dqbuf
gm_uvc_ioctl_dqbuf
    gm_uvc_dequeue_buffer
        gm_vb2_dqbuf
            /* 参数b, 为应用程序ioctl的输入参数 readbuffer */
            gm_vb2_internal_dqbuf(q, b, nonblocking)
                gm_vb2_core_dqbuf
                    gm__vb2_get_done_vb
                        gm__vb2_wait_for_done_vb
                            /* 当q->done_wq 被执行wake_up(),
                             * 并且 q->done_list 队列不为空时, 唤醒.
                             * 得以继续执行下面的代码.
                             *
                             * gm_vb2_buffer_done()内会执行wake_up(q->done_wq) */
                            wait_event_interruptible(q->done_wq, !list_empty(&q->done_list), xxx)
                     /* 将当前被reset的buffer的索引号,返回给应用程序
                      * 用于应用程序执行qbuf, 将被reset的buffer重新加入queued_list.*/
                    ret = call_bufop(q, fill_user_buffer1, vb, pb); => gm1__fill_v4l2_buffer

@uvc_v4l2.c
#ioctl(fd,VIDIOC_STREAMOFF,&type); => uvc_ioctl_ops->vidioc_streamoff
gm_uvc_ioctl_streamoff
    gm_uvc_queue_streamoff
        gm_vb2_streamoff
            gm_vb2_core_streamoff
                gm__vb2_queue_cancel
                    call_void_qop(q, stop_streaming, q); => gm_uvc_stop_streaming
                    wake_up_all(&q->done_wq);

@uvc_queue.c
gm_uvc_stop_streaming
    /* 选择 摄像头的 无端点的视频流接口, 即关闭视频流传输. */
    gm1_uvc_video_enable

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值