Linux&UVC驱动
+ -

LINUX&UVC视频缓冲区队列映射VIDIOC_QUERYBUF

2024-04-02 75 0

LINUX&UVC驱动中通过VIDIOC_REQBUFS申请的内存空间在内核层,而我们的应用程序在应用层,故为了从内核中将数据读到应用层,提高效率。LINUX&UVC驱动使用的是内存映射的方式。其原理就是同一片内存空间,分别映射到用户层和内核层。这样在内核中写内存,在应用层通过读应用层的地址即可读到数据。这样,虽然在应用层和内核层的地址不同,但是是同一片内存。

通过VIDIOC_REQBUFS申请的内核内层,可通过VIDIOC_QUERYBUF请求来进行映射。

    // 将缓冲区映射到用户空间
    struct v4l2_buffer buf;
    void *buffer_start[reqbuf.count];
    for (unsigned int i = 0; i < reqbuf.count; i++) {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
            perror("Failed to query buffer");
            close(fd);
            return EXIT_FAILURE;
        }
        buffer_start[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer_start[i] == MAP_FAILED) {
            perror("Failed to map buffer");
            close(fd);
            return EXIT_FAILURE;
        }
    }

VIDIOC_QUERYBUF对应的内核请求为vidioc_querybuf回调。

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
...
    .vidioc_querybuf = uvc_ioctl_querybuf,
...
}

其对应的函数为:

static int uvc_ioctl_querybuf(struct file *file, void *fh,
                  struct v4l2_buffer *buf)
{
    struct uvc_fh *handle = fh;
    struct uvc_streaming *stream = handle->stream;

    if (!uvc_has_privileges(handle))
        return -EBUSY;

    return uvc_query_buffer(&stream->queue, buf);
}

其最终还是由vb2模块实现(谁申请谁遇射,也合理):

int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
{
    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_querybuf(&queue->queue, buf);
    mutex_unlock(&queue->mutex);

    return ret;
}

调用vb2_querybufvb2_querybuf

int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    struct vb2_buffer *vb;
    int ret;

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

    if (b->index >= q->num_buffers) {
        dprintk(1, "buffer index out of range\n");
        return -EINVAL;
    }
    vb = q->bufs[b->index];
    ret = __verify_planes_array(vb, b);
    if (!ret)
        vb2_core_querybuf(q, b->index, b);
    return ret;
}
EXPORT_SYMBOL(vb2_querybuf);

vb2_core_querybuf调用队列初始化时vb2_queue_init注册的回调fill_user_buffer

void vb2_core_querybuf(struct vb2_queue *q, unsigned int index, void *pb)
{
    call_void_bufop(q, fill_user_buffer, q->bufs[index], pb);
}
EXPORT_SYMBOL_GPL(vb2_core_querybuf);

vb2_queue_init初始化时

int vb2_queue_init(struct vb2_queue *q)
{
...
q->buf_ops = &v4l2_buf_ops;
...
}

v4l2_buf_ops定义如下:

static const struct vb2_buf_ops v4l2_buf_ops = {
    .verify_planes_array    = __verify_planes_array_core,
    .init_buffer        = __init_vb2_v4l2_buffer,
    .fill_user_buffer    = __fill_v4l2_buffer,
    .fill_vb2_buffer    = __fill_vb2_buffer,
    .copy_timestamp        = __copy_timestamp,
};

__init_vb2_v4l2_buffer的函数如下:

/**
 * __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be
 * returned to userspace
 */
static void __fill_v4l2_buffer(struct vb2_buffer *vb, void *pb)
{
        struct v4l2_buffer *b = pb;
        /*
         * 对于vb2_v4l2_buffer好像是第一次见到,这里分析一下来源
         * 先说一下这个函数的2个参数
         * vb是来源于bufs[index],申请buffer的时候填充的
         * pb则是来自于用户空间的地址
         * vbuf = to_vb2_v4l2_buffer(vb)
         * 说明和vb有关系
         * __vb2_queue_alloc中vb创建如下
         * vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
         * 而q->buf_struct_size值在
         * vb2_queue_init
         * if (q->buf_struct_size == 0)
         *     q->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
         * 也就是不为0的时候,这里会赋值
         * 但是vivi驱动中已经对其赋值了
         * q->buf_struct_size = sizeof(struct vivi_buffer);
         * 这样就找到了,对应的vb2_v4l2_buffer 
         * 实际看vivi_buffer,
         * struct vivi_buffer {
     *    struct vb2_buffer    vb;
     *    struct list_head    list;
     *    struct vivi_fmt        *fmt;
         * }
         * 没看到vb2_v4l2_buffer相关的东西
         * 于是去看最新的vivi驱动代码,如下
         * struct vivid_buffer {
     *     struct vb2_v4l2_buffer vb;
     *     struct list_head    list;
         * };
         * 这里就可以看到vb2_v4l2_buffer的来源了
         * 其实主要原因还是vivi驱动太老,linux内核太新不匹配造成的
         * */
        struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
        struct vb2_queue *q = vb->vb2_queue;
        unsigned int plane;

        // 直接填充数据成员信息
        /* Copy back data such as timestamp, flags, etc. */
        b->index = vb->index;
        b->type = vb->type;
        b->memory = vb->memory;
        b->bytesused = 0;

        b->flags = vbuf->flags;
        b->field = vbuf->field;
        b->timestamp = ns_to_timeval(vb->timestamp);
        b->timecode = vbuf->timecode;
        b->sequence = vbuf->sequence;
        b->reserved2 = 0;
        b->reserved = 0;

        // 多平面视频格式暂不做解释,还没细研究!!!
        if (q->is_multiplanar) {
                /*
                 * Fill in plane-related data if userspace provided an array
                 * for it. The caller has already verified memory and size.
                 */
                b->length = vb->num_planes;
                for (plane = 0; plane < vb->num_planes; ++plane) {
                        struct v4l2_plane *pdst = &b->m.planes[plane];
                        struct vb2_plane *psrc = &vb->planes[plane];

                        pdst->bytesused = psrc->bytesused;
                        pdst->length = psrc->length;
                        if (q->memory == VB2_MEMORY_MMAP)
                                pdst->m.mem_offset = psrc->m.offset;
                        else if (q->memory == VB2_MEMORY_USERPTR)
                                pdst->m.userptr = psrc->m.userptr;
                        else if (q->memory == VB2_MEMORY_DMABUF)
                                pdst->m.fd = psrc->m.fd;
                        pdst->data_offset = psrc->data_offset;
                        memset(pdst->reserved, 0, sizeof(pdst->reserved));
                }
        } else {
        /*
                 * length: 平面的大小,可能与真实的数据帧大小不一致,驱动为了page对齐会变大
                 * byteused: 帧数据的大小,这里为0,后面的分析中可以看到更新
                 * 应用代码中有时候可能会用length代表帧数据的大小,这个值有可能是不准确的
                 * 比如帧大小是4812Byte,那么驱动中有可能为了方便,将length设置为5000
                 * 所以最好使用byteused
                */             
                 /*
                 * We use length and offset in v4l2_planes array even for
                 * single-planar buffers, but userspace does not.
                 */
                b->length = vb->planes[0].length;
                b->bytesused = vb->planes[0].bytesused;
                if (q->memory == VB2_MEMORY_MMAP)
                        b->m.offset = vb->planes[0].m.offset;
                else if (q->memory == VB2_MEMORY_USERPTR)
                        b->m.userptr = vb->planes[0].m.userptr;
                else if (q->memory == VB2_MEMORY_DMABUF)
                        b->m.fd = vb->planes[0].m.fd;
        }

        /*
         * Clear any buffer state related flags.
         */
        b->flags &= ~V4L2_BUFFER_MASK_FLAGS;
        b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
        if (!q->copy_timestamp) {
                /*
                 * For non-COPY timestamps, drop timestamp source bits
                 * and obtain the timestamp source from the queue.
                 */
                b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
                b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
        }
        /*
         * 对于vb->state的值
         * 在使用reqbuf的时候VIDIOC_REQBUFS,看上一篇
         * vb->state = VB2_BUF_STATE_DEQUEUED
         * 表示该buffer在用户空间控制,直接break
        */
      switch (vb->state) {
        case VB2_BUF_STATE_QUEUED:
        case VB2_BUF_STATE_ACTIVE:
                b->flags |= V4L2_BUF_FLAG_QUEUED;
                break;
        case VB2_BUF_STATE_ERROR:
                b->flags |= V4L2_BUF_FLAG_ERROR;
                /* fall through */
        case VB2_BUF_STATE_DONE:
                b->flags |= V4L2_BUF_FLAG_DONE;
                break;
        case VB2_BUF_STATE_PREPARED:
                b->flags |= V4L2_BUF_FLAG_PREPARED;
                break;
        case VB2_BUF_STATE_PREPARING:
        case VB2_BUF_STATE_DEQUEUED:
        case VB2_BUF_STATE_REQUEUEING:
                /* nothing */
                break;
        }

        if (vb2_buffer_in_use(q, vb))
                b->flags |= V4L2_BUF_FLAG_MAPPED;

        if (!q->is_output &&
                b->flags & V4L2_BUF_FLAG_DONE &&
                b->flags & V4L2_BUF_FLAG_LAST)
                q->last_buffer_dequeued = true;
}

0 篇笔记 写笔记

LINUX&UVC视频缓冲区队列映射VIDIOC_QUERYBUF
LINUX&UVC驱动中通过VIDIOC_REQBUFS申请的内存空间在内核层,而我们的应用程序在应用层,故为了从内核中将数据读到应用层,提高效率。LINUX&UVC驱动使用的是内存映射的方式。其原理就是同一片内存空间,分别映射到用户层和内核层。这样在内核中写内存,在应用层通过读应用......
关注公众号
  • HID人机交互
  • Linux&USB
  • UAC音频
  • TYPE-C
  • USB规范
  • USB大容量存储
  • USB百科
  • USB周边
  • UVC摄像头
  • Windows系统USB
  • 音视频博客
  • 取消
    感谢您的支持,我会继续努力的!
    扫码支持
    扫码打赏,你说多少就多少

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

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