11月18日 ========== v4l2_play.c文件wihle循环释疑 ++++++++++++++++++++++++++++++++++ 在上次培训(2013年11月15日)中第一章第2小节有讲到应用层调用v4l2的例子,代码如下: .. _fig-appwhile.png: .. figure:: images/app_while.png :width: 12.0cm v4l2_play循环采集数据代码 经过我研究,代码是没有问题的。之前认为调用VIDIOC_QUERYBUF之后没有对buffer的状态查询就开始VIDIOC_DQBUF有不妥。 其实是我理解不够。其实,此时VIDIOC_DQBUF是一个阻塞函数,直到有数据后才会返回。 1. v4l2数据获取方法 ************************* v4l2等待数据有效的方法有两种: #. select,poll方法:通过调用Linux提供的select或者poll函数。此种方法在任何情况都可以使用。 #. VIDIOC_DQBUF:当打开v4l2设备文件时,如果没有指定O_NONBLOCK标志位,那么VIDIOC_DQBUF就是阻塞态的。v4l2_play程序就是使用的这种方法。 2. v4l2 buffer管理概述 ************************** 在v4l2内部维护着两个buffer的队列,一个给驱动用(简称Queue A),一个给用户使用(简称Queue B)。可以先初略的理解队列中存储着每个buffer的指针。 驱动需要使用buffer来填充数据时,从Queue A中取出一个buffer,填充之。填满后会存入Queue B中。而驱动每次调用VIDIOC_DQBUF,会去Queue B中查询,若有buffer则出队次buffer,否则等待直到有buffer。用户使用完buffer后,调用VIDIOC_QBUF将buffer入队到Queue A中。 基本的过程就是这样。后面章节会具体讲v4l2缓冲区管理的细节。敬请期待。 3. 附录 VIDIOC_DQBUF阻塞实现细节 **************************************** 用户调用VIDIOC_DQBUF时,v4l2会调用vb2_dqbuf函数,其实现如下: .. code-block:: c :emphasize-lines: 16 int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { struct vb2_buffer *vb = NULL; int ret; if (q->fileio) { dprintk(1, "dqbuf: file io in progress\n"); return -EBUSY; } if (b->type != q->type) { dprintk(1, "dqbuf: invalid buffer type\n"); return -EINVAL; } ret = __vb2_get_done_vb(q, &vb, nonblocking); if (ret < 0) { dprintk(1, "dqbuf: error getting next done buffer\n"); return ret; } ret = call_qop(q, buf_finish, vb); if (ret) { dprintk(1, "dqbuf: buffer finish failed\n"); return ret; } switch (vb->state) { case VB2_BUF_STATE_DONE: dprintk(3, "dqbuf: Returning done buffer\n"); break; case VB2_BUF_STATE_ERROR: dprintk(3, "dqbuf: Returning done buffer with errors\n"); break; default: dprintk(1, "dqbuf: Invalid buffer state\n"); return -EINVAL; } /* Fill buffer information for the userspace */ __fill_v4l2_buffer(vb, b); /* Remove from videobuf queue */ list_del(&vb->queued_entry); dprintk(1, "dqbuf of buffer %d, with state %d\n", vb->v4l2_buf.index, vb->state); vb->state = VB2_BUF_STATE_DEQUEUED; return 0; } 其中最重要的操作(高亮的那一行)调用__vb2_get_done_vb函数去获取一个已经填充好的buffer。 .. code-block:: c :emphasize-lines: 7 static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, int nonblocking) { unsigned long flags; int ret; /* * Wait for at least one buffer to become available on the done_list. */ ret = __vb2_wait_for_done_vb(q, nonblocking); if (ret) return ret; /* * Driver's lock has been held since we last verified that done_list * is not empty, so no need for another list_empty(done_list) check. */ spin_lock_irqsave(&q->done_lock, flags); *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); list_del(&(*vb)->done_entry); spin_unlock_irqrestore(&q->done_lock, flags); return 0; } 从上面的代码中可以看出__vb2_get_done_vb函数调用__vb2_wait_for_done_vb获取一个buffer,并将其从队列中删除。__vb2_wait_for_done_vb代码如下: .. code-block:: c :emphasize-lines: 19,36,37 static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) { for (;;) { int ret; if (!q->streaming) { dprintk(1, "Streaming off, will not wait for buffers\n"); return -EINVAL; } if (!list_empty(&q->done_list)) { /* * Found a buffer that we were waiting for. */ break; } if (nonblocking) { dprintk(1, "Nonblocking and no buffers to dequeue, " "will not wait\n"); return -EAGAIN; } /* * We are streaming and blocking, wait for another buffer to * become ready or for streamoff. Driver's lock is released to * allow streamoff or qbuf to be called while waiting. */ call_qop(q, wait_prepare, q); /* * All locks have been released, it is safe to sleep now. */ dprintk(3, "Will sleep waiting for buffers\n"); ret = wait_event_interruptible(q->done_wq, !list_empty(&q->done_list) || !q->streaming); /* * We need to reevaluate both conditions again after reacquiring * the locks or return an error if one occurred. */ call_qop(q, wait_finish, q); if (ret) return ret; } return 0; } 从上面的代码,调用wait_event_interruptible函数实现等待的。退出等待的条件是q->done_list不为空。 如此这样,VIDIOC_DQBUF就实现了阻塞操作。 从__vb2_wait_for_done_vb函数的实现中,亦可发现,当nonblocking变量为True时,__vb2_wait_for_done_vb不会等待队列非空,直接返回-EAGAIN.