资源:
分析UVC驱动所使用的应用程序源码:drivers/media/usb/uvc/uvc_app.c · suiren/rk3328_kernel - Gitee.com
本博客的txt版:drivers/media/usb/uvc/uvc_probe_read.c · suiren/rk3328_kernel - Gitee.com
UVC驱动精简后的代码:rk3328_kernel: UVC 驱动精简.详见 drivers/media/usb/uvc.4.4.194 内核 rk3328 - Gitee.com
注意:
本博客中使用的函数名, 在分析过程中被我修改了,添加了前缀gm.
去掉前缀,即为原函数名.譬如:
gm_uvc_parse_control 对应原函数名为 uvc_parse_control .
总体uvc驱动执行流程
1. 驱动初始化, 生成video0节点. uvc_probe().
2. 流协商, 设置视频格式与分辨率.主要见gm_uvc_ioctl_s_fmt_vid_cap()函数.
3. 创建urb并提交, 等待urb完成. 主要见gm_uvc_init_video().
4. 将urb的视频数据复制到v4l2提供的buf内. 应用程序从此buf获得视频数据.
主要见 gm_uvc_video_complete()和 uvc_video_copy_data_work()函数.
uvc驱动初始化流程
@uvc_driver.c
uvc_probe
/* 填充 stream, stream->format, stream->format->frame*/
gm_uvc_parse_control
/* 生成video0设备节点, 关联 ops供应用层调用. */
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;
vb2_queue_init(&queue->queue);
/* 生成设备节点, video0 */
video_register_device
UVC执行流程分析
接下来, 结合应用程序,来分析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); => uvc_fops->open
gm_uvc_v4l2_open
/* 形成 file->private_data->stream 的关联
* 每一个的应用层调用,包括ioctl, 都有fie作为输入参数,
* 因此,stream则一直伴随着我们...*/
handle->stream = stream;
file->private_data = handle;
@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
/* 这就没什么好分析了, 直接使用v4l2的函数.. */
uvc_request_buffers(&stream->queue, rb);
vb2_reqbuf
@uvc_queue.c
/* 执行gm_uvc_ioctl_reqbufs 时会由v4l2来继续执行gm_uvc_queue_setup
* 在gm_uvc_register_video_device()函数内, 关联了uvc_queue_qops.*/
uvc_queue_qops->queue_setup
gm_uvc_queue_setup
@uvc_v4l2.c
#ioctl(fd,VIDIOC_QUERYBUF,&bufs); => uvc_ioctl_ops->vidioc_querybuf
gm_uvc_ioctl_querybuf
uvc_query_buffer(&stream->queue, buf)
vb2_querybuf
@uvc_v4l2.c
#mmap => uvc_fops->mmap
uvc_v4l2_mmap
uvc_queue_mmap(&stream->queue, vma)
vb2_mmap
@uvc_v4l2.c
#ioctl(fd,VIDIOC_QBUF,&bufs); => uvc_ioctl_ops->vidioc_qbuf
gm_uvc_ioctl_qbuf
uvc_queue_buffer
vb2_qbuf
@uvc_queue.c
/* v4l2 会接着执行以下函数.*/
gm_uvc_buffer_prepare
@uvc_v4l2.c
#ioctl(fd,VIDIOC_STREAMON,&type); => uvc_ioctl_ops->vidioc_streamon
uvc_queue_streamon
vb2_streamon
/* v4l2 会接着执行以下函数.*/
@uvc_queue.c
gm_uvc_buffer_queue
@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
/* 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);
/* 再次计算所需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;
/* 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 */
}
}
@uvc_video.c
gm_uvc_video_complete
/* 从queue中取出一个buffer, 这个buffer最终是v4l2给到应用程序的.*/
buf = uvc_queue_get_current_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.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*/
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);
}
/* 重新提交这个已完成的urb */
usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
@uvc_v4l2.c
#ioctl(fd,VIDIOC_DQBUF,&readbuffer); => uvc_ioctl_ops->vidioc_dqbuf
gm_uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf
@uvc_v4l2.c
#ioctl(fd,VIDIOC_STREAMOFF,&type); => uvc_ioctl_ops->vidioc_streamoff
gm_uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff
/* v4l2 会接着执行以下函数.*/
@ uvc_queue.c
gm_uvc_stop_streaming
gm1_uvc_video_enable
/* 将stream->uvc_urb释放 */
gm_uvc_uninit_video(stream, 1);
/* 设置摄像头的视频流接口为 0号设置.
* 即关闭视频流传输.*/
usb_set_interface(stream->dev->udev, stream->intfnum, 0);