Linux&UVC驱动
+ -

LINUX&UVC驱动申请视频帧缓冲区VIDIOC_REQBUFS

2024-04-02 3 0

在启动UVC相机前,需要先准备好数据的存储缓冲区,使用VIDIOC_REQBUFS控制吗。

    // 请求内存缓冲区
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    reqbuf.count = 4;//申请拥4个缓冲帧的缓冲区
    if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
...
    }

该控制码在LINUX&UVC中对应的操作函数是vidioc_reqbufs

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
...
    .vidioc_reqbufs = uvc_ioctl_reqbufs,
...
}

uvc_ioctl_reqbufs调用队列的申请缓冲区函数uvc_request_buffers,然后进入uvc_request_buffers

static int uvc_ioctl_reqbufs(struct file *file, void *fh,struct v4l2_requestbuffers *rb)
{
    struct uvc_fh *handle = fh;
    struct uvc_streaming *stream = handle->stream;
    int ret;

    ret = uvc_acquire_privileges(handle);
    if (ret < 0)
        return ret;

    mutex_lock(&stream->mutex);
    ret = uvc_request_buffers(&stream->queue, rb);
    mutex_unlock(&stream->mutex);
    if (ret < 0)
        return ret;

    if (ret == 0)
        uvc_dismiss_privileges(handle);

    return 0;
}

加锁操作:

int uvc_request_buffers(struct uvc_video_queue *queue,
            struct v4l2_requestbuffers *rb)
{
    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_reqbufs(&queue->queue, rb);
    mutex_unlock(&queue->mutex);

    return ret ? ret : rb->count;
}

进入vb2模块的vb2_reqbufs函数,其中依旧第一个参数是队列指针,第二个是上层应用的请求数据参数。

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    int ret = vb2_verify_memory_type(q, req->memory, req->type);

    fill_buf_caps(q, &req->capabilities);
    return ret ? ret : vb2_core_reqbufs(q, req->memory, &req->count);
}

vb2_reqbufs中是vb2的一个导出函数,做一些必要的检查,然后进入vb2_core_reqbufs

int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, unsigned int *count)
{
        unsigned int num_buffers, allocated_buffers, num_planes = 0;
        // #define VB2_MAX_PLANES  (8)
        unsigned plane_sizes[VB2_MAX_PLANES] = { };
        int ret;

        // 正在取流中
        if (q->streaming) {
                dprintk(1, "streaming active\n");
                return -EBUSY;
        }

        /*
        * 注意
        * 1。 这里进行count=0判断,memory类型判断,删除所有buffer,所以为什么应用层会进行0 buffer的申请操作。
        */
        if (*count == 0 || q->num_buffers != 0 || q->memory != memory) {
                /*
                 * We already have buffers allocated, so first check if they
                 * are not in use and can be freed.
                 */
                mutex_lock(&q->mmap_lock);
                if (q->memory == VB2_MEMORY_MMAP && __buffers_in_use(q)) {
                        mutex_unlock(&q->mmap_lock);
                        dprintk(1, "memory in use, cannot free\n");
                        return -EBUSY;
                }

                /*
                 * Call queue_cancel to clean up any buffers in the PREPARED or
                 * QUEUED state which is possible if buffers were prepared or
                 * queued without ever calling STREAMON.
                 */
                __vb2_queue_cancel(q);
                ret = __vb2_queue_free(q, q->num_buffers);
                mutex_unlock(&q->mmap_lock);
                if (ret)
                        return ret;

                /*
                 * In case of REQBUFS(0) return immediately without calling
                 * driver's queue_setup() callback and allocating resources.
                 */
                if (*count == 0)
                        return 0;
        }

        /*
         * Make sure the requested values and current defaults are sane.
         */
         // #define VB2_MAX_FRAME   (32)
        num_buffers = min_t(unsigned int, *count, VB2_MAX_FRAME);
        // min_buffers_needed = 2一般为2
        num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
        memset(q->alloc_devs, 0, sizeof(q->alloc_devs));
        q->memory = memory;

        // 从驱动层获取到可以支持多少buffers和planes per buffer
        /*
         * Ask the driver how many buffers and planes per buffer it requires.
         * Driver also sets the size and allocator context for each plane.
         */
        ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, plane_sizes, q->alloc_devs);
        if (ret)
                return ret;

        // 最后分配buffer
        /* Finally, allocate buffers and video memory */
        allocated_buffers =__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
        if (allocated_buffers == 0) {
                dprintk(1, "memory allocation failed\n");
                return -ENOMEM;
        }

        // 检查是否大于最小需求buffer
        /*
         * There is no point in continuing if we can't allocate the minimum
         * number of buffers needed by this vb2_queue.
         */
        if (allocated_buffers < q->min_buffers_needed)
                ret = -ENOMEM;

        /*
         * Check if driver can handle the allocated number of buffers.
         */
        if (!ret && allocated_buffers < num_buffers) {
                num_buffers = allocated_buffers;
                /*
                 * num_planes is set by the previous queue_setup(), but since it
                 * signals to queue_setup() whether it is called from create_bufs()
                 * vs reqbufs() we zero it here to signal that queue_setup() is
                 * called for the reqbufs() case.
                 */
                num_planes = 0;

                ret = call_qop(q, queue_setup, q, &num_buffers,
                               &num_planes, plane_sizes, q->alloc_devs);

                if (!ret && allocated_buffers < num_buffers)
                        ret = -ENOMEM;

                /*
                 * Either the driver has accepted a smaller number of buffers,
                 * or .queue_setup() returned an error
                 */
        }

        mutex_lock(&q->mmap_lock);
        q->num_buffers = allocated_buffers;

        if (ret < 0) {
                /*
                 * Note: __vb2_queue_free() will subtract 'allocated_buffers'
                 * from q->num_buffers.
                 */
                __vb2_queue_free(q, allocated_buffers);
                mutex_unlock(&q->mmap_lock);
                return ret;
        }
        mutex_unlock(&q->mmap_lock);

        /*
        * 注意
        *     1.这里对应用层传递过来的count进行了,已经分配buffer数量的更新操作。
        */
        /*
         * Return the number of successfully allocated buffers
         * to the userspace.
         */
        *count = allocated_buffers;
        q->waiting_for_buffers = !q->is_output;

        return 0;
}
EXPORT_SYMBOL_GPL(vb2_core_reqbufs);

vb2_core_reqbufs中先取一个合适的num_buffers,然后使用call_qop调用UVC驱动提供给队列的回调函数queue_setup,最使用vb2_queue_alloc分配内存。

/**
 * __vb2_queue_alloc() - allocate videobuf buffer structures and (for MMAP type)
 * video buffer memory for all buffers/planes on the queue and initializes the
 * queue
 *
 * Returns the number of buffers successfully allocated.
 */
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
                             unsigned int num_buffers, unsigned int num_planes,
                             const unsigned plane_sizes[VB2_MAX_PLANES])
{
        unsigned int buffer, plane;
        struct vb2_buffer *vb;
        int ret;

        /* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
        num_buffers = min_t(unsigned int, num_buffers,
                            VB2_MAX_FRAME - q->num_buffers);

        // 分配 vidoe buffer了
        for (buffer = 0; buffer < num_buffers; ++buffer) 
        {
                /* Allocate videobuf buffer structures */
                vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
                if (!vb) {
                        dprintk(1, "memory alloc for buffer struct failed\n");
                        break;
                }

                // 设置video buffer的数据成员
                vb->state = VB2_BUF_STATE_DEQUEUED;
                vb->vb2_queue = q;
                vb->num_planes = num_planes;
                vb->index = q->num_buffers + buffer;
                vb->type = q->type;
                vb->memory = memory;
                for (plane = 0; plane < num_planes; ++plane) {
                        vb->planes[plane].length = plane_sizes[plane];
                        vb->planes[plane].min_length = plane_sizes[plane];
                }
                /*
                * 注意
                *     1. 这里使用的idx索引
                */
                q->bufs[vb->index] = vb;

                /* Allocate video buffer memory for the MMAP type */
                if (memory == VB2_MEMORY_MMAP)
                {
                        ret = __vb2_buf_mem_alloc(vb);
                        if (ret) {
                                dprintk(1, "failed allocating memory for buffer %d\n",
                                        buffer);
                                q->bufs[vb->index] = NULL;
                                kfree(vb);
                                break;
                        }
                        __setup_offsets(vb);
                        /*
                         * Call the driver-provided buffer initialization
                         * callback, if given. An error in initialization
                         * results in queue setup failure.
                         */
                        ret = call_vb_qop(vb, buf_init, vb);
                        if (ret) {
                                dprintk(1, "buffer %d %p initialization failed\n",
                                        buffer, vb);
                                __vb2_buf_mem_free(vb);
                                q->bufs[vb->index] = NULL;
                                kfree(vb);
                                break;
                        }
                }
        }

        dprintk(1, "allocated %d buffers, %d plane(s) each\n",
                        buffer, num_planes);

        return buffer;
}

其实主要就是内存模块的对齐 page

// 
/**
 * __setup_offsets() - setup unique offsets ("cookies") for every plane in
 * the buffer.
 */
static void __setup_offsets(struct vb2_buffer *vb)
{
        struct vb2_queue *q = vb->vb2_queue;
        unsigned int plane;
        unsigned long off = 0;

        if (vb->index) 
        {
                struct vb2_buffer *prev = q->bufs[vb->index - 1];
                struct vb2_plane *p = &prev->planes[prev->num_planes - 1];

                off = PAGE_ALIGN(p->m.offset + p->length);
        }

        for (plane = 0; plane < vb->num_planes; ++plane) 
        {
                vb->planes[plane].m.offset = off;

                dprintk(3, "buffer %d, plane %d offset 0x%08lx\n",
                                vb->index, plane, off);

                off += vb->planes[plane].length;
                off = PAGE_ALIGN(off);
        }
}

最终的分配内存函数

/**
 * __vb2_buf_mem_alloc() - allocate video memory for the given buffer
 */
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
        struct vb2_queue *q = vb->vb2_queue;
        void *mem_priv;
        int plane;
        int ret = -ENOMEM;

        /*
         * Allocate memory for all planes in this buffer
         * NOTE: mmapped areas should be page aligned
         */
        for (plane = 0; plane < vb->num_planes; ++plane) 
        {
                // page对齐
                unsigned long size = PAGE_ALIGN(vb->planes[plane].length);

                // 内核mmap
                mem_priv = call_ptr_memop(vb, alloc,
                                q->alloc_devs[plane] ? : q->dev,
                                q->dma_attrs, size, q->dma_dir, q->gfp_flags);
                if (IS_ERR_OR_NULL(mem_priv)) {
                        if (mem_priv)
                                ret = PTR_ERR(mem_priv);
                        goto free;
                }

                /* Associate allocator private data with this plane */
                vb->planes[plane].mem_priv = mem_priv;
        }

        return 0;
free:
        /* Free already allocated memory if one of the allocations failed */
        for (; plane > 0; --plane) {
                call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
                vb->planes[plane - 1].mem_priv = NULL;
        }

        return ret;
}

0 篇笔记 写笔记

LINUX&UVC驱动申请视频帧缓冲区VIDIOC_REQBUFS
在启动UVC相机前,需要先准备好数据的存储缓冲区,使用VIDIOC_REQBUFS控制吗。 // 请求内存缓冲区 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; ......
关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!