v4l2: Add mmap fallback when USERPTR is not supported

When we don't support EXPBUF according to the probe, the host will
allocate memory for us. We will then try to use USERPTR to import the
memory into v4l2.

If this is not supported, try to fall back to MMAP support, mmap the
buffers and memcpy the MMAP buffer to the host allocated buffers.
This commit is contained in:
Wim Taymans 2024-11-07 15:46:28 +01:00
parent c4df4a0371
commit b2dd733520
2 changed files with 44 additions and 7 deletions

View file

@ -58,6 +58,7 @@ struct buffer {
struct spa_meta_videotransform *vt; struct spa_meta_videotransform *vt;
struct v4l2_buffer v4l2_buffer; struct v4l2_buffer v4l2_buffer;
void *ptr; void *ptr;
void *mmap_ptr;
}; };
#define MAX_CONTROLS 64 #define MAX_CONTROLS 64

View file

@ -120,7 +120,6 @@ static int spa_v4l2_buffer_recycle(struct impl *this, uint32_t buffer_id)
spa_log_error(this->log, "'%s' VIDIOC_QBUF: %m", this->props.device); spa_log_error(this->log, "'%s' VIDIOC_QBUF: %m", this->props.device);
return -err; return -err;
} }
return 0; return 0;
} }
@ -147,6 +146,8 @@ static int spa_v4l2_clear_buffers(struct impl *this)
if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) {
munmap(b->ptr, d[0].maxsize); munmap(b->ptr, d[0].maxsize);
} }
if (b->mmap_ptr)
munmap(b->mmap_ptr, b->v4l2_buffer.length);
if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) {
spa_log_debug(this->log, "close %d", (int) d[0].fd); spa_log_debug(this->log, "close %d", (int) d[0].fd);
close(d[0].fd); close(d[0].fd);
@ -1459,12 +1460,15 @@ static int mmap_read(struct impl *this)
d = b->outbuf->datas; d = b->outbuf->datas;
d[0].chunk->offset = 0; d[0].chunk->offset = 0;
d[0].chunk->size = buf.bytesused; d[0].chunk->size = SPA_MIN(buf.bytesused, d[0].maxsize);
d[0].chunk->stride = port->fmt.fmt.pix.bytesperline; d[0].chunk->stride = port->fmt.fmt.pix.bytesperline;
d[0].chunk->flags = 0; d[0].chunk->flags = 0;
if (buf.flags & V4L2_BUF_FLAG_ERROR) if (buf.flags & V4L2_BUF_FLAG_ERROR)
d[0].chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; d[0].chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED;
if (b->mmap_ptr && b->ptr)
memcpy(b->ptr, b->mmap_ptr, d[0].chunk->size);
spa_list_append(&port->queue, &b->link); spa_list_append(&port->queue, &b->link);
return 0; return 0;
} }
@ -1549,8 +1553,22 @@ static int spa_v4l2_use_buffers(struct impl *this, struct spa_buffer **buffers,
reqbuf.count = n_buffers; reqbuf.count = n_buffers;
if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device); if (port->memtype != V4L2_MEMORY_USERPTR) {
return -errno; spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device);
return -errno;
}
/* some drivers (v4l2loopback) don't support USERPTR
* and so we need to try again with MMAP and memcpy */
port->memtype = V4L2_MEMORY_MMAP;
spa_zero(reqbuf);
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = port->memtype;
reqbuf.count = n_buffers;
if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device);
return -errno;
}
} }
spa_log_debug(this->log, "got %d buffers", reqbuf.count); spa_log_debug(this->log, "got %d buffers", reqbuf.count);
if (reqbuf.count < n_buffers) { if (reqbuf.count < n_buffers) {
@ -1583,7 +1601,8 @@ static int spa_v4l2_use_buffers(struct impl *this, struct spa_buffer **buffers,
b->v4l2_buffer.memory = port->memtype; b->v4l2_buffer.memory = port->memtype;
b->v4l2_buffer.index = i; b->v4l2_buffer.index = i;
if (port->memtype == V4L2_MEMORY_USERPTR) { if (port->memtype == V4L2_MEMORY_USERPTR ||
port->memtype == V4L2_MEMORY_MMAP) {
if (d[0].data == NULL) { if (d[0].data == NULL) {
void *data; void *data;
@ -1601,8 +1620,24 @@ static int spa_v4l2_use_buffers(struct impl *this, struct spa_buffer **buffers,
else else
b->ptr = d[0].data; b->ptr = d[0].data;
b->v4l2_buffer.m.userptr = (unsigned long) b->ptr; if (port->memtype == V4L2_MEMORY_USERPTR) {
b->v4l2_buffer.length = d[0].maxsize; b->v4l2_buffer.m.userptr = (unsigned long) b->ptr;
b->v4l2_buffer.length = d[0].maxsize;
}
else {
if (xioctl(dev->fd, VIDIOC_QUERYBUF, &b->v4l2_buffer) < 0) {
spa_log_error(this->log, "'%s' VIDIOC_QUERYBUF: %m", this->props.device);
return -errno;
}
b->mmap_ptr = mmap(NULL,
b->v4l2_buffer.length,
PROT_READ, MAP_PRIVATE,
dev->fd, b->v4l2_buffer.m.offset);
if (b->mmap_ptr == MAP_FAILED) {
spa_log_error(this->log, "'%s' mmap: %m", this->props.device);
return -errno;
}
}
} }
else if (port->memtype == V4L2_MEMORY_DMABUF) { else if (port->memtype == V4L2_MEMORY_DMABUF) {
b->v4l2_buffer.m.fd = d[0].fd; b->v4l2_buffer.m.fd = d[0].fd;
@ -1814,6 +1849,7 @@ static int spa_v4l2_stream_on(struct impl *this)
spa_log_debug(this->log, "starting"); spa_log_debug(this->log, "starting");
port->first_buffer = true; port->first_buffer = true;
mmap_read(this);
type = V4L2_BUF_TYPE_VIDEO_CAPTURE; type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(dev->fd, VIDIOC_STREAMON, &type) < 0) { if (xioctl(dev->fd, VIDIOC_STREAMON, &type) < 0) {