| /* GStreamer |
| * Copyright (C) 2011 Wim Taymans <wim.taymans@gmail.be> |
| * |
| * gstallocator.c: memory block allocator |
| * |
| * 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. |
| */ |
| |
| /** |
| * SECTION:gstallocator |
| * @title: GstAllocator |
| * @short_description: allocate memory blocks |
| * @see_also: #GstMemory |
| * |
| * Memory is usually created by allocators with a gst_allocator_alloc() |
| * method call. When %NULL is used as the allocator, the default allocator will |
| * be used. |
| * |
| * New allocators can be registered with gst_allocator_register(). |
| * Allocators are identified by name and can be retrieved with |
| * gst_allocator_find(). gst_allocator_set_default() can be used to change the |
| * default allocator. |
| * |
| * New memory can be created with gst_memory_new_wrapped() that wraps the memory |
| * allocated elsewhere. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gst_private.h" |
| #include "gstmemory.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_allocator_debug); |
| #define GST_CAT_DEFAULT gst_allocator_debug |
| |
| #define GST_ALLOCATOR_GET_PRIVATE(obj) \ |
| (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_ALLOCATOR, GstAllocatorPrivate)) |
| |
| struct _GstAllocatorPrivate |
| { |
| gpointer dummy; |
| }; |
| |
| #if defined(MEMORY_ALIGNMENT_MALLOC) |
| gsize gst_memory_alignment = 7; |
| #elif defined(MEMORY_ALIGNMENT_PAGESIZE) |
| /* we fill this in in the _init method */ |
| gsize gst_memory_alignment = 0; |
| #elif defined(MEMORY_ALIGNMENT) |
| gsize gst_memory_alignment = MEMORY_ALIGNMENT - 1; |
| #else |
| #error "No memory alignment configured" |
| gsize gst_memory_alignment = 0; |
| #endif |
| |
| /* the default allocator */ |
| static GstAllocator *_default_allocator; |
| |
| static GstAllocator *_sysmem_allocator; |
| |
| /* registered allocators */ |
| static GRWLock lock; |
| static GHashTable *allocators; |
| |
| G_DEFINE_ABSTRACT_TYPE (GstAllocator, gst_allocator, GST_TYPE_OBJECT); |
| |
| static void |
| gst_allocator_class_init (GstAllocatorClass * klass) |
| { |
| g_type_class_add_private (klass, sizeof (GstAllocatorPrivate)); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_allocator_debug, "allocator", 0, |
| "allocator debug"); |
| } |
| |
| static GstMemory * |
| _fallback_mem_copy (GstMemory * mem, gssize offset, gssize size) |
| { |
| GstMemory *copy; |
| GstMapInfo sinfo, dinfo; |
| GstAllocationParams params = { 0, mem->align, 0, 0, }; |
| GstAllocator *allocator; |
| |
| if (!gst_memory_map (mem, &sinfo, GST_MAP_READ)) |
| return NULL; |
| |
| if (size == -1) |
| size = sinfo.size > offset ? sinfo.size - offset : 0; |
| |
| /* use the same allocator as the memory we copy */ |
| allocator = mem->allocator; |
| if (GST_OBJECT_FLAG_IS_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC)) |
| allocator = NULL; |
| copy = gst_allocator_alloc (allocator, size, ¶ms); |
| |
| if (!gst_memory_map (copy, &dinfo, GST_MAP_WRITE)) { |
| GST_CAT_WARNING (GST_CAT_MEMORY, "could not write map memory %p", copy); |
| gst_allocator_free (mem->allocator, copy); |
| gst_memory_unmap (mem, &sinfo); |
| return NULL; |
| } |
| |
| GST_CAT_DEBUG (GST_CAT_PERFORMANCE, |
| "memcpy %" G_GSSIZE_FORMAT " memory %p -> %p", size, mem, copy); |
| memcpy (dinfo.data, sinfo.data + offset, size); |
| gst_memory_unmap (copy, &dinfo); |
| gst_memory_unmap (mem, &sinfo); |
| |
| return copy; |
| } |
| |
| static gboolean |
| _fallback_mem_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset) |
| { |
| return FALSE; |
| } |
| |
| static void |
| gst_allocator_init (GstAllocator * allocator) |
| { |
| allocator->priv = GST_ALLOCATOR_GET_PRIVATE (allocator); |
| |
| allocator->mem_copy = _fallback_mem_copy; |
| allocator->mem_is_span = _fallback_mem_is_span; |
| } |
| |
| G_DEFINE_BOXED_TYPE (GstAllocationParams, gst_allocation_params, |
| (GBoxedCopyFunc) gst_allocation_params_copy, |
| (GBoxedFreeFunc) gst_allocation_params_free); |
| |
| /** |
| * gst_allocation_params_init: |
| * @params: a #GstAllocationParams |
| * |
| * Initialize @params to its default values |
| */ |
| void |
| gst_allocation_params_init (GstAllocationParams * params) |
| { |
| g_return_if_fail (params != NULL); |
| |
| memset (params, 0, sizeof (GstAllocationParams)); |
| } |
| |
| /** |
| * gst_allocation_params_copy: |
| * @params: (transfer none) (nullable): a #GstAllocationParams |
| * |
| * Create a copy of @params. |
| * |
| * Free-function: gst_allocation_params_free |
| * |
| * Returns: (transfer full) (nullable): a new ##GstAllocationParams, free with |
| * gst_allocation_params_free(). |
| */ |
| GstAllocationParams * |
| gst_allocation_params_copy (const GstAllocationParams * params) |
| { |
| GstAllocationParams *result = NULL; |
| |
| if (params) { |
| result = |
| (GstAllocationParams *) g_slice_copy (sizeof (GstAllocationParams), |
| params); |
| } |
| return result; |
| } |
| |
| /** |
| * gst_allocation_params_free: |
| * @params: (in) (transfer full): a #GstAllocationParams |
| * |
| * Free @params |
| */ |
| void |
| gst_allocation_params_free (GstAllocationParams * params) |
| { |
| g_slice_free (GstAllocationParams, params); |
| } |
| |
| /** |
| * gst_allocator_register: |
| * @name: the name of the allocator |
| * @allocator: (transfer full): #GstAllocator |
| * |
| * Registers the memory @allocator with @name. This function takes ownership of |
| * @allocator. |
| */ |
| void |
| gst_allocator_register (const gchar * name, GstAllocator * allocator) |
| { |
| g_return_if_fail (name != NULL); |
| g_return_if_fail (allocator != NULL); |
| |
| GST_CAT_DEBUG (GST_CAT_MEMORY, "registering allocator %p with name \"%s\"", |
| allocator, name); |
| |
| g_rw_lock_writer_lock (&lock); |
| /* The ref will never be released */ |
| GST_OBJECT_FLAG_SET (allocator, GST_OBJECT_FLAG_MAY_BE_LEAKED); |
| g_hash_table_insert (allocators, (gpointer) name, (gpointer) allocator); |
| g_rw_lock_writer_unlock (&lock); |
| } |
| |
| /** |
| * gst_allocator_find: |
| * @name: (allow-none): the name of the allocator |
| * |
| * Find a previously registered allocator with @name. When @name is %NULL, the |
| * default allocator will be returned. |
| * |
| * Returns: (transfer full) (nullable): a #GstAllocator or %NULL when |
| * the allocator with @name was not registered. Use gst_object_unref() |
| * to release the allocator after usage. |
| */ |
| GstAllocator * |
| gst_allocator_find (const gchar * name) |
| { |
| GstAllocator *allocator; |
| |
| g_rw_lock_reader_lock (&lock); |
| if (name) { |
| allocator = g_hash_table_lookup (allocators, (gconstpointer) name); |
| } else { |
| allocator = _default_allocator; |
| } |
| if (allocator) |
| gst_object_ref (allocator); |
| g_rw_lock_reader_unlock (&lock); |
| |
| return allocator; |
| } |
| |
| /** |
| * gst_allocator_set_default: |
| * @allocator: (transfer full): a #GstAllocator |
| * |
| * Set the default allocator. This function takes ownership of @allocator. |
| */ |
| void |
| gst_allocator_set_default (GstAllocator * allocator) |
| { |
| GstAllocator *old; |
| |
| g_return_if_fail (GST_IS_ALLOCATOR (allocator)); |
| |
| g_rw_lock_writer_lock (&lock); |
| old = _default_allocator; |
| _default_allocator = allocator; |
| g_rw_lock_writer_unlock (&lock); |
| |
| if (old) |
| gst_object_unref (old); |
| } |
| |
| /** |
| * gst_allocator_alloc: |
| * @allocator: (transfer none) (allow-none): a #GstAllocator to use |
| * @size: size of the visible memory area |
| * @params: (transfer none) (allow-none): optional parameters |
| * |
| * Use @allocator to allocate a new memory block with memory that is at least |
| * @size big. |
| * |
| * The optional @params can specify the prefix and padding for the memory. If |
| * %NULL is passed, no flags, no extra prefix/padding and a default alignment is |
| * used. |
| * |
| * The prefix/padding will be filled with 0 if flags contains |
| * #GST_MEMORY_FLAG_ZERO_PREFIXED and #GST_MEMORY_FLAG_ZERO_PADDED respectively. |
| * |
| * When @allocator is %NULL, the default allocator will be used. |
| * |
| * The alignment in @params is given as a bitmask so that @align + 1 equals |
| * the amount of bytes to align to. For example, to align to 8 bytes, |
| * use an alignment of 7. |
| * |
| * Returns: (transfer full) (nullable): a new #GstMemory. |
| */ |
| GstMemory * |
| gst_allocator_alloc (GstAllocator * allocator, gsize size, |
| GstAllocationParams * params) |
| { |
| GstMemory *mem; |
| static GstAllocationParams defparams = { 0, 0, 0, 0, }; |
| GstAllocatorClass *aclass; |
| |
| if (params) { |
| g_return_val_if_fail (((params->align + 1) & params->align) == 0, NULL); |
| } else { |
| params = &defparams; |
| } |
| |
| if (allocator == NULL) |
| allocator = _default_allocator; |
| |
| aclass = GST_ALLOCATOR_GET_CLASS (allocator); |
| if (aclass->alloc) |
| mem = aclass->alloc (allocator, size, params); |
| else |
| mem = NULL; |
| |
| return mem; |
| } |
| |
| /** |
| * gst_allocator_free: |
| * @allocator: (transfer none): a #GstAllocator to use |
| * @memory: (transfer full): the memory to free |
| * |
| * Free @memory that was previously allocated with gst_allocator_alloc(). |
| */ |
| void |
| gst_allocator_free (GstAllocator * allocator, GstMemory * memory) |
| { |
| GstAllocatorClass *aclass; |
| |
| g_return_if_fail (GST_IS_ALLOCATOR (allocator)); |
| g_return_if_fail (memory != NULL); |
| g_return_if_fail (memory->allocator == allocator); |
| |
| aclass = GST_ALLOCATOR_GET_CLASS (allocator); |
| if (aclass->free) |
| aclass->free (allocator, memory); |
| } |
| |
| /* default memory implementation */ |
| typedef struct |
| { |
| GstMemory mem; |
| |
| gsize slice_size; |
| guint8 *data; |
| |
| gpointer user_data; |
| GDestroyNotify notify; |
| } GstMemorySystem; |
| |
| typedef struct |
| { |
| GstAllocator parent; |
| } GstAllocatorSysmem; |
| |
| typedef struct |
| { |
| GstAllocatorClass parent_class; |
| } GstAllocatorSysmemClass; |
| |
| static GType gst_allocator_sysmem_get_type (void); |
| G_DEFINE_TYPE (GstAllocatorSysmem, gst_allocator_sysmem, GST_TYPE_ALLOCATOR); |
| |
| /* initialize the fields */ |
| static inline void |
| _sysmem_init (GstMemorySystem * mem, GstMemoryFlags flags, |
| GstMemory * parent, gsize slice_size, |
| gpointer data, gsize maxsize, gsize align, gsize offset, gsize size, |
| gpointer user_data, GDestroyNotify notify) |
| { |
| gst_memory_init (GST_MEMORY_CAST (mem), |
| flags, _sysmem_allocator, parent, maxsize, align, offset, size); |
| |
| mem->slice_size = slice_size; |
| mem->data = data; |
| mem->user_data = user_data; |
| mem->notify = notify; |
| } |
| |
| /* create a new memory block that manages the given memory */ |
| static inline GstMemorySystem * |
| _sysmem_new (GstMemoryFlags flags, |
| GstMemory * parent, gpointer data, gsize maxsize, gsize align, gsize offset, |
| gsize size, gpointer user_data, GDestroyNotify notify) |
| { |
| GstMemorySystem *mem; |
| gsize slice_size; |
| |
| slice_size = sizeof (GstMemorySystem); |
| |
| mem = g_slice_alloc (slice_size); |
| _sysmem_init (mem, flags, parent, slice_size, |
| data, maxsize, align, offset, size, user_data, notify); |
| |
| return mem; |
| } |
| |
| /* allocate the memory and structure in one block */ |
| static GstMemorySystem * |
| _sysmem_new_block (GstMemoryFlags flags, |
| gsize maxsize, gsize align, gsize offset, gsize size) |
| { |
| GstMemorySystem *mem; |
| gsize aoffset, slice_size, padding; |
| guint8 *data; |
| |
| /* ensure configured alignment */ |
| align |= gst_memory_alignment; |
| /* allocate more to compensate for alignment */ |
| maxsize += align; |
| /* alloc header and data in one block */ |
| slice_size = sizeof (GstMemorySystem) + maxsize; |
| |
| mem = g_slice_alloc (slice_size); |
| if (mem == NULL) |
| return NULL; |
| |
| data = (guint8 *) mem + sizeof (GstMemorySystem); |
| |
| /* do alignment */ |
| if ((aoffset = ((guintptr) data & align))) { |
| aoffset = (align + 1) - aoffset; |
| data += aoffset; |
| maxsize -= aoffset; |
| } |
| |
| if (offset && (flags & GST_MEMORY_FLAG_ZERO_PREFIXED)) |
| memset (data, 0, offset); |
| |
| padding = maxsize - (offset + size); |
| if (padding && (flags & GST_MEMORY_FLAG_ZERO_PADDED)) |
| memset (data + offset + size, 0, padding); |
| |
| _sysmem_init (mem, flags, NULL, slice_size, data, maxsize, |
| align, offset, size, NULL, NULL); |
| |
| return mem; |
| } |
| |
| static gpointer |
| _sysmem_map (GstMemorySystem * mem, gsize maxsize, GstMapFlags flags) |
| { |
| return mem->data; |
| } |
| |
| static gboolean |
| _sysmem_unmap (GstMemorySystem * mem) |
| { |
| return TRUE; |
| } |
| |
| static GstMemorySystem * |
| _sysmem_copy (GstMemorySystem * mem, gssize offset, gsize size) |
| { |
| GstMemorySystem *copy; |
| |
| if (size == -1) |
| size = mem->mem.size > offset ? mem->mem.size - offset : 0; |
| |
| copy = _sysmem_new_block (0, size, mem->mem.align, 0, size); |
| GST_CAT_DEBUG (GST_CAT_PERFORMANCE, |
| "memcpy %" G_GSIZE_FORMAT " memory %p -> %p", size, mem, copy); |
| memcpy (copy->data, mem->data + mem->mem.offset + offset, size); |
| |
| return copy; |
| } |
| |
| static GstMemorySystem * |
| _sysmem_share (GstMemorySystem * mem, gssize offset, gsize size) |
| { |
| GstMemorySystem *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 = |
| _sysmem_new (GST_MINI_OBJECT_FLAGS (parent) | |
| GST_MINI_OBJECT_FLAG_LOCK_READONLY, parent, mem->data, mem->mem.maxsize, |
| mem->mem.align, mem->mem.offset + offset, size, NULL, NULL); |
| |
| return sub; |
| } |
| |
| static gboolean |
| _sysmem_is_span (GstMemorySystem * mem1, GstMemorySystem * mem2, gsize * offset) |
| { |
| |
| if (offset) { |
| GstMemorySystem *parent; |
| |
| parent = (GstMemorySystem *) mem1->mem.parent; |
| |
| *offset = mem1->mem.offset - parent->mem.offset; |
| } |
| |
| /* and memory is contiguous */ |
| return mem1->data + mem1->mem.offset + mem1->mem.size == |
| mem2->data + mem2->mem.offset; |
| } |
| |
| static GstMemory * |
| default_alloc (GstAllocator * allocator, gsize size, |
| GstAllocationParams * params) |
| { |
| gsize maxsize = size + params->prefix + params->padding; |
| |
| return (GstMemory *) _sysmem_new_block (params->flags, |
| maxsize, params->align, params->prefix, size); |
| } |
| |
| static void |
| default_free (GstAllocator * allocator, GstMemory * mem) |
| { |
| GstMemorySystem *dmem = (GstMemorySystem *) mem; |
| gsize slice_size; |
| |
| if (dmem->notify) |
| dmem->notify (dmem->user_data); |
| |
| slice_size = dmem->slice_size; |
| |
| #ifdef USE_POISONING |
| /* just poison the structs, not all the data */ |
| memset (mem, 0xff, sizeof (GstMemorySystem)); |
| #endif |
| |
| g_slice_free1 (slice_size, mem); |
| } |
| |
| static void |
| gst_allocator_sysmem_finalize (GObject * obj) |
| { |
| /* Don't raise warnings if we are shutting down */ |
| if (_default_allocator) |
| g_warning ("The default memory allocator was freed!"); |
| |
| ((GObjectClass *) gst_allocator_sysmem_parent_class)->finalize (obj); |
| } |
| |
| static void |
| gst_allocator_sysmem_class_init (GstAllocatorSysmemClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstAllocatorClass *allocator_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| allocator_class = (GstAllocatorClass *) klass; |
| |
| gobject_class->finalize = gst_allocator_sysmem_finalize; |
| |
| allocator_class->alloc = default_alloc; |
| allocator_class->free = default_free; |
| } |
| |
| static void |
| gst_allocator_sysmem_init (GstAllocatorSysmem * allocator) |
| { |
| GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator); |
| |
| GST_CAT_DEBUG (GST_CAT_MEMORY, "init allocator %p", allocator); |
| |
| alloc->mem_type = GST_ALLOCATOR_SYSMEM; |
| alloc->mem_map = (GstMemoryMapFunction) _sysmem_map; |
| alloc->mem_unmap = (GstMemoryUnmapFunction) _sysmem_unmap; |
| alloc->mem_copy = (GstMemoryCopyFunction) _sysmem_copy; |
| alloc->mem_share = (GstMemoryShareFunction) _sysmem_share; |
| alloc->mem_is_span = (GstMemoryIsSpanFunction) _sysmem_is_span; |
| } |
| |
| void |
| _priv_gst_allocator_initialize (void) |
| { |
| g_rw_lock_init (&lock); |
| allocators = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, |
| gst_object_unref); |
| |
| #ifdef HAVE_GETPAGESIZE |
| #ifdef MEMORY_ALIGNMENT_PAGESIZE |
| gst_memory_alignment = getpagesize () - 1; |
| #endif |
| #endif |
| |
| GST_CAT_DEBUG (GST_CAT_MEMORY, "memory alignment: %" G_GSIZE_FORMAT, |
| gst_memory_alignment); |
| |
| _sysmem_allocator = g_object_new (gst_allocator_sysmem_get_type (), NULL); |
| |
| /* Clear floating flag */ |
| gst_object_ref_sink (_sysmem_allocator); |
| |
| gst_allocator_register (GST_ALLOCATOR_SYSMEM, |
| gst_object_ref (_sysmem_allocator)); |
| |
| _default_allocator = gst_object_ref (_sysmem_allocator); |
| } |
| |
| void |
| _priv_gst_allocator_cleanup (void) |
| { |
| gst_object_unref (_sysmem_allocator); |
| _sysmem_allocator = NULL; |
| |
| gst_object_unref (_default_allocator); |
| _default_allocator = NULL; |
| |
| g_clear_pointer (&allocators, g_hash_table_unref); |
| } |
| |
| /** |
| * gst_memory_new_wrapped: |
| * @flags: #GstMemoryFlags |
| * @data: (array length=size) (element-type guint8) (transfer none): data to |
| * wrap |
| * @maxsize: allocated size of @data |
| * @offset: offset in @data |
| * @size: size of valid data |
| * @user_data: (allow-none): user_data |
| * @notify: (allow-none) (scope async) (closure user_data): called with @user_data when the memory is freed |
| * |
| * Allocate a new memory block that wraps the given @data. |
| * |
| * The prefix/padding must be filled with 0 if @flags contains |
| * #GST_MEMORY_FLAG_ZERO_PREFIXED and #GST_MEMORY_FLAG_ZERO_PADDED respectively. |
| * |
| * Returns: (transfer full) (nullable): a new #GstMemory. |
| */ |
| GstMemory * |
| gst_memory_new_wrapped (GstMemoryFlags flags, gpointer data, |
| gsize maxsize, gsize offset, gsize size, gpointer user_data, |
| GDestroyNotify notify) |
| { |
| GstMemorySystem *mem; |
| |
| g_return_val_if_fail (data != NULL, NULL); |
| g_return_val_if_fail (offset + size <= maxsize, NULL); |
| |
| mem = |
| _sysmem_new (flags, NULL, data, maxsize, 0, offset, size, user_data, |
| notify); |
| |
| return (GstMemory *) mem; |
| } |