Chinaunix首页 | 论坛 | 博客
  • 博客访问: 535481
  • 博文数量: 86
  • 博客积分: 1076
  • 博客等级: 准尉
  • 技术积分: 1018
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-02 19:15
文章分类

全部博文(86)

文章存档

2013年(15)

2012年(69)

2011年(2)

分类: LINUX

2012-05-11 08:37:55

     最近在USB全速模式下运行一款原相PAP7501的USB camera的时候,发现我们的系统在设置的所谓20fps到25fps的时候,系统的负载发生了急剧的变化,而根据这款摄像头的mjpeg压缩传过来的一帧图片的数据量在10KB左右,应该只是50KB左右的数据量上的差距啊,但是为何性能发生了巨变呢?
     带着这个疑惑,根据手头的资源,最先想到的是利用USB的抓包仪(当然也可以在应用层打时间戳),分别看了一下,15fps、20fps、25fps、30fps下,一帧图片分多少个包搬运完的,因为对于全速USB模式,摄像头工作在ISO(同步传输)模式,每1ms会搬运一次数据包。那么例如20fps,那么一帧图片应该在50ms,也就是分50个包搬完。(mjpeg图片头部有ff d8 尾部有ff d9标志,再结合UVC驱动在每个包头部的12字节的某些标志位,可以清晰的辨认一帧图片的起止,贴出一段数据供参考)
 
     但事实却非如此,实验表明,15fps、20fps均是65个包搬完,25fps、30fps均是33个包搬完。恍然大悟,也就是spec上说的最大支持30fps的说法,并不是说支持所有的帧率。
     跟踪UVC驱动中对于帧速设置的代码

  1. static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream,       
  2.         struct v4l2_streamparm *parm)
  3. {
  4.     struct uvc_frame *frame = stream->cur_frame;
  5.     struct uvc_streaming_control probe;
  6.     struct v4l2_fract timeperframe;
  7.     uint32_t interval;
  8.     int ret;

  9.     if (parm->type != stream->type)
  10.         return -EINVAL;

  11.     if (uvc_queue_streaming(&stream->queue))
  12.         return -EBUSY;

  13.     if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)           
  14.         timeperframe = parm->parm.capture.timeperframe;
  15.     else
  16.         timeperframe = parm->parm.output.timeperframe;

  17.     memcpy(&probe, &stream->ctrl, sizeof probe);
  18.     interval = uvc_fraction_to_interval(timeperframe.numerator,  
  19.         timeperframe.denominator);

  20.     uvc_trace(UVC_TRACE_FORMAT, "Setting frame interval to %u/%u (%u).\n",
  21.         timeperframe.numerator, timeperframe.denominator, interval);
  22.     probe.dwFrameInterval = uvc_try_frame_interval(frame, interval);   

  23.     /* Probe the device with the new settings. */
  24.     ret = uvc_probe_video(stream, &probe);           
  25.     if (ret < 0)
  26.         return ret;

  27.     memcpy(&stream->ctrl, &probe, sizeof probe);

  28.     /* Return the actual frame period. */
  29.     timeperframe.numerator = probe.dwFrameInterval;
  30.     timeperframe.denominator = 10000000;
  31.     uvc_simplify_fraction(&timeperframe.numerator,                
  32.         &timeperframe.denominator, 8, 333);

  33.     if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
  34.         parm->parm.capture.timeperframe = timeperframe;
  35.     else
  36.         parm->parm.output.timeperframe = timeperframe;

  37.     return 0;
  38. }


首先介绍下结构体struct v4l2_fract timeperframe;
struct v4l2_fract {
 __u32   numerator;
 __u32   denominator;
};
numerator是分子,一般设置为1,而denominator是分母,这里就是你设置的帧速fps。那么驱动是如何处理的呢?
17行是从上层传递你设置的fps,22行是将这个分数通过算法换算成帧间隔:

 
  1. /* Convert a fraction to a frame interval in 100ns multiples. The idea here is
  2.  * to compute numerator / denominator * 10000000 using 32 bit fixed point
  3.  * arithmetic only.
  4.  */
  5. uint32_t uvc_fraction_to_interval(uint32_t numerator, uint32_t denominator)  
  6. {
  7.     uint32_t multiplier;

  8.     /* Saturate the result if the operation would overflow. */
  9.     if (denominator == 0 ||
  10.      numerator/denominator >= ((uint32_t)-1)/10000000)
  11.         return (uint32_t)-1;

  12.     /* Divide both the denominator and the multiplier by two until
  13.      * numerator * multiplier doesn't overflow. If anyone knows a better
  14.      * algorithm please let me know.
  15.      */
  16.     multiplier = 10000000;
  17.     while (numerator > ((uint32_t)-1)/multiplier) {
  18.         multiplier /= 2;
  19.         denominator /= 2;
  20.     }

  21.     return denominator ? numerator * multiplier / denominator : 0;
  22. }

例如设置20fps,那么interval=10 000 000 / 20 = 500 000,这个是你期望的值。那么下面要根据你具体摄像头的参数重新计算这个interval,看27行代码
 

  1. /*
  2.  * Find the frame interval closest to the requested frame interval for the
  3.  * given frame format and size. This should be done by the device as part of
  4.  * the Video Probe and Commit negotiation, but some hardware don't implement
  5.  * that feature.
  6.  */
  7. static __u32 uvc_try_frame_interval(struct uvc_frame *frame, __u32 interval)
  8. {
  9.     unsigned int i;

  10.     if (frame->bFrameIntervalType) {                        //pap7501此值为0
  11.         __u32 best = -1, dist;

  12.         for (i = 0; i < frame->bFrameIntervalType; ++i) {
  13.             dist = interval > frame->dwFrameInterval[i]
  14.              ? interval - frame->dwFrameInterval[i]
  15.              : frame->dwFrameInterval[i] - interval;

  16.             if (dist > best)
  17.                 break;

  18.             best = dist;
  19.         }

  20.         interval = frame->dwFrameInterval[i-1];
  21.     } else {
  22.         const __u32 min = frame->dwFrameInterval[0];
  23.         const __u32 max = frame->dwFrameInterval[1];
  24.         const __u32 step = frame->dwFrameInterval[2];

  25.         interval = min + (interval - min + step/2) / step * step;
  26.         if (interval > max)
  27.             interval = max;
  28.     }

  29.     return interval;
  30. }
很明显,这边的一些参数要配合USB CAMERA的描述符信息了,这里列出一部分pap7501全速USB模式下的参数信息:

  1. VideoStreaming Interface Descriptor:
  2. bLength 38
  3. bDescriptorType 36
  4. bDescriptorSubtype 7 (FRAME_MJPEG)
  5. bFrameIndex 5
  6. bmCapabilities 0x00
  7. Still image unsupported
  8. wWidth 352
  9. wHeight 288
  10. dwMinBitRate 128000
  11. dwMaxBitRate 3649536
  12. dwMaxVideoFrameBufferSize 152064
  13. dwDefaultFrameInterval 333333
  14. bFrameIntervalType 0
  15. dwMinFrameInterval 333333
  16. dwMaxFrameInterval 9999990
  17. dwFrameIntervalStep 333333

14行表明帧间隔类型是0,所以上面代码的11行的if分支不满足,走else分支,只是在找一个最接近的值。分析一下这款摄像头全速模式下,最小的间隔是333333,最大是9999990,步长333333,那么根据前面的算法,interval = 10 000 000 / fps,若设置为30fps,那么interval = 333333,如设置为25fps,结果是400000,最接近的是333333,等于还是30fps,类似对于20fps,最接近的是666666,其实就是15fps。所以这款摄像头全速模式支持的fps应该为30/n,也就是30fps、15fps、10fps......

对于高速USB模式,其描述符为


 

  1. VideoStreaming Interface Descriptor:
  2. bLength 50
  3. bDescriptorType 36
  4. bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)
  5. bFrameIndex 1
  6. bmCapabilities 0x00
  7. Still image unsupported
  8. wWidth 640
  9. wHeight 480
  10. dwMinBitRate 614400
  11. dwMaxBitRate 18432000
  12. dwMaxVideoFrameBufferSize 614400
  13. dwDefaultFrameInterval 333333
  14. bFrameIntervalType 6
  15. dwFrameInterval( 0) 333333
  16. dwFrameInterval( 1) 500000
  17. dwFrameInterval( 2) 666666
  18. dwFrameInterval( 3) 1000000
  19. dwFrameInterval( 4) 2000000
  20. dwFrameInterval( 5) 10000000

其interval参数为6,参照uvc_try_frame_interval函数,这里会比较你的设置值,例如这里就支持20fps,因为interval有500000这个值。此时支持的帧率为30fps、20fps、15fps、10fps、5fps、1fps。

因此,对于熟悉USB UVC驱动的同行来说,应该明白uvc驱动会从urb缓冲区urb->transfer_buffer地址将所有的数据剥离12字节数据头之后平凑成一块连续的数据。而这个操作是在一个urb切换的中断函数中进行的,这个操作时间且不可过长,与系统的访存(memcpy)和中断响应性能有很重要的关系。如果处理时间过长,会导致1ms之内未发生USB ISO请求,导致USB丢包问题,图像就会出错,我们的平台目前就遇到了这个问题。

虽然通过将数据搬运挪至中断底半部tasklet运行,可以使驱动迅速发出ISO请求,但此时我们的系统性能会影响USB WIFI的实时性,亟待解决。开始所谓的20fps到25fps的转换(实际是15fps->30fps,数据量翻倍了),实则是一次urb中断中memcpy拷贝数据量的double,应该关注的是什么原因导致了此时urb切换造成了请求延迟。

 


阅读(7717) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~