From bde275408c7f614ccf6e440bdd747f7b663fafe6 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Sat, 25 Jan 2020 17:47:17 -0500 Subject: gst: Add a pool and an allocator implementation This is needed to track the lifetime of the FrameBufferAllocator in relation to the GstBuffer/GstMemory objects travelling inside GStreamer. Signed-off-by: Nicolas Dufresne Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- src/gstreamer/gstlibcameraallocator.cpp | 245 ++++++++++++++++++++++++++++++++ src/gstreamer/gstlibcameraallocator.h | 30 ++++ src/gstreamer/gstlibcamerapool.cpp | 110 ++++++++++++++ src/gstreamer/gstlibcamerapool.h | 27 ++++ src/gstreamer/meson.build | 13 +- 5 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 src/gstreamer/gstlibcameraallocator.cpp create mode 100644 src/gstreamer/gstlibcameraallocator.h create mode 100644 src/gstreamer/gstlibcamerapool.cpp create mode 100644 src/gstreamer/gstlibcamerapool.h diff --git a/src/gstreamer/gstlibcameraallocator.cpp b/src/gstreamer/gstlibcameraallocator.cpp new file mode 100644 index 00000000..861af596 --- /dev/null +++ b/src/gstreamer/gstlibcameraallocator.cpp @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcameraallocator.cpp - GStreamer Custom Allocator + */ + +#include "gstlibcameraallocator.h" + +#include +#include +#include + +#include "gstlibcamera-utils.h" + +using namespace libcamera; + +static gboolean gst_libcamera_allocator_release(GstMiniObject *mini_object); + +/** + * \struct FrameWrap + * \brief An internal wrapper to track the relation between FrameBuffer and + * GstMemory(s) + * + * This wrapper maintains a count of the outstanding GstMemory (there may be + * multiple GstMemory per FrameBuffer), and give back the FrameBuffer to the + * allocator pool when all memory objects have returned. + */ + +struct FrameWrap { + FrameWrap(GstAllocator *allocator, FrameBuffer *buffer, + gpointer stream); + ~FrameWrap(); + + void acquirePlane() { ++outstandingPlanes_; } + bool releasePlane() { return --outstandingPlanes_ == 0; } + + static GQuark getQuark(); + + gpointer stream_; + FrameBuffer *buffer_; + std::vector planes_; + gint outstandingPlanes_; +}; + +FrameWrap::FrameWrap(GstAllocator *allocator, FrameBuffer *buffer, + gpointer stream) + + : stream_(stream), + buffer_(buffer), + outstandingPlanes_(0) +{ + for (const FrameBuffer::Plane &plane : buffer->planes()) { + GstMemory *mem = gst_fd_allocator_alloc(allocator, plane.fd.fd(), plane.length, + GST_FD_MEMORY_FLAG_DONT_CLOSE); + gst_mini_object_set_qdata(GST_MINI_OBJECT(mem), getQuark(), this, nullptr); + GST_MINI_OBJECT(mem)->dispose = gst_libcamera_allocator_release; + g_object_unref(mem->allocator); + planes_.push_back(mem); + } +} + +FrameWrap::~FrameWrap() +{ + for (GstMemory *mem : planes_) { + GST_MINI_OBJECT(mem)->dispose = nullptr; + g_object_ref(mem->allocator); + gst_memory_unref(mem); + } +} + +GQuark FrameWrap::getQuark(void) +{ + static gsize frame_quark = 0; + + if (g_once_init_enter(&frame_quark)) { + GQuark quark = g_quark_from_string("GstLibcameraFrameWrap"); + g_once_init_leave(&frame_quark, quark); + } + + return frame_quark; +} + +/** + * \struct _GstLibcameraAllocator + * \brief A pooling GstDmaBufAllocator for libcamera + * + * This is a pooling GstDmaBufAllocator implementation. This implementation override + * the dispose function of memory object in order to keep them alive when they + * are disposed by downstream elements. + */ +struct _GstLibcameraAllocator { + GstDmaBufAllocator parent; + FrameBufferAllocator *fb_allocator; + /* + * A hash table using Stream pointer as key and returning a GQueue of + * FrameWrap. + */ + GHashTable *pools; +}; + +G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator, + GST_TYPE_DMABUF_ALLOCATOR); + +static gboolean +gst_libcamera_allocator_release(GstMiniObject *mini_object) +{ + GstMemory *mem = GST_MEMORY_CAST(mini_object); + GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(mem->allocator); + GLibLocker lock(GST_OBJECT(self)); + auto *frame = reinterpret_cast(gst_mini_object_get_qdata(mini_object, FrameWrap::getQuark())); + + gst_memory_ref(mem); + + if (frame->releasePlane()) { + auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, frame->stream_)); + g_return_val_if_fail(pool, TRUE); + g_queue_push_tail(pool, frame); + } + + /* Keep last in case we are holding on the last allocator ref. */ + g_object_unref(mem->allocator); + + /* Return FALSE so that our mini object isn't freed. */ + return FALSE; +} + +static void +gst_libcamera_allocator_free_pool(gpointer data) +{ + GQueue *queue = reinterpret_cast(data); + FrameWrap *frame; + + while ((frame = reinterpret_cast(g_queue_pop_head(queue)))) { + g_warn_if_fail(frame->outstandingPlanes_ == 0); + delete frame; + } + + g_queue_free(queue); +} + +static void +gst_libcamera_allocator_init(GstLibcameraAllocator *self) +{ + self->pools = g_hash_table_new_full(nullptr, nullptr, nullptr, + gst_libcamera_allocator_free_pool); + GST_OBJECT_FLAG_SET(self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); +} + +static void +gst_libcamera_allocator_dispose(GObject *object) +{ + GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(object); + + if (self->pools) { + g_hash_table_unref(self->pools); + self->pools = nullptr; + } + + G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->dispose(object); +} + +static void +gst_libcamera_allocator_finalize(GObject *object) +{ + GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(object); + + delete self->fb_allocator; + + G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object); +} + +static void +gst_libcamera_allocator_class_init(GstLibcameraAllocatorClass *klass) +{ + auto *allocator_class = GST_ALLOCATOR_CLASS(klass); + auto *object_class = G_OBJECT_CLASS(klass); + + object_class->dispose = gst_libcamera_allocator_dispose; + object_class->finalize = gst_libcamera_allocator_finalize; + allocator_class->alloc = nullptr; +} + +GstLibcameraAllocator * +gst_libcamera_allocator_new(std::shared_ptr camera) +{ + auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR, + nullptr)); + + self->fb_allocator = FrameBufferAllocator::create(camera); + for (Stream *stream : camera->streams()) { + gint ret; + + ret = self->fb_allocator->allocate(stream); + if (ret == 0) + return nullptr; + + GQueue *pool = g_queue_new(); + for (const std::unique_ptr &buffer : + self->fb_allocator->buffers(stream)) { + auto *fb = new FrameWrap(GST_ALLOCATOR(self), + buffer.get(), stream); + g_queue_push_tail(pool, fb); + } + + g_hash_table_insert(self->pools, stream, pool); + } + + return self; +} + +bool +gst_libcamera_allocator_prepare_buffer(GstLibcameraAllocator *self, + Stream *stream, GstBuffer *buffer) +{ + GLibLocker lock(GST_OBJECT(self)); + + auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, stream)); + g_return_val_if_fail(pool, false); + + auto *frame = reinterpret_cast(g_queue_pop_head(pool)); + if (!frame) + return false; + + for (GstMemory *mem : frame->planes_) { + frame->acquirePlane(); + gst_buffer_append_memory(buffer, mem); + g_object_ref(mem->allocator); + } + + return true; +} + +gsize +gst_libcamera_allocator_get_pool_size(GstLibcameraAllocator *self, + Stream *stream) +{ + GLibLocker lock(GST_OBJECT(self)); + + auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, stream)); + g_return_val_if_fail(pool, false); + + return pool->length; +} diff --git a/src/gstreamer/gstlibcameraallocator.h b/src/gstreamer/gstlibcameraallocator.h new file mode 100644 index 00000000..b3f7f367 --- /dev/null +++ b/src/gstreamer/gstlibcameraallocator.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcameraallocator.h - GStreamer Custom Allocator + */ + +#ifndef __GST_LIBCAMERA_ALLOCATOR_H__ +#define __GST_LIBCAMERA_ALLOCATOR_H__ + +#include +#include + +#include + +#define GST_TYPE_LIBCAMERA_ALLOCATOR gst_libcamera_allocator_get_type() +G_DECLARE_FINAL_TYPE(GstLibcameraAllocator, gst_libcamera_allocator, + GST_LIBCAMERA, ALLOCATOR, GstDmaBufAllocator) + +GstLibcameraAllocator *gst_libcamera_allocator_new(std::shared_ptr camera); + +bool gst_libcamera_allocator_prepare_buffer(GstLibcameraAllocator *self, + libcamera::Stream *stream, + GstBuffer *buffer); + +gsize gst_libcamera_allocator_get_pool_size(GstLibcameraAllocator *allocator, + libcamera::Stream *stream); + +#endif /* __GST_LIBCAMERA_ALLOCATOR_H__ */ diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp new file mode 100644 index 00000000..75e189ab --- /dev/null +++ b/src/gstreamer/gstlibcamerapool.cpp @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcamerapool.cpp - GStreamer Buffer Pool + */ + +#include "gstlibcamerapool.h" + +#include + +#include "gstlibcamera-utils.h" + +using namespace libcamera; + +struct _GstLibcameraPool { + GstBufferPool parent; + + GstAtomicQueue *queue; + GstLibcameraAllocator *allocator; + Stream *stream; +}; + +G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL); + +static GstFlowReturn +gst_libcamera_pool_acquire_buffer(GstBufferPool *pool, GstBuffer **buffer, + GstBufferPoolAcquireParams *params) +{ + GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool); + GstBuffer *buf = GST_BUFFER(gst_atomic_queue_pop(self->queue)); + if (!buf) + return GST_FLOW_ERROR; + + if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf)) + return GST_FLOW_ERROR; + + *buffer = buf; + return GST_FLOW_OK; +} + +static void +gst_libcamera_pool_reset_buffer(GstBufferPool *pool, GstBuffer *buffer) +{ + GstBufferPoolClass *klass = GST_BUFFER_POOL_CLASS(gst_libcamera_pool_parent_class); + + /* Clears all the memories and only pool the GstBuffer objects */ + gst_buffer_remove_all_memory(buffer); + klass->reset_buffer(pool, buffer); + GST_BUFFER_FLAGS(buffer) = 0; +} + +static void +gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer) +{ + GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool); + gst_atomic_queue_push(self->queue, buffer); +} + +static void +gst_libcamera_pool_init(GstLibcameraPool *self) +{ + self->queue = gst_atomic_queue_new(4); +} + +static void +gst_libcamera_pool_finalize(GObject *object) +{ + GstLibcameraPool *self = GST_LIBCAMERA_POOL(object); + GstBuffer *buf; + + while ((buf = GST_BUFFER(gst_atomic_queue_pop(self->queue)))) + gst_buffer_unref(buf); + + gst_atomic_queue_unref(self->queue); + g_object_unref(self->allocator); + + G_OBJECT_CLASS(gst_libcamera_pool_parent_class)->finalize(object); +} + +static void +gst_libcamera_pool_class_init(GstLibcameraPoolClass *klass) +{ + auto *object_class = G_OBJECT_CLASS(klass); + auto *pool_class = GST_BUFFER_POOL_CLASS(klass); + + object_class->finalize = gst_libcamera_pool_finalize; + pool_class->start = nullptr; + pool_class->acquire_buffer = gst_libcamera_pool_acquire_buffer; + pool_class->reset_buffer = gst_libcamera_pool_reset_buffer; + pool_class->release_buffer = gst_libcamera_pool_release_buffer; +} + +GstLibcameraPool * +gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream) +{ + auto *pool = GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr)); + + pool->allocator = GST_LIBCAMERA_ALLOCATOR(g_object_ref(allocator)); + pool->stream = stream; + + gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream); + for (gsize i = 0; i < pool_size; i++) { + GstBuffer *buffer = gst_buffer_new(); + gst_atomic_queue_push(pool->queue, buffer); + } + + return pool; +} diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h new file mode 100644 index 00000000..c0a04cfb --- /dev/null +++ b/src/gstreamer/gstlibcamerapool.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcamerapool.h - GStreamer Buffer Pool + * + * This is a partial implementation of GstBufferPool intended for internal use + * only. This pool cannot be configured or activated. + */ + +#ifndef __GST_LIBCAMERA_POOL_H__ +#define __GST_LIBCAMERA_POOL_H__ + +#include "gstlibcameraallocator.h" + +#include + +#include + +#define GST_TYPE_LIBCAMERA_POOL gst_libcamera_pool_get_type() +G_DECLARE_FINAL_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_LIBCAMERA, POOL, GstBufferPool) + +GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator, + libcamera::Stream *stream); + +#endif /* __GST_LIBCAMERA_POOL_H__ */ diff --git a/src/gstreamer/meson.build b/src/gstreamer/meson.build index 5324b2f5..1965b504 100644 --- a/src/gstreamer/meson.build +++ b/src/gstreamer/meson.build @@ -1,7 +1,9 @@ libcamera_gst_sources = [ 'gstlibcamera-utils.cpp', 'gstlibcamera.c', + 'gstlibcameraallocator.cpp', 'gstlibcamerapad.cpp', + 'gstlibcamerapool.cpp', 'gstlibcameraprovider.cpp', 'gstlibcamerasrc.cpp', ] @@ -13,10 +15,13 @@ libcamera_gst_c_args = [ glib_dep = dependency('glib', required : get_option('gstreamer')) -gst_dep = dependency('gstreamer-video-1.0', version : '>=1.14.0', - required : get_option('gstreamer')) +gst_dep_version = '>=1.14.0' +gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_dep_version, + required : get_option('gstreamer')) +gstallocator_dep = dependency('gstreamer-allocators-1.0', version : gst_dep_version, + required : get_option('gstreamer')) -if glib_dep.found() and gst_dep.found() +if glib_dep.found() and gstvideo_dep.found() and gstallocator_dep.found() # The G_DECLARE_FINAL_TYPE macro creates static inline functions that were # not marked as possibly unused prior to GLib v2.63.0. This causes clang to # complain about the ones we are not using. Silence the -Wunused-function @@ -28,7 +33,7 @@ if glib_dep.found() and gst_dep.found() libcamera_gst = shared_library('gstlibcamera', libcamera_gst_sources, c_args : libcamera_gst_c_args, - dependencies : [libcamera_dep, gst_dep], + dependencies : [libcamera_dep, gstvideo_dep, gstallocator_dep], install: true, install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')), ) -- cgit v1.2.1