11月18日

v4l2_play.c文件wihle循环释疑

在上次培训(2013年11月15日)中第一章第2小节有讲到应用层调用v4l2的例子,代码如下:

dailyNote/images/app_while.png

v4l2_play循环采集数据代码

经过我研究,代码是没有问题的。之前认为调用VIDIOC_QUERYBUF之后没有对buffer的状态查询就开始VIDIOC_DQBUF有不妥。 其实是我理解不够。其实,此时VIDIOC_DQBUF是一个阻塞函数,直到有数据后才会返回。

1. v4l2数据获取方法

v4l2等待数据有效的方法有两种:

  1. select,poll方法:通过调用Linux提供的select或者poll函数。此种方法在任何情况都可以使用。
  2. 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函数,其实现如下:

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。

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代码如下:

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.

Table Of Contents

Previous topic

每日五分钟

Next topic

11月19日

This Page