/* GStreamer fd backed memory * Copyright (C) 2013 Linaro SA * Author: Benjamin Gaignard for Linaro. * * 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 mordetails. * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:gstfdmemory * @title: GstFdAllocator * @short_description: Memory wrapper for fd backed memory * @see_also: #GstMemory * * Since: 1.4 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstfdmemory.h" #ifdef HAVE_MMAP #include #include #endif GST_DEBUG_CATEGORY_STATIC (gst_fdmemory_debug); #define GST_CAT_DEFAULT gst_fdmemory_debug typedef struct { GstMemory mem; GstFdMemoryFlags flags; gint fd; gpointer data; gint mmapping_flags; gint mmap_count; GMutex lock; } GstFdMemory; static void gst_fd_mem_free (GstAllocator * allocator, GstMemory * gmem) { #ifdef HAVE_MMAP GstFdMemory *mem = (GstFdMemory *) gmem; if (mem->data) { if (!(mem->flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED)) g_warning (G_STRLOC ":%s: Freeing memory %p still mapped", G_STRFUNC, mem); munmap ((void *) mem->data, gmem->maxsize); } if (mem->fd >= 0 && gmem->parent == NULL && !(mem->flags & GST_FD_MEMORY_FLAG_DONT_CLOSE)) close (mem->fd); g_mutex_clear (&mem->lock); g_slice_free (GstFdMemory, mem); GST_DEBUG ("%p: freed", mem); #endif } static gpointer gst_fd_mem_map (GstMemory * gmem, gsize maxsize, GstMapFlags flags) { #ifdef HAVE_MMAP GstFdMemory *mem = (GstFdMemory *) gmem; gint prot; gpointer ret = NULL; if (gmem->parent) return gst_fd_mem_map (gmem->parent, maxsize, flags); prot = flags & GST_MAP_READ ? PROT_READ : 0; prot |= flags & GST_MAP_WRITE ? PROT_WRITE : 0; g_mutex_lock (&mem->lock); /* do not mmap twice the buffer */ if (mem->data) { /* only return address if mapping flags are a subset * of the previous flags */ if ((mem->mmapping_flags & prot) == prot) { ret = mem->data; mem->mmap_count++; } else if ((mem->flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED) && mem->mmap_count == 0 && mprotect (mem->data, gmem->maxsize, prot) == 0) { ret = mem->data; mem->mmapping_flags = prot; mem->mmap_count++; } goto out; } if (mem->fd != -1) { gint flags; flags = (mem->flags & GST_FD_MEMORY_FLAG_MAP_PRIVATE) ? MAP_PRIVATE : MAP_SHARED; mem->data = mmap (0, gmem->maxsize, prot, flags, mem->fd, 0); if (mem->data == MAP_FAILED) { GstDebugLevel level; mem->data = NULL; switch (errno) { case EACCES: level = GST_LEVEL_INFO; break; default: level = GST_LEVEL_ERROR; break; } GST_CAT_LEVEL_LOG (GST_CAT_DEFAULT, level, NULL, "%p: fd %d: mmap failed: %s", mem, mem->fd, g_strerror (errno)); goto out; } } GST_DEBUG ("%p: fd %d: mapped %p", mem, mem->fd, mem->data); if (mem->data) { mem->mmapping_flags = prot; mem->mmap_count++; ret = mem->data; } out: g_mutex_unlock (&mem->lock); return ret; #else /* !HAVE_MMAP */ return FALSE; #endif } static void gst_fd_mem_unmap (GstMemory * gmem) { #ifdef HAVE_MMAP GstFdMemory *mem = (GstFdMemory *) gmem; if (gmem->parent) return gst_fd_mem_unmap (gmem->parent); if (mem->flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED) { g_mutex_lock (&mem->lock); mem->mmap_count--; g_mutex_unlock (&mem->lock); return; } g_mutex_lock (&mem->lock); if (mem->data && !(--mem->mmap_count)) { munmap ((void *) mem->data, gmem->maxsize); mem->data = NULL; mem->mmapping_flags = 0; GST_DEBUG ("%p: fd %d unmapped", mem, mem->fd); } g_mutex_unlock (&mem->lock); #endif } static GstMemory * gst_fd_mem_share (GstMemory * gmem, gssize offset, gssize size) { #ifdef HAVE_MMAP GstFdMemory *mem = (GstFdMemory *) gmem; GstFdMemory *sub; GstMemory *parent; GST_DEBUG ("%p: share %" G_GSSIZE_FORMAT " %" G_GSIZE_FORMAT, mem, offset, size); /* find the real parent */ if ((parent = mem->mem.parent) == NULL) parent = (GstMemory *) mem; if (size == -1) size = gmem->maxsize - offset; sub = g_slice_new0 (GstFdMemory); /* the shared memory is always readonly */ gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) | GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->mem.allocator, parent, mem->mem.maxsize, mem->mem.align, mem->mem.offset + offset, size); sub->fd = mem->fd; g_mutex_init (&sub->lock); return GST_MEMORY_CAST (sub); #else /* !HAVE_MMAP */ return NULL; #endif } G_DEFINE_TYPE (GstFdAllocator, gst_fd_allocator, GST_TYPE_ALLOCATOR); static void gst_fd_allocator_class_init (GstFdAllocatorClass * klass) { GstAllocatorClass *allocator_class; allocator_class = (GstAllocatorClass *) klass; allocator_class->alloc = NULL; allocator_class->free = gst_fd_mem_free; GST_DEBUG_CATEGORY_INIT (gst_fdmemory_debug, "fdmemory", 0, "GstFdMemory and GstFdAllocator"); } static void gst_fd_allocator_init (GstFdAllocator * allocator) { GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator); alloc->mem_type = GST_ALLOCATOR_FD; alloc->mem_map = gst_fd_mem_map; alloc->mem_unmap = gst_fd_mem_unmap; alloc->mem_share = gst_fd_mem_share; GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); } /** * gst_fd_allocator_new: * * Return a new fd allocator. * * Returns: (transfer full): a new fd allocator. Use gst_object_unref() to * release the allocator after usage * * Since: 1.6 */ GstAllocator * gst_fd_allocator_new (void) { GstAllocator *alloc; alloc = g_object_new (GST_TYPE_FD_ALLOCATOR, NULL); gst_object_ref_sink (alloc); return alloc; } /** * gst_fd_allocator_alloc: * @allocator: allocator to be used for this memory * @fd: file descriptor * @size: memory size * @flags: extra #GstFdMemoryFlags * * Return a %GstMemory that wraps a generic file descriptor. * * Returns: (transfer full) (nullable): a GstMemory based on @allocator. * When the buffer will be released the allocator will close the @fd unless * the %GST_FD_MEMORY_FLAG_DONT_CLOSE flag is specified. * The memory is only mmapped on gst_buffer_map() request. * * Since: 1.6 */ GstMemory * gst_fd_allocator_alloc (GstAllocator * allocator, gint fd, gsize size, GstFdMemoryFlags flags) { #ifdef HAVE_MMAP GstFdMemory *mem; g_return_val_if_fail (GST_IS_FD_ALLOCATOR (allocator), NULL); mem = g_slice_new0 (GstFdMemory); gst_memory_init (GST_MEMORY_CAST (mem), 0, GST_ALLOCATOR_CAST (allocator), NULL, size, 0, 0, size); mem->flags = flags; mem->fd = fd; g_mutex_init (&mem->lock); GST_DEBUG ("%p: fd: %d size %" G_GSIZE_FORMAT, mem, mem->fd, mem->mem.maxsize); return (GstMemory *) mem; #else /* !HAVE_MMAP */ return NULL; #endif } /** * gst_is_fd_memory: * @mem: #GstMemory * * Check if @mem is memory backed by an fd * * Returns: %TRUE when @mem has an fd that can be retrieved with * gst_fd_memory_get_fd(). * * Since: 1.6 */ gboolean gst_is_fd_memory (GstMemory * mem) { g_return_val_if_fail (mem != NULL, FALSE); return GST_IS_FD_ALLOCATOR (mem->allocator); } /** * gst_fd_memory_get_fd: * @mem: #GstMemory * * Get the fd from @mem. Call gst_is_fd_memory() to check if @mem has * an fd. * * Returns: the fd of @mem or -1 when there is no fd on @mem * * Since: 1.6 */ gint gst_fd_memory_get_fd (GstMemory * mem) { g_return_val_if_fail (mem != NULL, -1); g_return_val_if_fail (GST_IS_FD_ALLOCATOR (mem->allocator), -1); return ((GstFdMemory *) mem)->fd; }