UVC批量传输技术探讨

原创 漫谈嵌入式 2022-09-29 20:26

1.描述符布局

如图为 bulk 传输描述符布局,相对于同步传输,批量传输只有一个可选择的配置,没有备用配置。

  • VideoControl :无变化
  • VideoStream:只有一个 bAlternateSetting(删除alt=1描述符)。同时支持bulk in 端点。

需要修改的地方:

static struct usb_interface_descriptor uvc_streaming_intf_alt0 = {
 .bLength  = USB_DT_INTERFACE_SIZE,
 .bDescriptorType = USB_DT_INTERFACE,
 .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
 .bAlternateSetting = 0,
 .bNumEndpoints  = 1,            /* alt0 挂一个bulk 端点*/
 .bInterfaceClass = USB_CLASS_VIDEO,
 .bInterfaceSubClass = UVC_SC_VIDEOSTREAMING,
 .bInterfaceProtocol = 0x00,
 .iInterface  = 0,
};

端点描述符:

static struct usb_endpoint_descriptor uvc_hs_streaming_ep = {
 .bLength  = USB_DT_ENDPOINT_SIZE,
 .bDescriptorType = USB_DT_ENDPOINT,
 .bEndpointAddress = USB_DIR_IN,
 .bmAttributes  = USB_ENDPOINT_XFER_BULK,
    .wMaxPacketSize = 512,
    .bInterval = 0,
};

2. 控制流程

根据USB规范可知,同步传输方式是只要带中带有同步端点的接口,系统会定时从设备中读取数据,无论设备中是否有数据。而如要停止数据的传输,只需要选中不带有同步端点的接口即可。

USB同步传输这种灵活的数据传输方式是依靠视频流接口的转换接口即我们常说的备份接口实现的。在默认情况下数据不传输时,视频数据流接口和备份接口ID为0,其它的备份接口是可根据视频数据传输的大小可按需选择。

我们知道,批量传输只有一个可选择的altsetting ,那么如何知道设备控制设备的开流和关流动作呢?

2.1 stream on

使用视频流接口的VS_COMMIT_CONTROL 提交给设备,让其以指定的数据格式进行数据采样。

2.2 stream off

关流操作,通过抓包可以看到,通过发送一个clear_halt 请求,来中断流的操作。

2.3 代码分析

基于 linux 4.14.281 内核版本:分析host 端uvc 开关流流程

  • drivers/media/usb/uvc/uvc_queue.c

开流操作:uvc_start_streaming

static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count)
{
 struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
 struct uvc_streaming *stream = uvc_queue_to_stream(queue);
 unsigned long flags;
 int ret;

 queue->buf_used = 0;

 ret = uvc_video_enable(stream, 1);
 if (ret == 0)
  return 0;

 spin_lock_irqsave(&queue->irqlock, flags);
 uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED);
 spin_unlock_irqrestore(&queue->irqlock, flags);

 return ret;
}

关流操作:uvc_stop_streaming

static void uvc_stop_streaming(struct vb2_queue *vq)
{
 struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
 struct uvc_streaming *stream = uvc_queue_to_stream(queue);
 unsigned long flags;

 uvc_video_enable(stream, 0);

 spin_lock_irqsave(&queue->irqlock, flags);
 uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
 spin_unlock_irqrestore(&queue->irqlock, flags);
}

重点关注:uvc_video_enable

/*
 * Enable or disable the video stream.
 */

int uvc_video_enable(struct uvc_streaming *stream, int enable)
{
 int ret;

 if (!enable) {
  uvc_uninit_video(stream, 1);
  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.
    */

   unsigned int epnum = stream->header.bEndpointAddress
        & USB_ENDPOINT_NUMBER_MASK;
   unsigned int dir = stream->header.bEndpointAddress
      & USB_ENDPOINT_DIR_MASK;
   unsigned int pipe;

   pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir;
   usb_clear_halt(stream->dev->udev, pipe);
  }

  uvc_video_clock_cleanup(stream);
  return 0;
 }

 ret = uvc_video_clock_init(stream);
 if (ret < 0)
  return ret;

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

分析代码可知:

  • 首先判断是否关流操作;
  • 如果是,判断接口的可选配置是否大于1,如果大于1,发送usb_set_interface(intfnum,0) 关流,否则发送usb_clear_halt 请求;
  • 如果是开流操作,发送commit 请求
  • 然后初始化 video
/*
 * Initialize isochronous/bulk URBs and allocate transfer buffers.
 */

static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags)
{
 struct usb_interface *intf = stream->intf;
 struct usb_host_endpoint *ep;
 unsigned int i;
 int ret;

 stream->sequence = -1;
 stream->last_fid = -1;
 stream->bulk.header_size = 0;
 stream->bulk.skip_payload = 0;
 stream->bulk.payload_size = 0;

 uvc_video_stats_start(stream);

 if (intf->num_altsetting > 1) {
  struct usb_host_endpoint *best_ep = NULL;
  unsigned int best_psize = UINT_MAX;
  unsigned int bandwidth;
  unsigned int uninitialized_var(altsetting);
  int intfnum = stream->intfnum;

  /* Isochronous endpoint, select the alternate setting. */
  bandwidth = stream->ctrl.dwMaxPayloadTransferSize;

  if (bandwidth == 0) {
   uvc_trace(UVC_TRACE_VIDEO, "Device requested null "
    "bandwidth, defaulting to lowest.\n");
   bandwidth = 1;
  } else {
   uvc_trace(UVC_TRACE_VIDEO, "Device requested %u "
    "B/frame bandwidth.\n", bandwidth);
  }

  for (i = 0; i < intf->num_altsetting; ++i) {
   struct usb_host_interface *alts;
   unsigned int psize;

   alts = &intf->altsetting[i];
   ep = uvc_find_endpoint(alts,
    stream->header.bEndpointAddress);
   if (ep == NULL)
    continue;

   /* Check if the bandwidth is high enough. */
   psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
   if (psize >= bandwidth && psize <= best_psize) {
    altsetting = alts->desc.bAlternateSetting;
    best_psize = psize;
    best_ep = ep;
   }
  }

  if (best_ep == NULL) {
   uvc_trace(UVC_TRACE_VIDEO, "No fast enough alt setting "
    "for requested bandwidth.\n");
   return -EIO;
  }

  uvc_trace(UVC_TRACE_VIDEO, "Selecting alternate setting %u "
   "(%u B/frame bandwidth).\n", altsetting, best_psize);

  ret = usb_set_interface(stream->dev->udev, intfnum, altsetting);
  if (ret < 0)
   return ret;

  ret = uvc_init_video_isoc(stream, best_ep, gfp_flags);
 } else {
  /* Bulk endpoint, proceed to URB initialization. */
  ep = uvc_find_endpoint(&intf->altsetting[0],
    stream->header.bEndpointAddress);
  if (ep == NULL)
   return -EIO;

  /* Reject broken descriptors. */
  if (usb_endpoint_maxp(&ep->desc) == 0)
   return -EIO;

  ret = uvc_init_video_bulk(stream, ep, gfp_flags);
 }

 if (ret < 0)
  return ret;

 /* Submit the URBs. */
 for (i = 0; i < UVC_URBS; ++i) {
  ret = usb_submit_urb(stream->urb[i], gfp_flags);
  if (ret < 0) {
   uvc_printk(KERN_ERR, "Failed to submit URB %u "
     "(%d).\n", i, ret);
   uvc_uninit_video(stream, 1);
   return ret;
  }
 }

 /* The Logitech C920 temporarily forgets that it should not be adjusting
  * Exposure Absolute during init so restore controls to stored values.
  */

 if (stream->dev->quirks & UVC_QUIRK_RESTORE_CTRLS_ON_INIT)
  uvc_ctrl_restore_values(stream->dev);

 return 0;
}

从这段代码可以看出,如果altsetting 大于1 走同步传输,发送usb_set_interface(intfnum, altsetting) ,选择合适带宽配置。然后初始化同步传输管道。

否则,初始化 同步传输管道,提交传输。

3. 其他注意点

对比同步传输和批量传输我们可以发现,对于uvc 批量传输, 由于没有同步传输类似的多个可选配置,所以没法灵活控制开流关流操作。特别是在linux 平台下,要切换不同的格式和分辨率的时候没有同步传输方便。

故,笔者觉得同步传输适合传固定数据,或者对usb camera 做中转使用比较合适。

对于批量传输如果能充分发送usb 吞吐量,(USB2.0)一个微帧传输13个packet,理论带宽将近50MB/s, 笔者实际测试能达到47MB/s,对于YUYV图像能够极大提高帧率。

如果有合适的方式控制流程,欢迎讨论!


漫谈嵌入式 一个爱跑马的程序员。致力于分享嵌入式物联网技术【单片机/MIPS/ARM/Linux/C/C++/Qt等】,探寻底层开发的究竟!
评论 (0)
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦