/* * Copyright (C) 2014 Collabora Ltd. * Author: Nicolas Dufresne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #ifndef _GNU_SOURCE # define _GNU_SOURCE /* O_CLOEXEC */ #endif #include "ext/videodev2.h" #include "gstv4l2allocator.h" #include "v4l2_calls.h" #include #include #include #include #include #include #define GST_V4L2_MEMORY_TYPE "V4l2Memory" #define gst_v4l2_allocator_parent_class parent_class G_DEFINE_TYPE (GstV4l2Allocator, gst_v4l2_allocator, GST_TYPE_ALLOCATOR); GST_DEBUG_CATEGORY_STATIC (v4l2allocator_debug); #define GST_CAT_DEFAULT v4l2allocator_debug #define UNSET_QUEUED(buffer) \ ((buffer).flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) #define SET_QUEUED(buffer) ((buffer).flags |= V4L2_BUF_FLAG_QUEUED) #define IS_QUEUED(buffer) \ ((buffer).flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) enum { GROUP_RELEASED, LAST_SIGNAL }; static guint gst_v4l2_allocator_signals[LAST_SIGNAL] = { 0 }; static void gst_v4l2_allocator_release (GstV4l2Allocator * allocator, GstV4l2Memory * mem); static const gchar * memory_type_to_str (guint32 memory) { switch (memory) { case V4L2_MEMORY_MMAP: return "mmap"; case V4L2_MEMORY_USERPTR: return "userptr"; case V4L2_MEMORY_DMABUF: return "dmabuf"; default: return "unknown"; } } /*************************************/ /* GstV4lMemory implementation */ /*************************************/ static gpointer _v4l2mem_map (GstV4l2Memory * mem, gsize maxsize, GstMapFlags flags) { gpointer data = NULL; switch (mem->group->buffer.memory) { case V4L2_MEMORY_MMAP: case V4L2_MEMORY_USERPTR: data = mem->data; break; case V4L2_MEMORY_DMABUF: /* v4l2 dmabuf memory are not shared with downstream */ g_assert_not_reached (); break; default: GST_WARNING ("Unknown memory type %i", mem->group->buffer.memory); break; } return data; } static gboolean _v4l2mem_unmap (GstV4l2Memory * mem) { gboolean ret = FALSE; switch (mem->group->buffer.memory) { case V4L2_MEMORY_MMAP: case V4L2_MEMORY_USERPTR: ret = TRUE; break; case V4L2_MEMORY_DMABUF: /* v4l2 dmabuf memory are not share with downstream */ g_assert_not_reached (); break; default: GST_WARNING ("Unknown memory type %i", mem->group->buffer.memory); break; } return ret; } static gboolean _v4l2mem_dispose (GstV4l2Memory * mem) { GstV4l2Allocator *allocator = (GstV4l2Allocator *) mem->mem.allocator; GstV4l2MemoryGroup *group = mem->group; gboolean ret; if (group->mem[mem->plane]) { /* We may have a dmabuf, replace it with returned original memory */ group->mem[mem->plane] = gst_memory_ref ((GstMemory *) mem); gst_v4l2_allocator_release (allocator, mem); ret = FALSE; } else { gst_object_ref (allocator); ret = TRUE; } return ret; } static inline GstV4l2Memory * _v4l2mem_new (GstMemoryFlags flags, GstAllocator * allocator, GstMemory * parent, gsize maxsize, gsize align, gsize offset, gsize size, gint plane, gpointer data, int dmafd, GstV4l2MemoryGroup * group) { GstV4l2Memory *mem; mem = g_slice_new0 (GstV4l2Memory); gst_memory_init (GST_MEMORY_CAST (mem), flags, allocator, parent, maxsize, align, offset, size); if (parent == NULL) mem->mem.mini_object.dispose = (GstMiniObjectDisposeFunction) _v4l2mem_dispose; mem->plane = plane; mem->data = data; mem->dmafd = dmafd; mem->group = group; return mem; } static GstV4l2Memory * _v4l2mem_share (GstV4l2Memory * mem, gssize offset, gsize size) { GstV4l2Memory *sub; GstMemory *parent; /* find the real parent */ if ((parent = mem->mem.parent) == NULL) parent = (GstMemory *) mem; if (size == -1) size = mem->mem.size - offset; /* the shared memory is always readonly */ sub = _v4l2mem_new (GST_MINI_OBJECT_FLAGS (parent) | GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->mem.allocator, parent, mem->mem.maxsize, mem->mem.align, offset, size, mem->plane, mem->data, -1, mem->group); return sub; } static gboolean _v4l2mem_is_span (GstV4l2Memory * mem1, GstV4l2Memory * mem2, gsize * offset) { if (offset) *offset = mem1->mem.offset - mem1->mem.parent->offset; /* and memory is contiguous */ return mem1->mem.offset + mem1->mem.size == mem2->mem.offset; } gboolean gst_is_v4l2_memory (GstMemory * mem) { return gst_memory_is_type (mem, GST_V4L2_MEMORY_TYPE); } GQuark gst_v4l2_memory_quark (void) { static GQuark quark = 0; if (quark == 0) quark = g_quark_from_string ("GstV4l2Memory"); return quark; } /*************************************/ /* GstV4l2MemoryGroup implementation */ /*************************************/ static void gst_v4l2_memory_group_free (GstV4l2MemoryGroup * group) { gint i; for (i = 0; i < group->n_mem; i++) { GstMemory *mem = group->mem[i]; group->mem[i] = NULL; if (mem) gst_memory_unref (mem); } g_slice_free (GstV4l2MemoryGroup, group); } static GstV4l2MemoryGroup * gst_v4l2_memory_group_new (GstV4l2Allocator * allocator, guint32 index) { gint video_fd = allocator->video_fd; guint32 memory = allocator->memory; struct v4l2_format *format = &allocator->format; GstV4l2MemoryGroup *group; gsize img_size, buf_size; group = g_slice_new0 (GstV4l2MemoryGroup); group->buffer.type = format->type; group->buffer.index = index; group->buffer.memory = memory; if (V4L2_TYPE_IS_MULTIPLANAR (format->type)) { group->n_mem = group->buffer.length = format->fmt.pix_mp.num_planes; group->buffer.m.planes = group->planes; } else { group->n_mem = 1; } if (v4l2_ioctl (video_fd, VIDIOC_QUERYBUF, &group->buffer) < 0) goto querybuf_failed; if (group->buffer.index != index) { GST_ERROR_OBJECT (allocator, "Buffer index returned by VIDIOC_QUERYBUF " "didn't match, this indicate the presence of a bug in your driver or " "libv4l2"); g_slice_free (GstV4l2MemoryGroup, group); return NULL; } /* Check that provided size matches the format we have negotiation. Failing * there usually means a driver of libv4l bug. */ if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { gint i; for (i = 0; i < group->n_mem; i++) { img_size = allocator->format.fmt.pix_mp.plane_fmt[i].sizeimage; buf_size = group->planes[i].length; if (buf_size < img_size) goto buffer_too_short; } } else { img_size = allocator->format.fmt.pix.sizeimage; buf_size = group->buffer.length; if (buf_size < img_size) goto buffer_too_short; } /* We save non planar buffer information into the multi-planar plane array * to avoid duplicating the code later */ if (!V4L2_TYPE_IS_MULTIPLANAR (format->type)) { group->planes[0].bytesused = group->buffer.bytesused; group->planes[0].length = group->buffer.length; g_assert (sizeof (group->planes[0].m) == sizeof (group->buffer.m)); memcpy (&group->planes[0].m, &group->buffer.m, sizeof (group->buffer.m)); } GST_LOG_OBJECT (allocator, "Got %s buffer", memory_type_to_str (memory)); GST_LOG_OBJECT (allocator, " index: %u", group->buffer.index); GST_LOG_OBJECT (allocator, " type: %d", group->buffer.type); GST_LOG_OBJECT (allocator, " flags: %08x", group->buffer.flags); GST_LOG_OBJECT (allocator, " field: %d", group->buffer.field); GST_LOG_OBJECT (allocator, " memory: %d", group->buffer.memory); GST_LOG_OBJECT (allocator, " planes: %d", group->n_mem); #ifndef GST_DISABLE_GST_DEBUG if (memory == V4L2_MEMORY_MMAP) { gint i; for (i = 0; i < group->n_mem; i++) { GST_LOG_OBJECT (allocator, " [%u] bytesused: %u, length: %u", i, group->planes[i].bytesused, group->planes[i].length); GST_LOG_OBJECT (allocator, " [%u] MMAP offset: %u", i, group->planes[i].m.mem_offset); } } #endif return group; querybuf_failed: { GST_ERROR ("error querying buffer %d: %s", index, g_strerror (errno)); goto failed; } buffer_too_short: { GST_ERROR ("buffer size %" G_GSIZE_FORMAT " is smaller then negotiated size %" G_GSIZE_FORMAT ", this is usually the result of a bug in the v4l2 driver or libv4l.", buf_size, img_size); goto failed; } failed: gst_v4l2_memory_group_free (group); return NULL; } /*************************************/ /* GstV4lAllocator implementation */ /*************************************/ static void gst_v4l2_allocator_release (GstV4l2Allocator * allocator, GstV4l2Memory * mem) { GstV4l2MemoryGroup *group = mem->group; GST_LOG_OBJECT (allocator, "plane %i of buffer %u released", mem->plane, group->buffer.index); switch (allocator->memory) { case V4L2_MEMORY_DMABUF: close (mem->dmafd); mem->dmafd = -1; break; case V4L2_MEMORY_USERPTR: mem->data = NULL; break; default: break; } /* When all memory are back, put the group back in the free queue */ if (g_atomic_int_dec_and_test (&group->mems_allocated)) { GST_LOG_OBJECT (allocator, "buffer %u released", group->buffer.index); gst_atomic_queue_push (allocator->free_queue, group); g_signal_emit (allocator, gst_v4l2_allocator_signals[GROUP_RELEASED], 0); } /* Keep last, allocator may be freed after this call */ g_object_unref (allocator); } static void gst_v4l2_allocator_free (GstAllocator * gallocator, GstMemory * gmem) { GstV4l2Allocator *allocator = (GstV4l2Allocator *) gallocator; GstV4l2Memory *mem = (GstV4l2Memory *) gmem; GstV4l2MemoryGroup *group = mem->group; /* Only free unparented memory */ if (mem->mem.parent == NULL) { GST_LOG_OBJECT (allocator, "freeing plane %i of buffer %u", mem->plane, group->buffer.index); if (allocator->memory == V4L2_MEMORY_MMAP) { if (mem->data) v4l2_munmap (mem->data, group->planes[mem->plane].length); } /* This apply for both mmap with expbuf, and dmabuf imported memory */ if (mem->dmafd >= 0) close (mem->dmafd); } g_slice_free (GstV4l2Memory, mem); } static void gst_v4l2_allocator_dispose (GObject * obj) { GstV4l2Allocator *allocator = (GstV4l2Allocator *) obj; gint i; GST_LOG_OBJECT (obj, "called"); for (i = 0; i < allocator->count; i++) { GstV4l2MemoryGroup *group = allocator->groups[i]; allocator->groups[i] = NULL; if (group) gst_v4l2_memory_group_free (group); } G_OBJECT_CLASS (parent_class)->dispose (obj); } static void gst_v4l2_allocator_finalize (GObject * obj) { GstV4l2Allocator *allocator = (GstV4l2Allocator *) obj; GST_LOG_OBJECT (obj, "called"); v4l2_close (allocator->video_fd); gst_atomic_queue_unref (allocator->free_queue); G_OBJECT_CLASS (parent_class)->finalize (obj); } static void gst_v4l2_allocator_class_init (GstV4l2AllocatorClass * klass) { GObjectClass *object_class; GstAllocatorClass *allocator_class; allocator_class = (GstAllocatorClass *) klass; object_class = (GObjectClass *) klass; allocator_class->alloc = NULL; allocator_class->free = gst_v4l2_allocator_free; object_class->dispose = gst_v4l2_allocator_dispose; object_class->finalize = gst_v4l2_allocator_finalize; gst_v4l2_allocator_signals[GROUP_RELEASED] = g_signal_new ("group-released", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); GST_DEBUG_CATEGORY_INIT (v4l2allocator_debug, "v4l2allocator", 0, "V4L2 Allocator"); } static void gst_v4l2_allocator_init (GstV4l2Allocator * allocator) { GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator); alloc->mem_type = GST_V4L2_MEMORY_TYPE; alloc->mem_map = (GstMemoryMapFunction) _v4l2mem_map; alloc->mem_unmap = (GstMemoryUnmapFunction) _v4l2mem_unmap; alloc->mem_share = (GstMemoryShareFunction) _v4l2mem_share; alloc->mem_is_span = (GstMemoryIsSpanFunction) _v4l2mem_is_span; /* Use the default, fallback copy function */ allocator->free_queue = gst_atomic_queue_new (VIDEO_MAX_FRAME); GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); } #define GST_V4L2_ALLOCATOR_PROBE(obj,type) \ gst_v4l2_allocator_probe ((obj), V4L2_MEMORY_ ## type, \ GST_V4L2_ALLOCATOR_FLAG_ ## type ## _REQBUFS, \ GST_V4L2_ALLOCATOR_FLAG_ ## type ## _CREATE_BUFS) static guint32 gst_v4l2_allocator_probe (GstV4l2Allocator * allocator, guint32 memory, guint32 breq_flag, guint32 bcreate_flag) { struct v4l2_requestbuffers breq = { 0 }; guint32 flags = 0; breq.type = allocator->type; breq.count = 0; breq.memory = memory; if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) == 0) { struct v4l2_create_buffers bcreate = { 0 }; flags |= breq_flag; bcreate.memory = memory; bcreate.format = allocator->format; if ((v4l2_ioctl (allocator->video_fd, VIDIOC_CREATE_BUFS, &bcreate) == 0)) flags |= bcreate_flag; } return flags; } static GstV4l2MemoryGroup * gst_v4l2_allocator_create_buf (GstV4l2Allocator * allocator) { struct v4l2_create_buffers bcreate = { 0 }; GstV4l2MemoryGroup *group = NULL; GST_OBJECT_LOCK (allocator); if (!g_atomic_int_get (&allocator->active)) goto done; bcreate.memory = allocator->memory; bcreate.format = allocator->format; bcreate.count = 1; if (!allocator->can_allocate) goto done; if (v4l2_ioctl (allocator->video_fd, VIDIOC_CREATE_BUFS, &bcreate) < 0) goto create_bufs_failed; if (allocator->groups[bcreate.index] != NULL) goto create_bufs_bug; group = gst_v4l2_memory_group_new (allocator, bcreate.index); if (group) { allocator->groups[bcreate.index] = group; allocator->count++; } done: GST_OBJECT_UNLOCK (allocator); return group; create_bufs_failed: { GST_WARNING_OBJECT (allocator, "error creating a new buffer: %s", g_strerror (errno)); goto done; } create_bufs_bug: { GST_ERROR_OBJECT (allocator, "created buffer has already used buffer " "index %i, this means there is an bug in your driver or libv4l2", bcreate.index); goto done; } } static GstV4l2MemoryGroup * gst_v4l2_allocator_alloc (GstV4l2Allocator * allocator) { GstV4l2MemoryGroup *group; if (!g_atomic_int_get (&allocator->active)) return NULL; group = gst_atomic_queue_pop (allocator->free_queue); if (group == NULL) { if (allocator->can_allocate) { group = gst_v4l2_allocator_create_buf (allocator); /* Don't hammer on CREATE_BUFS */ if (group == NULL) allocator->can_allocate = FALSE; } } return group; } static void gst_v4l2_allocator_reset_size (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { gsize size; gboolean imported = FALSE; switch (allocator->memory) { case V4L2_MEMORY_USERPTR: case V4L2_MEMORY_DMABUF: imported = TRUE; break; } if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { gint i; for (i = 0; i < group->n_mem; i++) { size = allocator->format.fmt.pix_mp.plane_fmt[i].sizeimage; if (imported) group->mem[i]->maxsize = size; gst_memory_resize (group->mem[i], 0, size); } } else { size = allocator->format.fmt.pix.sizeimage; if (imported) group->mem[0]->maxsize = size; gst_memory_resize (group->mem[0], 0, size); } } static void _cleanup_failed_alloc (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { if (group->mems_allocated > 0) { gint i; /* If one or more mmap worked, we need to unref the memory, otherwise * they will keep a ref on the allocator and leak it. This will put back * the group into the free_queue */ for (i = 0; i < group->n_mem; i++) gst_memory_unref (group->mem[i]); } else { /* Otherwise, group has to be on free queue for _stop() to work */ gst_atomic_queue_push (allocator->free_queue, group); } } GstV4l2Allocator * gst_v4l2_allocator_new (GstObject * parent, gint video_fd, struct v4l2_format *format) { GstV4l2Allocator *allocator; guint32 flags = 0; gchar *name, *parent_name; parent_name = gst_object_get_name (parent); name = g_strconcat (parent_name, ":allocator", NULL); g_free (parent_name); allocator = g_object_new (GST_TYPE_V4L2_ALLOCATOR, "name", name, NULL); g_free (name); /* Save everything */ allocator->video_fd = v4l2_dup (video_fd); allocator->type = format->type; allocator->format = *format; flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, MMAP); flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, USERPTR); flags |= GST_V4L2_ALLOCATOR_PROBE (allocator, DMABUF); if (flags == 0) { /* Drivers not ported from videobuf to videbuf2 don't allow freeing buffers * using REQBUFS(0). This is a workaround to still support these drivers, * which are known to have MMAP support. */ GST_WARNING_OBJECT (allocator, "Could not probe supported memory type, " "assuming MMAP is supported, this is expected for older drivers not " " yet ported to videobuf2 framework"); flags = GST_V4L2_ALLOCATOR_FLAG_MMAP_REQBUFS; } GST_OBJECT_FLAG_SET (allocator, flags); return allocator; } guint gst_v4l2_allocator_start (GstV4l2Allocator * allocator, guint32 count, guint32 memory) { struct v4l2_requestbuffers breq = { count, allocator->type, memory }; gboolean can_allocate; gint i; g_return_val_if_fail (count != 0, 0); GST_OBJECT_LOCK (allocator); if (g_atomic_int_get (&allocator->active)) goto already_active; if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) < 0) goto reqbufs_failed; if (breq.count < 1) goto out_of_memory; switch (memory) { case V4L2_MEMORY_MMAP: can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, MMAP); break; case V4L2_MEMORY_USERPTR: can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, USERPTR); break; case V4L2_MEMORY_DMABUF: can_allocate = GST_V4L2_ALLOCATOR_CAN_ALLOCATE (allocator, DMABUF); break; default: can_allocate = FALSE; break; } GST_DEBUG_OBJECT (allocator, "allocated %u %s buffers out of %u requested", breq.count, memory_type_to_str (memory), count); allocator->can_allocate = can_allocate; allocator->count = breq.count; allocator->memory = memory; /* Create memory groups */ for (i = 0; i < allocator->count; i++) { allocator->groups[i] = gst_v4l2_memory_group_new (allocator, i); if (allocator->groups[i] == NULL) goto error; gst_atomic_queue_push (allocator->free_queue, allocator->groups[i]); } g_atomic_int_set (&allocator->active, TRUE); done: GST_OBJECT_UNLOCK (allocator); return breq.count; already_active: { GST_ERROR_OBJECT (allocator, "allocator already active"); goto error; } reqbufs_failed: { GST_ERROR_OBJECT (allocator, "error requesting %d buffers: %s", count, g_strerror (errno)); goto error; } out_of_memory: { GST_ERROR_OBJECT (allocator, "Not enough memory to allocate buffers"); goto error; } error: { breq.count = 0; goto done; } } GstV4l2Return gst_v4l2_allocator_stop (GstV4l2Allocator * allocator) { struct v4l2_requestbuffers breq = { 0, allocator->type, allocator->memory }; gint i = 0; GstV4l2Return ret = GST_V4L2_OK; GST_DEBUG_OBJECT (allocator, "stop allocator"); GST_OBJECT_LOCK (allocator); if (!g_atomic_int_get (&allocator->active)) goto done; if (gst_atomic_queue_length (allocator->free_queue) != allocator->count) { GST_DEBUG_OBJECT (allocator, "allocator is still in use"); ret = GST_V4L2_BUSY; goto done; } while (gst_atomic_queue_pop (allocator->free_queue)) { /* nothing */ }; for (i = 0; i < allocator->count; i++) { GstV4l2MemoryGroup *group = allocator->groups[i]; allocator->groups[i] = NULL; if (group) gst_v4l2_memory_group_free (group); } /* Not all drivers support rebufs(0), so warn only */ if (v4l2_ioctl (allocator->video_fd, VIDIOC_REQBUFS, &breq) < 0) GST_WARNING_OBJECT (allocator, "error releasing buffers buffers: %s", g_strerror (errno)); allocator->count = 0; g_atomic_int_set (&allocator->active, FALSE); done: GST_OBJECT_UNLOCK (allocator); return ret; } GstV4l2MemoryGroup * gst_v4l2_allocator_alloc_mmap (GstV4l2Allocator * allocator) { GstV4l2MemoryGroup *group; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_MMAP, NULL); group = gst_v4l2_allocator_alloc (allocator); if (group == NULL) return NULL; for (i = 0; i < group->n_mem; i++) { if (group->mem[i] == NULL) { gpointer data; data = v4l2_mmap (NULL, group->planes[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, allocator->video_fd, group->planes[i].m.mem_offset); if (data == MAP_FAILED) goto mmap_failed; GST_LOG_OBJECT (allocator, "mmap buffer length %d, data offset %d, plane %d", group->planes[i].length, group->planes[i].data_offset, i); group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator), NULL, group->planes[i].length, 0, 0, group->planes[i].length, i, data, -1, group); } else { /* Take back the allocator reference */ gst_object_ref (allocator); } group->mems_allocated++; } /* Ensure group size. Unlike GST, v4l2 have size (bytesused) initially set * to 0. As length might be bigger then the expected size exposed in the * format, we simply set bytesused initially and reset it here for * simplicity */ gst_v4l2_allocator_reset_size (allocator, group); return group; mmap_failed: { GST_ERROR_OBJECT (allocator, "Failed to mmap buffer: %s", g_strerror (errno)); _cleanup_failed_alloc (allocator, group); return NULL; } } GstV4l2MemoryGroup * gst_v4l2_allocator_alloc_dmabuf (GstV4l2Allocator * allocator, GstAllocator * dmabuf_allocator) { GstV4l2MemoryGroup *group; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_MMAP, NULL); group = gst_v4l2_allocator_alloc (allocator); if (group == NULL) return NULL; for (i = 0; i < group->n_mem; i++) { GstV4l2Memory *mem; GstMemory *dma_mem; gint dmafd; if (group->mem[i] == NULL) { struct v4l2_exportbuffer expbuf = { 0 }; expbuf.type = allocator->type; expbuf.index = group->buffer.index; expbuf.plane = i; expbuf.flags = O_CLOEXEC | O_RDWR; if (v4l2_ioctl (allocator->video_fd, VIDIOC_EXPBUF, &expbuf) < 0) goto expbuf_failed; GST_LOG_OBJECT (allocator, "exported DMABUF as fd %i plane %d", expbuf.fd, i); group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator), NULL, group->planes[i].length, 0, 0, group->planes[i].length, i, NULL, expbuf.fd, group); } else { /* Take back the allocator reference */ gst_object_ref (allocator); } g_assert (gst_is_v4l2_memory (group->mem[i])); mem = (GstV4l2Memory *) group->mem[i]; if ((dmafd = dup (mem->dmafd)) < 0) goto dup_failed; dma_mem = gst_dmabuf_allocator_alloc (dmabuf_allocator, dmafd, mem->mem.maxsize); gst_mini_object_set_qdata (GST_MINI_OBJECT (dma_mem), GST_V4L2_MEMORY_QUARK, mem, (GDestroyNotify) gst_memory_unref); group->mem[i] = dma_mem; group->mems_allocated++; } gst_v4l2_allocator_reset_size (allocator, group); return group; expbuf_failed: { GST_ERROR_OBJECT (allocator, "Failed to export DMABUF: %s", g_strerror (errno)); goto cleanup; } dup_failed: { GST_ERROR_OBJECT (allocator, "Failed to dup DMABUF descriptor: %s", g_strerror (errno)); goto cleanup; } cleanup: { _cleanup_failed_alloc (allocator, group); return NULL; } } static void gst_v4l2_allocator_clear_dmabufin (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { GstV4l2Memory *mem; gint i; g_return_if_fail (allocator->memory == V4L2_MEMORY_DMABUF); for (i = 0; i < group->n_mem; i++) { mem = (GstV4l2Memory *) group->mem[i]; GST_LOG_OBJECT (allocator, "clearing DMABUF import, fd %i plane %d", mem->dmafd, i); if (mem->dmafd >= 0) close (mem->dmafd); /* Update memory */ mem->mem.maxsize = 0; mem->mem.offset = 0; mem->mem.size = 0; mem->dmafd = -1; /* Update v4l2 structure */ group->planes[i].length = 0; group->planes[i].bytesused = 0; group->planes[i].m.fd = -1; group->planes[i].data_offset = 0; } if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { group->buffer.bytesused = 0; group->buffer.length = 0; group->buffer.m.fd = -1; } } GstV4l2MemoryGroup * gst_v4l2_allocator_alloc_dmabufin (GstV4l2Allocator * allocator) { GstV4l2MemoryGroup *group; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_DMABUF, NULL); group = gst_v4l2_allocator_alloc (allocator); if (group == NULL) return NULL; GST_LOG_OBJECT (allocator, "allocating empty DMABUF import group"); for (i = 0; i < group->n_mem; i++) { if (group->mem[i] == NULL) { group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator), NULL, 0, 0, 0, 0, i, NULL, -1, group); } else { /* Take back the allocator reference */ gst_object_ref (allocator); } group->mems_allocated++; } gst_v4l2_allocator_clear_dmabufin (allocator, group); return group; } static void gst_v4l2_allocator_clear_userptr (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { GstV4l2Memory *mem; gint i; g_return_if_fail (allocator->memory == V4L2_MEMORY_USERPTR); for (i = 0; i < group->n_mem; i++) { mem = (GstV4l2Memory *) group->mem[i]; GST_LOG_OBJECT (allocator, "clearing USERPTR %p plane %d size %" G_GSIZE_FORMAT, mem->data, i, mem->mem.size); mem->mem.maxsize = 0; mem->mem.size = 0; mem->data = NULL; group->planes[i].length = 0; group->planes[i].bytesused = 0; group->planes[i].m.userptr = 0; } if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { group->buffer.bytesused = 0; group->buffer.length = 0; group->buffer.m.userptr = 0; } } GstV4l2MemoryGroup * gst_v4l2_allocator_alloc_userptr (GstV4l2Allocator * allocator) { GstV4l2MemoryGroup *group; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_USERPTR, NULL); group = gst_v4l2_allocator_alloc (allocator); if (group == NULL) return NULL; GST_LOG_OBJECT (allocator, "allocating empty USERPTR group"); for (i = 0; i < group->n_mem; i++) { if (group->mem[i] == NULL) { group->mem[i] = (GstMemory *) _v4l2mem_new (0, GST_ALLOCATOR (allocator), NULL, 0, 0, 0, 0, i, NULL, -1, group); } else { /* Take back the allocator reference */ gst_object_ref (allocator); } group->mems_allocated++; } gst_v4l2_allocator_clear_userptr (allocator, group); return group; } gboolean gst_v4l2_allocator_import_dmabuf (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group, gint n_mem, GstMemory ** dma_mem) { GstV4l2Memory *mem; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_DMABUF, FALSE); if (group->n_mem != n_mem) goto n_mem_missmatch; for (i = 0; i < group->n_mem; i++) { gint dmafd; gsize size, offset, maxsize; if (!gst_is_dmabuf_memory (dma_mem[i])) goto not_dmabuf; size = gst_memory_get_sizes (dma_mem[i], &offset, &maxsize); if ((dmafd = dup (gst_dmabuf_memory_get_fd (dma_mem[i]))) < 0) goto dup_failed; GST_LOG_OBJECT (allocator, "imported DMABUF as fd %i plane %d", dmafd, i); mem = (GstV4l2Memory *) group->mem[i]; /* Update memory */ mem->mem.maxsize = maxsize; mem->mem.offset = offset; mem->mem.size = size; mem->dmafd = dmafd; /* Update v4l2 structure */ group->planes[i].length = maxsize; group->planes[i].bytesused = size; group->planes[i].m.fd = dmafd; group->planes[i].data_offset = offset; } /* Copy into buffer structure if not using planes */ if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { group->buffer.bytesused = group->planes[0].bytesused; group->buffer.length = group->planes[0].length; group->buffer.m.fd = group->planes[0].m.userptr; } else { group->buffer.length = group->n_mem; } return TRUE; n_mem_missmatch: { GST_ERROR_OBJECT (allocator, "Got %i dmabuf but needed %i", n_mem, group->n_mem); return FALSE; } not_dmabuf: { GST_ERROR_OBJECT (allocator, "Memory %i is not of DMABUF", i); return FALSE; } dup_failed: { GST_ERROR_OBJECT (allocator, "Failed to dup DMABUF descriptor: %s", g_strerror (errno)); return FALSE; } } gboolean gst_v4l2_allocator_import_userptr (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group, gsize img_size, int n_planes, gpointer * data, gsize * size) { GstV4l2Memory *mem; gint i; g_return_val_if_fail (allocator->memory == V4L2_MEMORY_USERPTR, FALSE); /* TODO Support passing N plane from 1 memory to MPLANE v4l2 format */ if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type) && n_planes != group->n_mem) goto n_mem_missmatch; for (i = 0; i < group->n_mem; i++) { gsize maxsize, psize; if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { struct v4l2_pix_format_mplane *pix = &allocator->format.fmt.pix_mp; maxsize = pix->plane_fmt[i].sizeimage; psize = size[i]; } else { maxsize = allocator->format.fmt.pix.sizeimage; psize = img_size; } g_assert (psize <= img_size); GST_LOG_OBJECT (allocator, "imported USERPTR %p plane %d size %" G_GSIZE_FORMAT, data[i], i, psize); mem = (GstV4l2Memory *) group->mem[i]; mem->mem.maxsize = maxsize; mem->mem.size = psize; mem->data = data[i]; group->planes[i].length = maxsize; group->planes[i].bytesused = psize; group->planes[i].m.userptr = (unsigned long) data[i]; group->planes[i].data_offset = 0; } /* Copy into buffer structure if not using planes */ if (!V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { group->buffer.bytesused = group->planes[0].bytesused; group->buffer.length = group->planes[0].length; group->buffer.m.userptr = group->planes[0].m.userptr; } else { group->buffer.length = group->n_mem; } return TRUE; n_mem_missmatch: { GST_ERROR_OBJECT (allocator, "Got %i userptr plane while driver need %i", n_planes, group->n_mem); return FALSE; } } void gst_v4l2_allocator_flush (GstV4l2Allocator * allocator) { gint i; GST_OBJECT_LOCK (allocator); if (!g_atomic_int_get (&allocator->active)) goto done; for (i = 0; i < allocator->count; i++) { GstV4l2MemoryGroup *group = allocator->groups[i]; gint n; if (IS_QUEUED (group->buffer)) { UNSET_QUEUED (group->buffer); gst_v4l2_allocator_reset_group (allocator, group); for (n = 0; n < group->n_mem; n++) gst_memory_unref (group->mem[n]); } } done: GST_OBJECT_UNLOCK (allocator); } gboolean gst_v4l2_allocator_qbuf (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { gboolean ret = TRUE; gint i; g_return_val_if_fail (g_atomic_int_get (&allocator->active), FALSE); /* update sizes */ if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { for (i = 0; i < group->n_mem; i++) group->planes[i].bytesused = gst_memory_get_sizes (group->mem[i], NULL, NULL); } else { group->buffer.bytesused = gst_memory_get_sizes (group->mem[0], NULL, NULL); } /* Ensure the memory will stay around and is RO */ for (i = 0; i < group->n_mem; i++) gst_memory_ref (group->mem[i]); if (v4l2_ioctl (allocator->video_fd, VIDIOC_QBUF, &group->buffer) < 0) { GST_ERROR_OBJECT (allocator, "failed queueing buffer %i: %s", group->buffer.index, g_strerror (errno)); /* Release the memory, possibly making it RW again */ for (i = 0; i < group->n_mem; i++) gst_memory_unref (group->mem[i]); ret = FALSE; if (IS_QUEUED (group->buffer)) { GST_DEBUG_OBJECT (allocator, "driver pretends buffer is queued even if queue failed"); UNSET_QUEUED (group->buffer); } goto done; } GST_LOG_OBJECT (allocator, "queued buffer %i (flags 0x%X)", group->buffer.index, group->buffer.flags); if (!IS_QUEUED (group->buffer)) { GST_DEBUG_OBJECT (allocator, "driver pretends buffer is not queued even if queue succeeded"); SET_QUEUED (group->buffer); } done: return ret; } GstFlowReturn gst_v4l2_allocator_dqbuf (GstV4l2Allocator * allocator, GstV4l2MemoryGroup ** group_out) { struct v4l2_buffer buffer = { 0 }; struct v4l2_plane planes[VIDEO_MAX_PLANES] = { {0} }; gint i; GstV4l2MemoryGroup *group = NULL; g_return_val_if_fail (g_atomic_int_get (&allocator->active), GST_FLOW_ERROR); buffer.type = allocator->type; buffer.memory = allocator->memory; if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { buffer.length = allocator->format.fmt.pix_mp.num_planes; buffer.m.planes = planes; } if (v4l2_ioctl (allocator->video_fd, VIDIOC_DQBUF, &buffer) < 0) goto error; group = allocator->groups[buffer.index]; if (!IS_QUEUED (group->buffer)) { GST_ERROR_OBJECT (allocator, "buffer %i was not queued, this indicate a driver bug.", buffer.index); return GST_FLOW_ERROR; } group->buffer = buffer; GST_LOG_OBJECT (allocator, "dequeued buffer %i (flags 0x%X)", buffer.index, buffer.flags); if (IS_QUEUED (group->buffer)) { GST_DEBUG_OBJECT (allocator, "driver pretends buffer is queued even if dequeue succeeded"); UNSET_QUEUED (group->buffer); } if (V4L2_TYPE_IS_MULTIPLANAR (allocator->type)) { group->buffer.m.planes = group->planes; memcpy (group->planes, buffer.m.planes, sizeof (planes)); } else { group->planes[0].bytesused = group->buffer.bytesused; group->planes[0].length = group->buffer.length; g_assert (sizeof (group->planes[0].m) == sizeof (group->buffer.m)); memcpy (&group->planes[0].m, &group->buffer.m, sizeof (group->buffer.m)); } /* And update memory size */ if (V4L2_TYPE_IS_OUTPUT (allocator->type)) { gst_v4l2_allocator_reset_size (allocator, group); } else { /* for capture, simply read the size */ for (i = 0; i < group->n_mem; i++) { if (G_LIKELY (group->planes[i].bytesused <= group->mem[i]->maxsize)) gst_memory_resize (group->mem[i], 0, group->planes[i].bytesused); else { GST_WARNING_OBJECT (allocator, "v4l2 provided buffer that is too big for the memory it was " "writing into. v4l2 claims %" G_GUINT32_FORMAT " bytes used but " "memory is only %" G_GSIZE_FORMAT "B. This is probably a driver " "bug.", group->planes[i].bytesused, group->mem[i]->maxsize); gst_memory_resize (group->mem[i], 0, group->mem[i]->maxsize); } } } /* Release the memory, possibly making it RW again */ for (i = 0; i < group->n_mem; i++) gst_memory_unref (group->mem[i]); *group_out = group; return GST_FLOW_OK; error: if (errno == EPIPE) { GST_DEBUG_OBJECT (allocator, "broken pipe signals last buffer"); return GST_FLOW_EOS; } GST_ERROR_OBJECT (allocator, "failed dequeuing a %s buffer: %s", memory_type_to_str (allocator->memory), g_strerror (errno)); switch (errno) { case EAGAIN: GST_WARNING_OBJECT (allocator, "Non-blocking I/O has been selected using O_NONBLOCK and" " no buffer was in the outgoing queue."); break; case EINVAL: GST_ERROR_OBJECT (allocator, "The buffer type is not supported, or the index is out of bounds, " "or no buffers have been allocated yet, or the userptr " "or length are invalid."); break; case ENOMEM: GST_ERROR_OBJECT (allocator, "insufficient memory to enqueue a user pointer buffer"); break; case EIO: GST_INFO_OBJECT (allocator, "VIDIOC_DQBUF failed due to an internal error." " Can also indicate temporary problems like signal loss." " Note the driver might dequeue an (empty) buffer despite" " returning an error, or even stop capturing."); /* have we de-queued a buffer ? */ if (!IS_QUEUED (buffer)) { GST_DEBUG_OBJECT (allocator, "reenqueueing buffer"); /* FIXME ... should we do something here? */ } break; case EINTR: GST_WARNING_OBJECT (allocator, "could not sync on a buffer on device"); break; default: GST_WARNING_OBJECT (allocator, "Grabbing frame got interrupted unexpectedly. %d: %s.", errno, g_strerror (errno)); break; } return GST_FLOW_ERROR; } void gst_v4l2_allocator_reset_group (GstV4l2Allocator * allocator, GstV4l2MemoryGroup * group) { switch (allocator->memory) { case V4L2_MEMORY_USERPTR: gst_v4l2_allocator_clear_userptr (allocator, group); break; case V4L2_MEMORY_DMABUF: gst_v4l2_allocator_clear_dmabufin (allocator, group); break; case V4L2_MEMORY_MMAP: break; default: g_assert_not_reached (); break; } gst_v4l2_allocator_reset_size (allocator, group); }