#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
1029

被折叠的 条评论
为什么被折叠?



