最近在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驱动中对于帧速设置的代码
- static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream,
- struct v4l2_streamparm *parm)
- {
- struct uvc_frame *frame = stream->cur_frame;
- struct uvc_streaming_control probe;
- struct v4l2_fract timeperframe;
- uint32_t interval;
- int ret;
- if (parm->type != stream->type)
- return -EINVAL;
- if (uvc_queue_streaming(&stream->queue))
- return -EBUSY;
- if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
- timeperframe = parm->parm.capture.timeperframe;
- else
- timeperframe = parm->parm.output.timeperframe;
- memcpy(&probe, &stream->ctrl, sizeof probe);
- interval = uvc_fraction_to_interval(timeperframe.numerator,
- timeperframe.denominator);
- uvc_trace(UVC_TRACE_FORMAT, "Setting frame interval to %u/%u (%u).\n",
- timeperframe.numerator, timeperframe.denominator, interval);
- probe.dwFrameInterval = uvc_try_frame_interval(frame, interval);
- /* Probe the device with the new settings. */
- ret = uvc_probe_video(stream, &probe);
- if (ret < 0)
- return ret;
- memcpy(&stream->ctrl, &probe, sizeof probe);
- /* Return the actual frame period. */
- timeperframe.numerator = probe.dwFrameInterval;
- timeperframe.denominator = 10000000;
- uvc_simplify_fraction(&timeperframe.numerator,
- &timeperframe.denominator, 8, 333);
- if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
- parm->parm.capture.timeperframe = timeperframe;
- else
- parm->parm.output.timeperframe = timeperframe;
- return 0;
- }
首先介绍下结构体struct v4l2_fract timeperframe;
struct v4l2_fract {
__u32 numerator;
__u32 denominator;
};
numerator是分子,一般设置为1,而denominator是分母,这里就是你设置的帧速fps。那么驱动是如何处理的呢?
17行是从上层传递你设置的fps,22行是将这个分数通过算法换算成帧间隔:
- /* Convert a fraction to a frame interval in 100ns multiples. The idea here is
- * to compute numerator / denominator * 10000000 using 32 bit fixed point
- * arithmetic only.
- */
- uint32_t uvc_fraction_to_interval(uint32_t numerator, uint32_t denominator)
- {
- uint32_t multiplier;
- /* Saturate the result if the operation would overflow. */
- if (denominator == 0 ||
- numerator/denominator >= ((uint32_t)-1)/10000000)
- return (uint32_t)-1;
- /* Divide both the denominator and the multiplier by two until
- * numerator * multiplier doesn't overflow. If anyone knows a better
- * algorithm please let me know.
- */
- multiplier = 10000000;
- while (numerator > ((uint32_t)-1)/multiplier) {
- multiplier /= 2;
- denominator /= 2;
- }
- return denominator ? numerator * multiplier / denominator : 0;
- }
例如设置20fps,那么interval=10 000 000 / 20 = 500 000,这个是你期望的值。那么下面要根据你具体摄像头的参数重新计算这个interval,看27行代码
- /*
- * Find the frame interval closest to the requested frame interval for the
- * given frame format and size. This should be done by the device as part of
- * the Video Probe and Commit negotiation, but some hardware don't implement
- * that feature.
- */
- static __u32 uvc_try_frame_interval(struct uvc_frame *frame, __u32 interval)
- {
- unsigned int i;
- if (frame->bFrameIntervalType) { //pap7501此值为0
- __u32 best = -1, dist;
- for (i = 0; i < frame->bFrameIntervalType; ++i) {
- dist = interval > frame->dwFrameInterval[i]
- ? interval - frame->dwFrameInterval[i]
- : frame->dwFrameInterval[i] - interval;
- if (dist > best)
- break;
- best = dist;
- }
- interval = frame->dwFrameInterval[i-1];
- } else {
- const __u32 min = frame->dwFrameInterval[0];
- const __u32 max = frame->dwFrameInterval[1];
- const __u32 step = frame->dwFrameInterval[2];
- interval = min + (interval - min + step/2) / step * step;
- if (interval > max)
- interval = max;
- }
- return interval;
- }
很明显,这边的一些参数要配合USB CAMERA的描述符信息了,这里列出一部分pap7501全速USB模式下的参数信息:
- VideoStreaming Interface Descriptor:
- bLength 38
- bDescriptorType 36
- bDescriptorSubtype 7 (FRAME_MJPEG)
- bFrameIndex 5
- bmCapabilities 0x00
- Still image unsupported
- wWidth 352
- wHeight 288
- dwMinBitRate 128000
- dwMaxBitRate 3649536
- dwMaxVideoFrameBufferSize 152064
- dwDefaultFrameInterval 333333
- bFrameIntervalType 0
- dwMinFrameInterval 333333
- dwMaxFrameInterval 9999990
- 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模式,其描述符为
- VideoStreaming Interface Descriptor:
- bLength 50
- bDescriptorType 36
- bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)
- bFrameIndex 1
- bmCapabilities 0x00
- Still image unsupported
- wWidth 640
- wHeight 480
- dwMinBitRate 614400
- dwMaxBitRate 18432000
- dwMaxVideoFrameBufferSize 614400
- dwDefaultFrameInterval 333333
- bFrameIntervalType 6
- dwFrameInterval( 0) 333333
- dwFrameInterval( 1) 500000
- dwFrameInterval( 2) 666666
- dwFrameInterval( 3) 1000000
- dwFrameInterval( 4) 2000000
- 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切换造成了请求延迟。
阅读(7694) | 评论(0) | 转发(0) |