v4l2应用框架-摄像头v4l2编程(15)_出队VIDIOC_DQBUF

v4l2应用框架-摄像头v4l2编程(15)_出队VIDIOC_DQBUF

参考链接:

(127条消息) 从应用调用vivi驱动分析v4l2 – 出队列(VIDIOC_DQBUF)_dianlong_lee的博客-CSDN博客_vidioc_dqbuf
(127条消息) V4L2框架-缓存出队列(VIDIOC_DQBUF)_深海带鲤鱼的博客-CSDN博客_vidioc_dqbuf

VIDIOC_QBUF

这里单独说一下:VIDIOC_QBUF和VIDIOC_DQBUF
VIDIOC_QBUF /* 将空闲的内存加入可捕获视频的队列
VIDIOC_DQBUF /* 将已经捕获好视频的内存拉出已捕获视频的队列 *
在uvc_gadget.c里执行:

if ((ret = ioctl(dev->fd, VIDIOC_QBUF, &buf)) < 0) {
    printf("Unable to requeue buffer: %s (%d).\n", strerror(errno),
        errno);
    return ret;
}

会调用到内核态。

使用VIDIOC_DQBUF命令调用ioctl,最终会调用到vb2_dqbuf函数,内核使用vb2_dqbuf函数将填充满数据的缓存从驱动中返回给应用。

    [include/media/videobuf2-core.h]
    // q-缓冲区队列数据结构struct vb2_queue指针
    // b-v4l2_buffer结构体指针
    // nonblocking-阻塞标志,根据file->f_flags & O_NONBLOCK决定
    // 返回值-0成功,小于0失败
    int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking);

vb2_dqbuf主要的工作如下:
(1)检查缓冲区是否可用。图像采集模式下,需要等待图像数据填充到缓冲区中才能被使用。非阻塞且无缓冲区可用,则直接返回-EAGAIN错误。阻塞且无缓冲区可用,睡眠等待。
(2)缓冲区可用,则获取一个可用的缓冲区并将其从done_list链表中删除。
(3)将可用的缓冲区信息拷贝到用户空间。
(4)将可用的缓冲区从queued_list链表中删除。
(5)设置缓冲区状态为VB2_BUF_STATE_DEQUEUED。

    vb2_dqbuf
        ->vb2_internal_dqbuf
            // 等待缓冲器是否可用,缓冲区可用,返回0
            // 非阻塞且无缓冲区可用,则直接返回-EAGAIN错误
            // 阻塞且无缓冲区可用,睡眠等待
            ->__vb2_wait_for_done_vb
                // 睡眠等待缓冲区
                call_void_qop(q, wait_prepare, q)
                ->vb2_ops_wait_prepare  // wait_prepare最终调用vb2_ops_wait_prepare函数
                ->vb2_ops_wait_prepare
                    ->mutex_unlock(vq->lock);  // 释放锁
                ->wait_event_interruptible     // 睡眠等待缓冲区可用
                // 缓冲区可用被唤醒,执行wait_finish函数,最终调用vb2_ops_wait_finish函数
                call_void_qop(q, wait_finish, q)
                ->vb2_ops_wait_finish
                    ->mutex_lock(vq->lock);            // 获取锁
            ->spin_lock_irqsave(&q->done_lock, flags)  // 获取自旋锁
            // 获取done_list链表中一个可用的缓冲区
            list_first_entry(&q->done_list, struct vb2_buffer, done_entry)
            ->__verify_planes_array  // 确保取出的缓冲区所有planes可用
            list_del(&(*vb)->done_entry)  // 缓冲区所有planes可用,则将缓冲区从done_list链表移除
            spin_unlock_irqrestore(&q->done_lock, flags)  // 释放自旋锁
        // 调用buf_finish函数,imx6ull平台没有实现
        call_void_vb_qop(vb, buf_finish, vb)
        ->__fill_v4l2_buffer(vb, b)  // 将出队缓冲区信息填充到用户空间
        list_del(&vb->queued_entry); // 将缓冲区从queued_entry链表中删除
        q->queued_count--;  // 入队缓冲区减1
        ->__vb2_dqbuf
            vb->state = VB2_BUF_STATE_DEQUEUED;  // 设置缓冲器为VB2_BUF_STATE_DEQUEUED状态

内核态的执行流程

uvc_gadget.c的VIDIOC_DQBUF和VIDIOC_QBUF对应到内核的驱动接口是:uvc_v4l2_dqbuf和uvc_v4l2_qbuf,
结构体定义在: kernel/msm-4.14/include/media/v4l2-ioctl.h

const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
    .vidioc_querycap = uvc_v4l2_querycap,
    .vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
    .vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
    .vidioc_reqbufs = uvc_v4l2_reqbufs,
    .vidioc_querybuf = uvc_v4l2_querybuf,
    .vidioc_qbuf = uvc_v4l2_qbuf,
    .vidioc_dqbuf = uvc_v4l2_dqbuf,
    .vidioc_streamon = uvc_v4l2_streamon,
    .vidioc_streamoff = uvc_v4l2_streamoff,
    .vidioc_subscribe_event = uvc_v4l2_subscribe_event,
    .vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event,
    .vidioc_default = uvc_v4l2_ioctl_default,
};

VIDIOC_DQBUF程序分析

uvc_v4l2_dqbuf

static int
uvc_v4l2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
{
>---struct video_device *vdev = video_devdata(file);
>---struct uvc_device *uvc = video_get_drvdata(vdev);
>---struct uvc_video *video = &uvc->video;

>---return uvcg_dequeue_buffer(&video->queue, b, file->f_flags & O_NONBLOCK);
}

uvcg_dequeue_buffer

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer

/*
 * Dequeue a video buffer. If nonblocking is false, block until a buffer is
 * available.
 */
int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
    >--->---int nonblocking)
{
    return vb2_dqbuf(&queue->queue, buf, nonblocking);
}

vb2_dqbuf

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer
---->vb2_dqbuf

/**
 * vb2_dqbuf() - Dequeue a buffer to the userspace
 * @q:>->---videobuf2 queue
 * @b:>->---buffer structure passed from userspace to VIDIOC_DQBUF() handler
 *>->---in driver
 * @nonblocking: if true, this call will not sleep waiting for a buffer if no
 *>->--- buffers ready for dequeuing are present. Normally the driver
 *>->--- would be passing (file->f_flags & O_NONBLOCK) here
 *
 * Should be called from VIDIOC_DQBUF() ioctl handler of a driver.
 *
 * This function:
 *
 * #) verifies the passed buffer,
 * #) calls buf_finish callback in the driver (if provided), in which
 *    driver can perform any additional operations that may be required before
 *    returning the buffer to userspace, such as cache sync,
 * #) the buffer struct members are filled with relevant information for
 *    the userspace.
 *
 * The return values from this function are intended to be directly returned
 * from VIDIOC_DQBUF() handler in driver.
 */
 int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    int ret;

    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n");
        return -EBUSY;
    }

    if (b->type != q->type) {
        dprintk(1, "invalid buffer type\n");
        return -EINVAL;
    }

    ret = vb2_core_dqbuf(q, NULL, b, nonblocking);

    /*
     *  After calling the VIDIOC_DQBUF V4L2_BUF_FLAG_DONE must be
     *  cleared.
     */
    b->flags &= ~V4L2_BUF_FLAG_DONE;

    return ret;
}

vb2_core_dqbuf

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer
---->vb2_dqbuf
------>vb2_core_dqbuf

int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
           bool nonblocking)
{
    struct vb2_buffer *vb = NULL;
    int ret;
    
    //ret = 0的时候,得到有效的buf的地址 vb 看5.4.1
    // 等待缓冲器是否可用,缓冲区可用,返回0
    // 非阻塞且无缓冲区可用,则直接返回-EAGAIN错误
    // 阻塞且无缓冲区可用,睡眠等待
    ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
    if (ret < 0)
        return ret;

    switch (vb->state) {
    //正常来说,STATE_DONE
    case VB2_BUF_STATE_DONE:
        dprintk(3, "returning done buffer\n");
        break;
    case VB2_BUF_STATE_ERROR:
        dprintk(3, "returning done buffer with errors\n");
        break;
    default:
        dprintk(1, "invalid buffer state\n");
        return -EINVAL;
    }

    /*
     * vb可以访问到缓冲区,驱动可以最后对缓冲区的数据进行修改
     * 不修改的话,后面将释放放给用户空间
     */
    call_void_vb_qop(vb, buf_finish, vb);

    if (pindex)
        *pindex = vb->index;

    /* Fill buffer information for the userspace */
    if (pb)
        /*
         * __vb2_get_done_vb中分析过
         * 如果这个last_buffer_dequeued置位了,会不会忽略最后一帧
         * last_buffer_dequeued的条件是
         * 1.V4L2_BUF_FLAG_DONE,vb2_buffer_done中会置位
         * 2.V4L2_BUF_FLAG_DONE,驱动中可以置位
         * 以上2个条件成立才会将last_buffer_dequeued置1
         * 关键是last_buffer_dequeued在哪里置1呢?
         * 就是下面这个fill_user_buffer
         * 说明出来完标记了V4L2_BUF_FLAG_DONE和V4L2_BUF_FLAG_LAST的
         * 这最后一帧后,才将last_buffer_dequeued置1,所以最后一帧不会丢
         */
        call_void_bufop(q, fill_user_buffer, vb, pb);

    /* Remove from videobuf queue */
    list_del(&vb->queued_entry);
    q->queued_count--;

    trace_vb2_dqbuf(q, vb);

    /* go back to dequeued state */
     
   /*
    * 对于MMAP
    * 主要就是 vb->state = VB2_BUF_STATE_DEQUEUED;
    * 更改状态
    */
    __vb2_dqbuf(vb);

    dprintk(2, "dqbuf of buffer %d, with state %d\n",
            vb->index, vb->state);

    return 0;

}
__vb2_get_done_vb

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer
---->vb2_dqbuf
------>vb2_core_dqbuf
-------->__vb2_get_done_vb

 /**
  * __vb2_get_done_vb() - get a buffer ready for dequeuing
  *
  * Will sleep if required for nonblocking == false.
  */
 static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,
                  void *pb, int nonblocking)
 {
     unsigned long flags;
     int ret = 0;

     /*
      * Wait for at least one buffer to become available on the done_list.
      */
     //ret = 0 获取到了有效的buffer
     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);
     //得到一个有效的buffer,*vb是地址
     *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
     /*
      * Only remove the buffer from done_list if all planes can be
      * handled. Some cases such as V4L2 file I/O and DVB have pb
      * == NULL; skip the check then as there's nothing to verify.
      */
     if (pb)
         ret = call_bufop(q, verify_planes_array, *vb, pb);
         //verify_planes_array 这里对应的是多平台
     if (!ret)
        //将buffer从done_list上移除         
        list_del(&(*vb)->done_entry);
         
     spin_unlock_irqrestore(&q->done_lock, flags);

     return ret;
 }
__vb2_wait_for_done_vb

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer
---->vb2_dqbuf
------>vb2_core_dqbuf
-------->__vb2_get_done_vb
---------->__vb2_wait_for_done_vb

/**
 * __vb2_wait_for_done_vb() - wait for a buffer to become available
 * for dequeuing
 *
 * Will sleep if required for nonblocking == false.
 */
static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking)
{
    /*
     * All operations on vb_done_list are performed under done_lock
     * spinlock protection. However, buffers may be removed from
     * it and returned to userspace only while holding both driver's
     * lock and the done_lock spinlock. Thus we can be sure that as
     * long as we hold the driver's lock, the list will remain not
     * empty if list_empty() check succeeds.
     */

    for (;;) {
        int ret;
 
        /*
         * 调用过STREAM_ON 这里streaming值为1
         * 如何为0,说明执行了stream_off操作,就不会有数据继续产生
         * 所以这里也不用继续执行了
         */
        if (!q->streaming) {
            dprintk(1, "streaming off, will not wait for buffers\n");
            return -EINVAL;
        }

        if (q->error) {
            dprintk(1, "Queue in error state, will not wait for buffers\n");
            return -EIO;
        }
       /*
        * 如果这个last_buffer_dequeued置位了
        * 那么说明后面没有有效的帧数据了,所以直接返回就行
        * 问题来了,这里会不会忽略最后一帧?
        * 答案是不会,原因后面分析
        */
        if (q->last_buffer_dequeued) {
            dprintk(3, "last buffer dequeued already, will not wait for buffers\n");
            return -EPIPE;
        }
       /*
        * done_list上有buffer则跳出这个循环,继续往下走
        * 对于使用了select的方式,这里应该就返回了
        */
        if (!list_empty(&q->done_list)) {
            /*
             * Found a buffer that we were waiting for.
             */
            break;
        }
       /*
        * 如果应用传递的是不堵塞
        * 那么这里直接返回
        */
        if (nonblocking) {
            dprintk(3, "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_void_qop(q, wait_prepare, q);

        /*
         * All locks have been released, it is safe to sleep now.
         */
        dprintk(3, "will sleep waiting for buffers\n");
       /*
        * wait_event_interruptible(wq, condition)
        * 对于condition来说
        * condition = 0 休眠
        * condition = 1 唤醒
        * 前提是wake_up_interruptible唤醒后,进一步才是condition
        * 对于返回值 
        * 1.condition = 1时调用这个函数将直接返回0
        * 2.正常wakeup且condition=1 返回0
        * 3.其他信号唤醒,返回负值
        */
        ret = wait_event_interruptible(q->done_wq,
                !list_empty(&q->done_list) || !q->streaming ||
                q->error);

        /*
         * We need to reevaluate both conditions again after reacquiring
         * the locks or return an error if one occurred.
         */
       /*
        * wait_finish 对应vivi驱动的vivi_lock
        * 主要就是代码 mutex_lock(&dev->mutex);
        * 获取自旋锁
        *
        * 如果这样的话,就算stream_off,自旋锁还是没有释放 ???
        */   

        call_void_qop(q, wait_finish, q);
        if (ret) {
            dprintk(1, "sleep was interrupted\n");
            return ret;
        }
    }
    return 0;
}

__vb2_dqbuf

uvc_v4l2_dqbuf
–>uvcg_dequeue_buffer
---->vb2_dqbuf
------>vb2_core_dqbuf
-------->__vb2_dqbuf

/**
 * __vb2_dqbuf() - bring back the buffer to the DEQUEUED state
 */
static void __vb2_dqbuf(struct vb2_buffer *vb)
{
    struct vb2_queue *q = vb->vb2_queue;
    unsigned int i;

    /* nothing to do if the buffer is already dequeued */
    if (vb->state == VB2_BUF_STATE_DEQUEUED)
        return;

    vb->state = VB2_BUF_STATE_DEQUEUED;

    /* unmap DMABUF buffer */
    if (q->memory == VB2_MEMORY_DMABUF)
        for (i = 0; i < vb->num_planes; ++i) {
            if (!vb->planes[i].dbuf_mapped)
                continue;
            call_void_memop(vb, unmap_dmabuf, vb->planes[i].mem_priv);
            vb->planes[i].dbuf_mapped = 0;
        }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值