前段时间有个读者咨询UVC bulk 传输实现,接着这个机会重新梳理一遍UVC bulk 传输实现思路,同时对比ISO 与 Bulk 实现不同。有关bulk 传输实现请先看前文
阅读此文前提:假设读者,已知晓UVC 协议,以及UVC 设备驱动框架
ISO 传输模式下的描述符布局如上图所示:
接口0:VideoControl。处理 UVC CT/PU/XU 等处理,主要用来控制 接口1:VideoStream。处理UVC 视频流控制。
一个VideoControl 可以关联一个或者多个流接口。每个流接口下面,关联2个多或者多个接口配置。
对于ISO 传输,流接口下有多个备用接口,用来控制视频流的传输。一般用alt0 关流,altx 开流。
BULK 传输模式下的描述符布局如上图所示:
接口0:VideoControl。处理 UVC CT/PU/XU 等处理,主要用来控制 接口1:VideoStream。处理UVC 视频流控制。
对于BULK 传输, 与ISO 模式最大的不同,是流接口只有一个alt0 接口配置。故,对其视频流的控制流程,相比较ISO 模式比较复杂一些。
根据USB规范可知,同步传输方式是只要选中中带有同步端点的接口,系统会定时从设备中读取数据,无论设备中是否有数据。而如要停止数据的传输,只需要选中不带有同步端点的接口即可。
USB同步传输这种灵活的数据传输方式是依靠视频流接口的转换接口即我们常说的备份接口实现的。
Stream ON:
Stream OFF:
整个视频流的控制流程(开流/关流),可以在设备收到set_alt1/0 后进行处理。
从描述符上分析 UVC 的 bulk 传输只有一个备用接口。故无法通过ISO 模式下通过备用接口进行开关流控制。
StreamOn:
StreamOff
通过抓包对比发现,开流过程通过uvc probe 与commit 流程处理。关流过程主机会下发一个clear_halt 请求。在clear_halt 请求里面做关流的后处理。
通过对比发现,对于ISO 传输,针对UVC 的设备描述修改基本不大,难处理的是UVC 的控制流程,特别是视频流的处理,接下来我们会以linux 平台介绍如何在iso 基础上修改为 bulk 模式
我们以linux 平台举例,其他平台只要熟悉uvc 底层协议,即可快速迁移。
源码位置:driver/media/uvc/uvc_queue.c
uvc 使能:
int uvc_video_enable(struct uvc_streaming *stream, int enable)
{
int ret;
if (!enable) {
...
if (stream->intf->num_altsetting > 1) {
usb_set_interface(stream->dev->udev,
stream->intfnum, 0);
} else {
/* UVC doesn't specify how to inform a bulk-based device
* when the video stream is stopped. Windows sends a
* CLEAR_FEATURE(HALT) request to the video streaming
* bulk endpoint, mimic the same behaviour.
*/
......
usb_clear_halt(stream->dev->udev, pipe);
}
......
return 0;
}
/* Commit the streaming parameters. */
ret = uvc_commit_video(stream, &stream->ctrl);
if (ret < 0)
goto error_commit;
ret = uvc_init_video(stream, GFP_KERNEL);
if (ret < 0)
goto error_video;
return 0;
error_video:
usb_set_interface(stream->dev->udev, stream->intfnum, 0);
error_commit:
uvc_video_clock_cleanup(stream);
return ret;
}
StreamOn:
uvc_start_streaming() //
ret = uvc_video_enable(stream, 1);
StreamOff:
uvc_stop_streaming();
uvc_video_enable(stream, 0);
从host 端源码分析和我们猜想得到了印证:
视频传输:源码参考:driver/media/usb/uvc_video.c
// usb complete 中断
static void uvc_video_complete(struct urb *urb)
{
struct uvc_streaming *stream = urb->context;
struct uvc_video_queue *queue = &stream->queue;
struct uvc_buffer *buf = NULL;
unsigned long flags;
int ret;
switch (urb->status) {
case 0:
break;
default:
uvc_printk(KERN_WARNING, "Non-zero status (%d) in video "
"completion handler.\n", urb->status);
/* fall through */
case -ENOENT: /* usb_kill_urb() called. */
if (stream->frozen)
return;
/* fall through */
case -ECONNRESET: /* usb_unlink_urb() called. */
case -ESHUTDOWN: /* The endpoint is being disabled. */
uvc_queue_cancel(queue, urb->status == -ESHUTDOWN);
return;
}
spin_lock_irqsave(&queue->irqlock, flags);
if (!list_empty(&queue->irqqueue))
buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
queue);
spin_unlock_irqrestore(&queue->irqlock, flags);
/* 视频解码 :视频传输关键 */
stream->decode(urb, stream, buf);
/* 提交urb */
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
ret);
}
}
host 端视频数据解码:
stream->decode(urb, stream, buf);
uvc_video_decode_bulk();
uvc_video_decode_start(); // 解析uvc header
uvc_video_decode_data(); // 解析视频数据
uvc_video_decode_end(); // 帧结束标记
源码位置:driver/usb/gadget/function
ISO 开关流
static int uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
{
.......
/* VideoControl process */
if (interface == uvc->control_intf) {
/* 复位控制端点 */
usb_ep_disable(uvc->control_ep);
...
usb_ep_enable(uvc->control_ep);
if (uvc->state == UVC_STATE_DISCONNECTED) {
/*提交uvc 状态 */
memset(&v4l2_event, 0 ,sizof(v4l2_evetn);
v4l2_event.type = UVC_EVENT_CONNECT;
uvc_event->speed = cdev->gadget->speed;
v4l2_event_queue(&uvc->vdev, &v4l2_event);
uvc->state = UVC_STATE_CONNECTED;
}
return 0;
}
/* VideoStream process */
if (interface != uvc->streaming_intf) {
return -EINVAL;
}
/* 判断端点是否为bulk 端点: */
if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep)) {
/* 使能端点 */
usb_ep_enable(uvc->video_ep);
return alt ? -EIVAL : 0;
}
/*ISO 开关流处理 */
switch (alt) {
case 0:
if (uvc->state != UVC_STATE_STREAMING)
return 0;
if (uvc->video.ep)
usb_ep_disable(uvc->video.ep);
/*提交应用 : 关流*/
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_STREAMOFF;
v4l2_event_queue(&uvc->vdev, &v4l2_event);
uvc->state = UVC_STATE_CONNECTED;
return 0;
case 1:
if (uvc->state != UVC_STATE_CONNECTED)
return 0;
if (!uvc->video.ep)
return -EINVAL;
INFO(cdev, "reset UVC\n");
usb_ep_disable(uvc->video.ep);
ret = config_ep_by_speed(f->config->cdev->gadget,
&(uvc->func), uvc->video.ep);
if (ret)
return ret;
usb_ep_enable(uvc->video.ep);
/* 开流*/
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_STREAMON;
v4l2_event_queue(&uvc->vdev, &v4l2_event);
return USB_GADGET_DELAYED_STATUS;
default:
return -EINVAL;
}
}
bulk 传输开关流
uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct uvc_device *uvc = to_uvc(f);
struct v4l2_event v4l2_event;
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
/* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n",
* ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue),
* le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
*/
if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
/* bulk 传输关流:需要修改udc 控制器,将usb 控制器clear_halt 注册到uvc function 驱动里面 */
if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep)) {
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_STREANOFF;
memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
v4l2_event_queue(&uvc->vdev, &v4l2_event);
} else {
INFO(f->config->cdev, "invalid request type\n");
return -EINVAL;
}
}
/* Stall too big requests. */
if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE)
return -EINVAL;
/* Tell the complete callback to generate an event for the next request
* that will be enqueued by UVCIOC_SEND_RESPONSE.
*/
uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
uvc->event_length = le16_to_cpu(ctrl->wLength);
/* bulk 传输 开流:通过uvc probe 和 commit 提交分辨率和帧率控制 */
memset(&v4l2_event, 0, sizeof(v4l2_event));
v4l2_event.type = UVC_EVENT_SETUP;
memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
v4l2_event_queue(&uvc->vdev, &v4l2_event);
return 0;
}
整个bulk 传输控制流程如上图所示。
对于linux 平台而言,需要关心的有两点:1)如何将底层clear_halt 请求 与uvc_function 请求关联上,而不影响其他端点;2)如何将clear_halt 请求提交到应用层去处理。这一步不是非必须的。
对于Rtos 平台大同小异,只要理解了整个开关流流程,处理起来自然简单许多。