/* GStreamer Apple Core Video memory * Copyright (C) 2015 Ilya Konstantinov * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "corevideomemory.h" GST_DEBUG_CATEGORY_STATIC (GST_CAT_APPLE_CORE_VIDEO_MEMORY); #define GST_CAT_DEFAULT GST_CAT_APPLE_CORE_VIDEO_MEMORY static const char *_lock_state_names[] = { "Unlocked", "Locked Read-Only", "Locked Read-Write" }; /** * gst_apple_core_video_pixel_buffer_new: * @buf: an unlocked CVPixelBuffer * * Initializes a wrapper to manage locking state for a CVPixelBuffer. * This function expects to receive unlocked CVPixelBuffer, and further assumes * that no one else will lock it (as long as the wrapper exists). * * This function retains @buf. * * Returns: The wrapped @buf. */ GstAppleCoreVideoPixelBuffer * gst_apple_core_video_pixel_buffer_new (CVPixelBufferRef buf) { GstAppleCoreVideoPixelBuffer *gpixbuf = g_slice_new (GstAppleCoreVideoPixelBuffer); gpixbuf->refcount = 1; g_mutex_init (&gpixbuf->mutex); gpixbuf->buf = CVPixelBufferRetain (buf); gpixbuf->lock_state = GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED; gpixbuf->lock_count = 0; return gpixbuf; } GstAppleCoreVideoPixelBuffer * gst_apple_core_video_pixel_buffer_ref (GstAppleCoreVideoPixelBuffer * gpixbuf) { g_atomic_int_inc (&gpixbuf->refcount); return gpixbuf; } void gst_apple_core_video_pixel_buffer_unref (GstAppleCoreVideoPixelBuffer * gpixbuf) { if (g_atomic_int_dec_and_test (&gpixbuf->refcount)) { if (gpixbuf->lock_state != GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED) { GST_ERROR ("%p: CVPixelBuffer memory still locked (lock_count = %d), likely forgot to unmap GstAppleCoreVideoMemory", gpixbuf, gpixbuf->lock_count); } CVPixelBufferRelease (gpixbuf->buf); g_mutex_clear (&gpixbuf->mutex); g_slice_free (GstAppleCoreVideoPixelBuffer, gpixbuf); } } /** * gst_apple_core_video_pixel_buffer_lock: * @gpixbuf: the wrapped CVPixelBuffer * @flags: mapping flags for either read-only or read-write locking * * Locks the pixel buffer into CPU memory for reading only, or * reading and writing. The desired lock mode is deduced from @flags. * * For planar buffers, each plane's #GstAppleCoreVideoMemory will reference * the same #GstAppleCoreVideoPixelBuffer; therefore this function will be * called multiple times for the same @gpixbuf. Each call to this function * should be matched by a call to gst_apple_core_video_pixel_buffer_unlock(). * * Notes: * * - Read-only locking improves performance by preventing Core Video * from invalidating existing caches of the buffer’s contents. * * - Only the first call actually locks; subsequent calls succeed * as long as their requested flags are compatible with how the buffer * is already locked. * * For example, the following code will succeed: * |[ * gst_memory_map(plane1, GST_MAP_READWRITE); * gst_memory_map(plane2, GST_MAP_READ); * ]| * while the ƒollowing code will fail: * |[ * gst_memory_map(plane1, GST_MAP_READ); * gst_memory_map(plane2, GST_MAP_READWRITE); /* ERROR: already locked for read-only */ * ]| * * Returns: %TRUE if the buffer was locked as requested */ static gboolean gst_apple_core_video_pixel_buffer_lock (GstAppleCoreVideoPixelBuffer * gpixbuf, GstMapFlags flags) { CVReturn cvret; CVOptionFlags lockFlags; g_mutex_lock (&gpixbuf->mutex); switch (gpixbuf->lock_state) { case GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED: lockFlags = (flags & GST_MAP_WRITE) ? 0 : kCVPixelBufferLock_ReadOnly; cvret = CVPixelBufferLockBaseAddress (gpixbuf->buf, lockFlags); if (cvret != kCVReturnSuccess) { g_mutex_unlock (&gpixbuf->mutex); /* TODO: Map kCVReturnError etc. into strings */ GST_ERROR ("%p: unable to lock base address for pixbuf %p: %d", gpixbuf, gpixbuf->buf, cvret); return FALSE; } gpixbuf->lock_state = (flags & GST_MAP_WRITE) ? GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READ_WRITE : GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY; break; case GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY: if (flags & GST_MAP_WRITE) { g_mutex_unlock (&gpixbuf->mutex); GST_ERROR ("%p: pixel buffer %p already locked for read-only access", gpixbuf, gpixbuf->buf); return FALSE; } break; case GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READ_WRITE: break; /* nothing to do, already most permissive mapping */ } g_atomic_int_inc (&gpixbuf->lock_count); g_mutex_unlock (&gpixbuf->mutex); GST_DEBUG ("%p: pixbuf %p, %s (%d times)", gpixbuf, gpixbuf->buf, _lock_state_names[gpixbuf->lock_state], gpixbuf->lock_count); return TRUE; } /** * gst_apple_core_video_pixel_buffer_unlock: * @gpixbuf: the wrapped CVPixelBuffer * * Unlocks the pixel buffer from CPU memory. Should be called * for every gst_apple_core_video_pixel_buffer_lock() call. */ static gboolean gst_apple_core_video_pixel_buffer_unlock (GstAppleCoreVideoPixelBuffer * gpixbuf) { CVOptionFlags lockFlags; CVReturn cvret; if (gpixbuf->lock_state == GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED) { GST_ERROR ("%p: pixel buffer %p not locked", gpixbuf, gpixbuf->buf); return FALSE; } if (!g_atomic_int_dec_and_test (&gpixbuf->lock_count)) { return TRUE; /* still locked, by current and/or other callers */ } g_mutex_lock (&gpixbuf->mutex); lockFlags = (gpixbuf->lock_state == GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY) ? kCVPixelBufferLock_ReadOnly : 0; cvret = CVPixelBufferUnlockBaseAddress (gpixbuf->buf, lockFlags); if (cvret != kCVReturnSuccess) { g_mutex_unlock (&gpixbuf->mutex); g_atomic_int_inc (&gpixbuf->lock_count); /* TODO: Map kCVReturnError etc. into strings */ GST_ERROR ("%p: unable to unlock base address for pixbuf %p: %d", gpixbuf, gpixbuf->buf, cvret); return FALSE; } gpixbuf->lock_state = GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED; g_mutex_unlock (&gpixbuf->mutex); GST_DEBUG ("%p: pixbuf %p, %s (%d locks remaining)", gpixbuf, gpixbuf->buf, _lock_state_names[gpixbuf->lock_state], gpixbuf->lock_count); return TRUE; } /* * GstAppleCoreVideoAllocator */ struct _GstAppleCoreVideoAllocatorClass { GstAllocatorClass parent_class; }; typedef struct _GstAppleCoreVideoAllocatorClass GstAppleCoreVideoAllocatorClass; struct _GstAppleCoreVideoAllocator { GstAllocator parent_instance; }; typedef struct _GstAppleCoreVideoAllocator GstAppleCoreVideoAllocator; /* GType for GstAppleCoreVideoAllocator */ GType gst_apple_core_video_allocator_get_type (void); #define GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR (gst_apple_core_video_allocator_get_type()) #define GST_IS_APPLE_CORE_VIDEO_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR)) #define GST_IS_APPLE_CORE_VIDEO_ALLOCATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR)) #define GST_APPLE_CORE_VIDEO_ALLOCATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocatorClass)) #define GST_APPLE_CORE_VIDEO_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocator)) #define GST_APPLE_CORE_VIDEO_ALLOCATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocatorClass)) G_DEFINE_TYPE (GstAppleCoreVideoAllocator, gst_apple_core_video_allocator, GST_TYPE_ALLOCATOR); /* Name for allocator registration */ #define GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME "AppleCoreVideoMemory" /* Singleton instance of GstAppleCoreVideoAllocator */ static GstAppleCoreVideoAllocator *_apple_core_video_allocator; /** * gst_apple_core_video_memory_init: * * Initializes the Core Video Memory allocator. This function must be called * before #GstAppleCoreVideoMemory can be created. * * It is safe to call this function multiple times. */ void gst_apple_core_video_memory_init (void) { static volatile gsize _init = 0; if (g_once_init_enter (&_init)) { GST_DEBUG_CATEGORY_INIT (GST_CAT_APPLE_CORE_VIDEO_MEMORY, "corevideomemory", 0, "Apple Core Video Memory"); _apple_core_video_allocator = g_object_new (GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, NULL); gst_object_ref_sink (_apple_core_video_allocator); gst_allocator_register (GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME, gst_object_ref (_apple_core_video_allocator)); g_once_init_leave (&_init, 1); } } /** * gst_is_apple_core_video_memory: * @mem: #GstMemory * * Checks whether @mem is backed by a CVPixelBuffer. * This has limited use since #GstAppleCoreVideoMemory is transparently * mapped into CPU memory on request. * * Returns: %TRUE when @mem is backed by a CVPixelBuffer */ gboolean gst_is_apple_core_video_memory (GstMemory * mem) { g_return_val_if_fail (mem != NULL, FALSE); return GST_IS_APPLE_CORE_VIDEO_ALLOCATOR (mem->allocator); } /** * gst_apple_core_video_memory_new: * * Helper function for gst_apple_core_video_mem_share(). * Users should call gst_apple_core_video_memory_new_wrapped() instead. */ static GstAppleCoreVideoMemory * gst_apple_core_video_memory_new (GstMemoryFlags flags, GstMemory * parent, GstAppleCoreVideoPixelBuffer * gpixbuf, gsize plane, gsize maxsize, gsize align, gsize offset, gsize size) { GstAppleCoreVideoMemory *mem; g_return_val_if_fail (gpixbuf != NULL, NULL); mem = g_slice_new0 (GstAppleCoreVideoMemory); gst_memory_init (GST_MEMORY_CAST (mem), flags, GST_ALLOCATOR_CAST (_apple_core_video_allocator), parent, maxsize, align, offset, size); mem->gpixbuf = gst_apple_core_video_pixel_buffer_ref (gpixbuf); mem->plane = plane; GST_DEBUG ("%p: gpixbuf %p, plane: %" G_GSSIZE_FORMAT ", size %" G_GSIZE_FORMAT, mem, mem->gpixbuf, mem->plane, mem->mem.size); return mem; } /** * gst_apple_core_video_memory_new_wrapped: * @gpixbuf: the backing #GstAppleCoreVideoPixelBuffer * @plane: the plane this memory will represent, or 0 for non-planar buffer * @size: the size of the buffer or specific plane * * Returns: a newly allocated #GstAppleCoreVideoMemory */ GstAppleCoreVideoMemory * gst_apple_core_video_memory_new_wrapped (GstAppleCoreVideoPixelBuffer * gpixbuf, gsize plane, gsize size) { return gst_apple_core_video_memory_new (0, NULL, gpixbuf, plane, size, 0, 0, size); } static gpointer gst_apple_core_video_mem_map (GstMemory * gmem, gsize maxsize, GstMapFlags flags) { GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem; gpointer ret; if (!gst_apple_core_video_pixel_buffer_lock (mem->gpixbuf, flags)) return NULL; if (CVPixelBufferIsPlanar (mem->gpixbuf->buf)) { ret = CVPixelBufferGetBaseAddressOfPlane (mem->gpixbuf->buf, mem->plane); if (ret != NULL) GST_DEBUG ("%p: pixbuf %p plane %" G_GSIZE_FORMAT " flags %08x: mapped %p", mem, mem->gpixbuf->buf, mem->plane, flags, ret); else GST_ERROR ("%p: invalid plane base address (NULL) for pixbuf %p plane %" G_GSIZE_FORMAT, mem, mem->gpixbuf->buf, mem->plane); } else { ret = CVPixelBufferGetBaseAddress (mem->gpixbuf->buf); if (ret != NULL) GST_DEBUG ("%p: pixbuf %p flags %08x: mapped %p", mem, mem->gpixbuf->buf, flags, ret); else GST_ERROR ("%p: invalid base address (NULL) for pixbuf %p" G_GSIZE_FORMAT, mem, mem->gpixbuf->buf); } return ret; } static void gst_apple_core_video_mem_unmap (GstMemory * gmem) { GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem; (void) gst_apple_core_video_pixel_buffer_unlock (mem->gpixbuf); GST_DEBUG ("%p: pixbuf %p plane %" G_GSIZE_FORMAT, mem, mem->gpixbuf->buf, mem->plane); } static GstMemory * gst_apple_core_video_mem_share (GstMemory * gmem, gssize offset, gssize size) { GstAppleCoreVideoMemory *mem; GstMemory *parent, *sub; mem = (GstAppleCoreVideoMemory *) gmem; /* find the real parent */ parent = gmem->parent; if (parent == NULL) parent = gmem; if (size == -1) size = gmem->size - offset; /* the shared memory is always readonly */ sub = GST_MEMORY_CAST (gst_apple_core_video_memory_new (GST_MINI_OBJECT_FLAGS (parent) | GST_MINI_OBJECT_FLAG_LOCK_READONLY, parent, mem->gpixbuf, mem->plane, gmem->maxsize, gmem->align, gmem->offset + offset, size)); return sub; } static gboolean gst_apple_core_video_mem_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset) { /* We may only return FALSE since: * 1) Core Video gives no guarantees about planes being consecutive. * We may only know this after mapping. * 2) GstAppleCoreVideoMemory instances for planes do not share a common * parent -- i.e. they're not offsets into the same parent * memory instance. * * It's not unlikely that planes will be stored in consecutive memory * but it should be checked by the user after mapping. */ return FALSE; } static void gst_apple_core_video_mem_free (GstAllocator * allocator, GstMemory * gmem) { GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem; gst_apple_core_video_pixel_buffer_unref (mem->gpixbuf); g_slice_free (GstAppleCoreVideoMemory, mem); } static void gst_apple_core_video_allocator_class_init (GstAppleCoreVideoAllocatorClass * klass) { GstAllocatorClass *allocator_class; allocator_class = (GstAllocatorClass *) klass; /* we don't do allocations, only wrap existing pixel buffers */ allocator_class->alloc = NULL; allocator_class->free = gst_apple_core_video_mem_free; } static void gst_apple_core_video_allocator_init (GstAppleCoreVideoAllocator * allocator) { GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator); alloc->mem_type = GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME; alloc->mem_map = gst_apple_core_video_mem_map; alloc->mem_unmap = gst_apple_core_video_mem_unmap; alloc->mem_share = gst_apple_core_video_mem_share; alloc->mem_is_span = gst_apple_core_video_mem_is_span; GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); }