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;
}