/* GStreamer * Copyright (C) 2020 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 "gstv4l2codecallocator.h" #include #include #define GST_CAT_DEFAULT allocator_debug GST_DEBUG_CATEGORY_STATIC (allocator_debug); typedef struct _GstV4l2CodecBuffer GstV4l2CodecBuffer; struct _GstV4l2CodecBuffer { gint index; GstMemory *mem[GST_VIDEO_MAX_PLANES]; guint num_mems; guint outstanding_mems; }; struct _GstV4l2CodecAllocator { GstDmaBufAllocator parent; GQueue pool; gint pool_size; gboolean detached; GCond buffer_cond; gboolean flushing; GstV4l2Decoder *decoder; GstPadDirection direction; }; G_DEFINE_TYPE_WITH_CODE (GstV4l2CodecAllocator, gst_v4l2_codec_allocator, GST_TYPE_DMABUF_ALLOCATOR, GST_DEBUG_CATEGORY_INIT (allocator_debug, "v4l2codecs-allocator", 0, "V4L2 Codecs Allocator")); static gboolean gst_v4l2_codec_allocator_release (GstMiniObject * mini_object); static GQuark gst_v4l2_codec_buffer_quark (void) { static gsize buffer_quark = 0; if (g_once_init_enter (&buffer_quark)) { GQuark quark = g_quark_from_string ("GstV4l2CodecBuffer"); g_once_init_leave (&buffer_quark, quark); } return buffer_quark; } static GstV4l2CodecBuffer * gst_v4l2_codec_buffer_new (GstAllocator * allocator, GstV4l2Decoder * decoder, GstPadDirection direction, gint index) { GstV4l2CodecBuffer *buf; guint i, num_mems; gint fds[GST_VIDEO_MAX_PLANES]; gsize sizes[GST_VIDEO_MAX_PLANES]; gsize offsets[GST_VIDEO_MAX_PLANES]; if (!gst_v4l2_decoder_export_buffer (decoder, direction, index, fds, sizes, offsets, &num_mems)) return NULL; buf = g_new0 (GstV4l2CodecBuffer, 1); buf->index = index; buf->num_mems = num_mems; for (i = 0; i < buf->num_mems; i++) { GstMemory *mem = gst_fd_allocator_alloc (allocator, fds[i], sizes[i], GST_FD_MEMORY_FLAG_KEEP_MAPPED); gst_memory_resize (mem, offsets[i], sizes[i] - offsets[i]); GST_MINI_OBJECT (mem)->dispose = gst_v4l2_codec_allocator_release; gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), gst_v4l2_codec_buffer_quark (), buf, NULL); /* On outstanding memory keeps a reference on the allocator, this is * needed to break the cycle. */ gst_object_unref (mem->allocator); buf->mem[i] = mem; } GST_DEBUG_OBJECT (allocator, "Create buffer %i with %i memory fds", buf->index, buf->num_mems); return buf; } static void gst_v4l2_codec_buffer_free (GstV4l2CodecBuffer * buf) { guint i; g_warn_if_fail (buf->outstanding_mems == 0); GST_DEBUG_OBJECT (buf->mem[0]->allocator, "Freeing buffer %i", buf->index); for (i = 0; i < buf->num_mems; i++) { GstMemory *mem = buf->mem[i]; GST_MINI_OBJECT (mem)->dispose = NULL; g_object_ref (mem->allocator); gst_memory_unref (mem); } g_free (buf); } static void gst_v4l2_codec_buffer_acquire (GstV4l2CodecBuffer * buf) { buf->outstanding_mems += buf->num_mems; } static gboolean gst_v4l2_codec_buffer_release_mem (GstV4l2CodecBuffer * buf) { return (--buf->outstanding_mems == 0); } static gboolean gst_v4l2_codec_allocator_release (GstMiniObject * mini_object) { GstMemory *mem = GST_MEMORY_CAST (mini_object); GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (mem->allocator); GstV4l2CodecBuffer *buf; GST_OBJECT_LOCK (self); buf = gst_mini_object_get_qdata (mini_object, gst_v4l2_codec_buffer_quark ()); gst_memory_ref (mem); if (gst_v4l2_codec_buffer_release_mem (buf)) { GST_DEBUG_OBJECT (self, "Placing back buffer %i into pool", buf->index); g_queue_push_tail (&self->pool, buf); g_cond_signal (&self->buffer_cond); } GST_OBJECT_UNLOCK (self); /* Keep last in case we are holding on the last allocator ref */ g_object_unref (mem->allocator); /* Returns FALSE so that our mini object isn't freed */ return FALSE; } static gboolean gst_v4l2_codec_allocator_prepare (GstV4l2CodecAllocator * self) { GstV4l2Decoder *decoder = self->decoder; GstPadDirection direction = self->direction; guint i; GST_DEBUG_OBJECT (self, "Try to create %d buffers", self->pool_size); /* Allocate buffers one by one to avoid fragmentation issue and * use the possible holes in v4l2 queue array */ for (i = 0; i < self->pool_size; i++) { GstV4l2CodecBuffer *buf; gint index = gst_v4l2_decoder_create_buffers (decoder, direction, 1); if (index < 0) { GST_ERROR_OBJECT (self, "%i buffer was needed, but only %i could be allocated", self->pool_size, i); goto failed; } buf = gst_v4l2_codec_buffer_new (GST_ALLOCATOR (self), decoder, direction, index); g_queue_push_tail (&self->pool, buf); } return TRUE; failed: gst_v4l2_decoder_request_buffers (decoder, direction, 0); return FALSE; } static void gst_v4l2_codec_allocator_init (GstV4l2CodecAllocator * self) { g_cond_init (&self->buffer_cond); } static void gst_v4l2_codec_allocator_dispose (GObject * object) { GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (object); GstV4l2CodecBuffer *buf; while ((buf = g_queue_pop_head (&self->pool))) gst_v4l2_codec_buffer_free (buf); if (self->decoder) { gst_v4l2_codec_allocator_detach (self); gst_clear_object (&self->decoder); } G_OBJECT_CLASS (gst_v4l2_codec_allocator_parent_class)->dispose (object); } static void gst_v4l2_codec_allocator_finalize (GObject * object) { GstV4l2CodecAllocator *self = GST_V4L2_CODEC_ALLOCATOR (object); g_cond_clear (&self->buffer_cond); G_OBJECT_CLASS (gst_v4l2_codec_allocator_parent_class)->finalize (object); } static void gst_v4l2_codec_allocator_class_init (GstV4l2CodecAllocatorClass * klass) { GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gst_v4l2_codec_allocator_dispose; object_class->finalize = gst_v4l2_codec_allocator_finalize; allocator_class->alloc = NULL; } GstV4l2CodecAllocator * gst_v4l2_codec_allocator_new (GstV4l2Decoder * decoder, GstPadDirection direction, guint num_buffers) { GstV4l2CodecAllocator *self = g_object_new (GST_TYPE_V4L2_CODEC_ALLOCATOR, NULL); self->decoder = g_object_ref (decoder); self->direction = direction; self->pool_size = num_buffers; if (!gst_v4l2_codec_allocator_prepare (self)) { g_object_unref (self); return NULL; } return self; } GstMemory * gst_v4l2_codec_allocator_alloc (GstV4l2CodecAllocator * self) { GstV4l2CodecBuffer *buf; GstMemory *mem = NULL; GST_OBJECT_LOCK (self); buf = g_queue_pop_head (&self->pool); if (buf) { GST_DEBUG_OBJECT (self, "Allocated buffer %i", buf->index); g_warn_if_fail (buf->num_mems == 1); mem = buf->mem[0]; g_object_ref (mem->allocator); buf->outstanding_mems++; } GST_OBJECT_UNLOCK (self); return mem; } gboolean gst_v4l2_codec_allocator_create_buffer (GstV4l2CodecAllocator * self) { /* TODO implement */ return FALSE; } gboolean gst_v4l2_codec_allocator_wait_for_buffer (GstV4l2CodecAllocator * self) { gboolean ret; GST_OBJECT_LOCK (self); while (self->pool.length == 0 && !self->flushing) g_cond_wait (&self->buffer_cond, GST_OBJECT_GET_LOCK (self)); ret = !self->flushing; GST_OBJECT_UNLOCK (self); return ret; } gboolean gst_v4l2_codec_allocator_prepare_buffer (GstV4l2CodecAllocator * self, GstBuffer * gstbuf) { GstV4l2CodecBuffer *buf; guint i; GST_OBJECT_LOCK (self); buf = g_queue_pop_head (&self->pool); if (!buf) { GST_OBJECT_UNLOCK (self); return FALSE; } GST_DEBUG_OBJECT (self, "Allocated buffer %i", buf->index); gst_v4l2_codec_buffer_acquire (buf); for (i = 0; i < buf->num_mems; i++) { gst_buffer_append_memory (gstbuf, buf->mem[i]); g_object_ref (buf->mem[i]->allocator); } GST_OBJECT_UNLOCK (self); return TRUE; } guint gst_v4l2_codec_allocator_get_pool_size (GstV4l2CodecAllocator * self) { guint size; GST_OBJECT_LOCK (self); size = self->pool_size; GST_OBJECT_UNLOCK (self); return size; } void gst_v4l2_codec_allocator_detach (GstV4l2CodecAllocator * self) { GST_OBJECT_LOCK (self); if (!self->detached) { self->detached = TRUE; gst_v4l2_decoder_request_buffers (self->decoder, self->direction, 0); } GST_OBJECT_UNLOCK (self); } void gst_v4l2_codec_allocator_set_flushing (GstV4l2CodecAllocator * self, gboolean flushing) { GST_OBJECT_LOCK (self); self->flushing = flushing; if (flushing) g_cond_broadcast (&self->buffer_cond); GST_OBJECT_UNLOCK (self); } guint32 gst_v4l2_codec_memory_get_index (GstMemory * mem) { GstV4l2CodecBuffer *buf; buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), gst_v4l2_codec_buffer_quark ()); g_return_val_if_fail (buf, G_MAXUINT32); return buf->index; }