做过usbcamera的同学们应该都知道,usbcamera有一个缓存队列,当应用上面调用startPreview的时候,就会层层的调到usbcamera 的ioctl(mFd, VIDIOC_DQBUF, &tmp_buf);用于从队列里出去一个缓存,并将这个缓存返回给用户去处理。当处理完后,要接着调用ioctl (mFd, VIDIOC_QBUF, &tmp_buf);来将刚刚出队的这个缓存重新放回队列。否则会导致队列里的缓存不够而读不出任何东西来,表现为黑屏。
usbcamera提供了一系列的ioctl来操作视频流的处理,但没有提供清空缓存队列这个功能。如果刚好某个应用,只open了usbcamera一次,然后就重复调用startPreview和stopPreview,这个时候就会出现一个问题。
即当用户在stopPreview的时候,最后一次读取的这个缓存队列(假设有8个缓存buf)里的buf的id为1,也就是只读了这个队列的第1个,还有剩下的7个缓存里面的数据没有被读出来。而这个时候上层用户调用了stopPreview,停止调用缓存数据。那么就会出,下次调用startPreview的时候,会先将这剩下的7个buf读出来的情况。因为我们的队列是先进先出的,你上次stop的时候出了一个,剩下的7个自然又自动往前排了,坐等下次取出来。
以倒车为例,上次司机倒车完后,下次如果再启动倒车,再调用startPreview的时候,首先会显示上次倒车时最后的影像,然后才会接着显示新的影像。
要解决这个BUG,首先想到的是要将这个队列给清空。蛋疼的是,usbcamera没有清空的ioctl操作。不过我们可以自己想办法来模拟清空的操作。比如我们在启动倒车前,先循环将这个缓存里的Buf出队,然后再入队。这么一来,入队的buf,录的自然就是新的影像了。上代码:
//倒车前,先清掉缓存里残留的上次倒车最后画面
if(status == UVC_PARK)
{
int numBufs = 0;
int result = 0;
struct v4l2_buffer tmp_buf ;
#if V4L2DEVICE_FPS_LIMIT > 0
struct timeval prevTime;
gettimeofday(&prevTime, NULL);
#endif
for (numBufs = 0; numBufs < V4L2DEVICE_BUF_COUNT; numBufs++)
{
memset( &tmp_buf, 0, sizeof(tmp_buf) );
tmp_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
tmp_buf.memory = V4L2_MEMORY_MMAP ;
tmp_buf.index = 0;
result = ioctl(mFd, VIDIOC_DQBUF, &tmp_buf);
if ( result != 0)
{
ALOGD("v4l2UvcStatusUpdate VIDIOC_DQBUF error (id=%u): %s (%d)", numBufs, strerror(errno), errno);
// return;
}
else
{
ALOGD("v4l2UvcStatusUpdate VIDIOC_DQBUF done numBufs is %d, index is %d ", numBufs, tmp_buf.index);
}
result = ioctl (mFd, VIDIOC_QBUF, &tmp_buf);
if ( result < 0)
{
ALOGD("v4l2UvcStatusUpdate Could not VIDIOC_QBUF %d: %s (%d)", numBufs, strerror(errno), errno);
}
else
{
ALOGD("v4l2UvcStatusUpdate VIDIOC_QBUF done numBufs is %d ", numBufs);
}
#if V4L2DEVICE_FPS_LIMIT > 0
double step = 1000*1000/V4L2DEVICE_FPS_LIMIT;//微秒
struct timeval nowTime;
gettimeofday(&nowTime, NULL);
double interval = (nowTime.tv_usec - prevTime.tv_usec);
ALOGD("v4l2UvcStatusUpdate startTime is [%ld] , nowTime is [%ld] \n", prevTime.tv_usec, nowTime.tv_usec);
ALOGD("v4l2UvcStatusUpdate FPS is [%d] , interval time is [%lf] , step is %lf\n", V4L2DEVICE_FPS_LIMIT, interval, step);
//如果是放在缓存里,两次取缓存的时间间隔会非常的短,都在1000微秒以内。而如果是正常读取队列的话,会在30*1000到50*1000之间。
//所以下面用一个正常时的间隔的一半值来做阈值就足够了。
if(interval >= step/2)
{
ALOGD("v4l2UvcStatusUpdate this frame is not buff ");
break;
}
prevTime = nowTime;
#endif
}
}