1 /* GStreamer Apple Core Video memory
2  * Copyright (C) 2015 Ilya Konstantinov
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for mordetails.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "corevideomemory.h"
25 
26 GST_DEBUG_CATEGORY_STATIC (GST_CAT_APPLE_CORE_VIDEO_MEMORY);
27 #define GST_CAT_DEFAULT GST_CAT_APPLE_CORE_VIDEO_MEMORY
28 
29 static const char *_lock_state_names[] = {
30   "Unlocked", "Locked Read-Only", "Locked Read-Write"
31 };
32 
33 /**
34  * gst_apple_core_video_pixel_buffer_new:
35  * @buf: an unlocked CVPixelBuffer
36  *
37  * Initializes a wrapper to manage locking state for a CVPixelBuffer.
38  * This function expects to receive unlocked CVPixelBuffer, and further assumes
39  * that no one else will lock it (as long as the wrapper exists).
40  *
41  * This function retains @buf.
42  *
43  * Returns: The wrapped @buf.
44  */
45 GstAppleCoreVideoPixelBuffer *
gst_apple_core_video_pixel_buffer_new(CVPixelBufferRef buf)46 gst_apple_core_video_pixel_buffer_new (CVPixelBufferRef buf)
47 {
48   GstAppleCoreVideoPixelBuffer *gpixbuf =
49       g_slice_new (GstAppleCoreVideoPixelBuffer);
50   gpixbuf->refcount = 1;
51   g_mutex_init (&gpixbuf->mutex);
52   gpixbuf->buf = CVPixelBufferRetain (buf);
53   gpixbuf->lock_state = GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED;
54   gpixbuf->lock_count = 0;
55   return gpixbuf;
56 }
57 
58 GstAppleCoreVideoPixelBuffer *
gst_apple_core_video_pixel_buffer_ref(GstAppleCoreVideoPixelBuffer * gpixbuf)59 gst_apple_core_video_pixel_buffer_ref (GstAppleCoreVideoPixelBuffer * gpixbuf)
60 {
61   g_atomic_int_inc (&gpixbuf->refcount);
62   return gpixbuf;
63 }
64 
65 void
gst_apple_core_video_pixel_buffer_unref(GstAppleCoreVideoPixelBuffer * gpixbuf)66 gst_apple_core_video_pixel_buffer_unref (GstAppleCoreVideoPixelBuffer * gpixbuf)
67 {
68   if (g_atomic_int_dec_and_test (&gpixbuf->refcount)) {
69     if (gpixbuf->lock_state != GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED) {
70       GST_ERROR
71           ("%p: CVPixelBuffer memory still locked (lock_count = %d), likely forgot to unmap GstAppleCoreVideoMemory",
72           gpixbuf, gpixbuf->lock_count);
73     }
74     CVPixelBufferRelease (gpixbuf->buf);
75     g_mutex_clear (&gpixbuf->mutex);
76     g_slice_free (GstAppleCoreVideoPixelBuffer, gpixbuf);
77   }
78 }
79 
80 /**
81  * gst_apple_core_video_pixel_buffer_lock:
82  * @gpixbuf: the wrapped CVPixelBuffer
83  * @flags: mapping flags for either read-only or read-write locking
84  *
85  * Locks the pixel buffer into CPU memory for reading only, or
86  * reading and writing. The desired lock mode is deduced from @flags.
87  *
88  * For planar buffers, each plane's #GstAppleCoreVideoMemory will reference
89  * the same #GstAppleCoreVideoPixelBuffer; therefore this function will be
90  * called multiple times for the same @gpixbuf. Each call to this function
91  * should be matched by a call to gst_apple_core_video_pixel_buffer_unlock().
92  *
93  * Notes:
94  *
95  * - Read-only locking improves performance by preventing Core Video
96  *   from invalidating existing caches of the buffer’s contents.
97  *
98  * - Only the first call actually locks; subsequent calls succeed
99  *   as long as their requested flags are compatible with how the buffer
100  *   is already locked.
101  *
102  *   For example, the following code will succeed:
103  *   |[<!-- language="C" -->
104  *   gst_memory_map(plane1, GST_MAP_READWRITE);
105  *   gst_memory_map(plane2, GST_MAP_READ);
106  *   ]|
107  *   while the ƒollowing code will fail:
108  *   |[<!-- language="C" -->
109  *   gst_memory_map(plane1, GST_MAP_READ);
110  *   gst_memory_map(plane2, GST_MAP_READWRITE); /<!-- -->* ERROR: already locked for read-only *<!-- -->/
111  *   ]|
112  *
113  * Returns: %TRUE if the buffer was locked as requested
114  */
115 static gboolean
gst_apple_core_video_pixel_buffer_lock(GstAppleCoreVideoPixelBuffer * gpixbuf,GstMapFlags flags)116 gst_apple_core_video_pixel_buffer_lock (GstAppleCoreVideoPixelBuffer * gpixbuf,
117     GstMapFlags flags)
118 {
119   CVReturn cvret;
120   CVOptionFlags lockFlags;
121 
122   g_mutex_lock (&gpixbuf->mutex);
123 
124   switch (gpixbuf->lock_state) {
125     case GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED:
126       lockFlags = (flags & GST_MAP_WRITE) ? 0 : kCVPixelBufferLock_ReadOnly;
127       cvret = CVPixelBufferLockBaseAddress (gpixbuf->buf, lockFlags);
128       if (cvret != kCVReturnSuccess) {
129         g_mutex_unlock (&gpixbuf->mutex);
130         /* TODO: Map kCVReturnError etc. into strings */
131         GST_ERROR ("%p: unable to lock base address for pixbuf %p: %d", gpixbuf,
132             gpixbuf->buf, cvret);
133         return FALSE;
134       }
135       gpixbuf->lock_state =
136           (flags & GST_MAP_WRITE) ?
137           GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READ_WRITE :
138           GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY;
139       break;
140 
141     case GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY:
142       if (flags & GST_MAP_WRITE) {
143         g_mutex_unlock (&gpixbuf->mutex);
144         GST_ERROR ("%p: pixel buffer %p already locked for read-only access",
145             gpixbuf, gpixbuf->buf);
146         return FALSE;
147       }
148       break;
149 
150     case GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READ_WRITE:
151       break;                    /* nothing to do, already most permissive mapping */
152   }
153 
154   g_atomic_int_inc (&gpixbuf->lock_count);
155 
156   g_mutex_unlock (&gpixbuf->mutex);
157 
158   GST_DEBUG ("%p: pixbuf %p, %s (%d times)",
159       gpixbuf,
160       gpixbuf->buf,
161       _lock_state_names[gpixbuf->lock_state], gpixbuf->lock_count);
162 
163   return TRUE;
164 }
165 
166 /**
167  * gst_apple_core_video_pixel_buffer_unlock:
168  * @gpixbuf: the wrapped CVPixelBuffer
169  *
170  * Unlocks the pixel buffer from CPU memory. Should be called
171  * for every gst_apple_core_video_pixel_buffer_lock() call.
172  */
173 static gboolean
gst_apple_core_video_pixel_buffer_unlock(GstAppleCoreVideoPixelBuffer * gpixbuf)174 gst_apple_core_video_pixel_buffer_unlock (GstAppleCoreVideoPixelBuffer *
175     gpixbuf)
176 {
177   CVOptionFlags lockFlags;
178   CVReturn cvret;
179 
180   if (gpixbuf->lock_state == GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED) {
181     GST_ERROR ("%p: pixel buffer %p not locked", gpixbuf, gpixbuf->buf);
182     return FALSE;
183   }
184 
185   if (!g_atomic_int_dec_and_test (&gpixbuf->lock_count)) {
186     return TRUE;                /* still locked, by current and/or other callers */
187   }
188 
189   g_mutex_lock (&gpixbuf->mutex);
190 
191   lockFlags =
192       (gpixbuf->lock_state ==
193       GST_APPLE_CORE_VIDEO_MEMORY_LOCKED_READONLY) ? kCVPixelBufferLock_ReadOnly
194       : 0;
195   cvret = CVPixelBufferUnlockBaseAddress (gpixbuf->buf, lockFlags);
196   if (cvret != kCVReturnSuccess) {
197     g_mutex_unlock (&gpixbuf->mutex);
198     g_atomic_int_inc (&gpixbuf->lock_count);
199     /* TODO: Map kCVReturnError etc. into strings */
200     GST_ERROR ("%p: unable to unlock base address for pixbuf %p: %d", gpixbuf,
201         gpixbuf->buf, cvret);
202     return FALSE;
203   }
204 
205   gpixbuf->lock_state = GST_APPLE_CORE_VIDEO_MEMORY_UNLOCKED;
206 
207   g_mutex_unlock (&gpixbuf->mutex);
208 
209   GST_DEBUG ("%p: pixbuf %p, %s (%d locks remaining)",
210       gpixbuf,
211       gpixbuf->buf,
212       _lock_state_names[gpixbuf->lock_state], gpixbuf->lock_count);
213 
214   return TRUE;
215 }
216 
217 /*
218  * GstAppleCoreVideoAllocator
219  */
220 
221 struct _GstAppleCoreVideoAllocatorClass
222 {
223   GstAllocatorClass parent_class;
224 };
225 
226 typedef struct _GstAppleCoreVideoAllocatorClass GstAppleCoreVideoAllocatorClass;
227 
228 struct _GstAppleCoreVideoAllocator
229 {
230   GstAllocator parent_instance;
231 };
232 
233 typedef struct _GstAppleCoreVideoAllocator GstAppleCoreVideoAllocator;
234 
235 /* GType for GstAppleCoreVideoAllocator */
236 GType gst_apple_core_video_allocator_get_type (void);
237 #define GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR             (gst_apple_core_video_allocator_get_type())
238 #define GST_IS_APPLE_CORE_VIDEO_ALLOCATOR(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR))
239 #define GST_IS_APPLE_CORE_VIDEO_ALLOCATOR_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR))
240 #define GST_APPLE_CORE_VIDEO_ALLOCATOR_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocatorClass))
241 #define GST_APPLE_CORE_VIDEO_ALLOCATOR(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocator))
242 #define GST_APPLE_CORE_VIDEO_ALLOCATOR_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, GstAppleCoreVideoAllocatorClass))
243 
244 G_DEFINE_TYPE (GstAppleCoreVideoAllocator, gst_apple_core_video_allocator,
245     GST_TYPE_ALLOCATOR);
246 
247 /* Name for allocator registration */
248 #define GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME "AppleCoreVideoMemory"
249 
250 /* Singleton instance of GstAppleCoreVideoAllocator */
251 static GstAppleCoreVideoAllocator *_apple_core_video_allocator;
252 
253 /**
254  * gst_apple_core_video_memory_init:
255  *
256  * Initializes the Core Video Memory allocator. This function must be called
257  * before #GstAppleCoreVideoMemory can be created.
258  *
259  * It is safe to call this function multiple times.
260  */
261 void
gst_apple_core_video_memory_init(void)262 gst_apple_core_video_memory_init (void)
263 {
264   static volatile gsize _init = 0;
265 
266   if (g_once_init_enter (&_init)) {
267     GST_DEBUG_CATEGORY_INIT (GST_CAT_APPLE_CORE_VIDEO_MEMORY, "corevideomemory",
268         0, "Apple Core Video Memory");
269 
270     _apple_core_video_allocator =
271         g_object_new (GST_TYPE_APPLE_CORE_VIDEO_ALLOCATOR, NULL);
272     gst_object_ref_sink (_apple_core_video_allocator);
273 
274     gst_allocator_register (GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME,
275         gst_object_ref (_apple_core_video_allocator));
276     g_once_init_leave (&_init, 1);
277   }
278 }
279 
280 /**
281  * gst_is_apple_core_video_memory:
282  * @mem: #GstMemory
283  *
284  * Checks whether @mem is backed by a CVPixelBuffer.
285  * This has limited use since #GstAppleCoreVideoMemory is transparently
286  * mapped into CPU memory on request.
287  *
288  * Returns: %TRUE when @mem is backed by a CVPixelBuffer
289  */
290 gboolean
gst_is_apple_core_video_memory(GstMemory * mem)291 gst_is_apple_core_video_memory (GstMemory * mem)
292 {
293   g_return_val_if_fail (mem != NULL, FALSE);
294 
295   return GST_IS_APPLE_CORE_VIDEO_ALLOCATOR (mem->allocator);
296 }
297 
298 /**
299  * gst_apple_core_video_memory_new:
300  *
301  * Helper function for gst_apple_core_video_mem_share().
302  * Users should call gst_apple_core_video_memory_new_wrapped() instead.
303  */
304 static GstAppleCoreVideoMemory *
gst_apple_core_video_memory_new(GstMemoryFlags flags,GstMemory * parent,GstAppleCoreVideoPixelBuffer * gpixbuf,gsize plane,gsize maxsize,gsize align,gsize offset,gsize size)305 gst_apple_core_video_memory_new (GstMemoryFlags flags, GstMemory * parent,
306     GstAppleCoreVideoPixelBuffer * gpixbuf, gsize plane, gsize maxsize,
307     gsize align, gsize offset, gsize size)
308 {
309   GstAppleCoreVideoMemory *mem;
310 
311   g_return_val_if_fail (gpixbuf != NULL, NULL);
312 
313   mem = g_slice_new0 (GstAppleCoreVideoMemory);
314   gst_memory_init (GST_MEMORY_CAST (mem), flags,
315       GST_ALLOCATOR_CAST (_apple_core_video_allocator), parent, maxsize, align,
316       offset, size);
317 
318   mem->gpixbuf = gst_apple_core_video_pixel_buffer_ref (gpixbuf);
319   mem->plane = plane;
320 
321   GST_DEBUG ("%p: gpixbuf %p, plane: %" G_GSSIZE_FORMAT ", size %"
322       G_GSIZE_FORMAT, mem, mem->gpixbuf, mem->plane, mem->mem.size);
323 
324   return mem;
325 }
326 
327 /**
328  * gst_apple_core_video_memory_new_wrapped:
329  * @gpixbuf: the backing #GstAppleCoreVideoPixelBuffer
330  * @plane: the plane this memory will represent, or 0 for non-planar buffer
331  * @size: the size of the buffer or specific plane
332  *
333  * Returns: a newly allocated #GstAppleCoreVideoMemory
334  */
335 GstAppleCoreVideoMemory *
gst_apple_core_video_memory_new_wrapped(GstAppleCoreVideoPixelBuffer * gpixbuf,gsize plane,gsize size)336 gst_apple_core_video_memory_new_wrapped (GstAppleCoreVideoPixelBuffer * gpixbuf,
337     gsize plane, gsize size)
338 {
339   return gst_apple_core_video_memory_new (0, NULL, gpixbuf, plane, size, 0, 0,
340       size);
341 }
342 
343 static gpointer
gst_apple_core_video_mem_map(GstMemory * gmem,gsize maxsize,GstMapFlags flags)344 gst_apple_core_video_mem_map (GstMemory * gmem, gsize maxsize,
345     GstMapFlags flags)
346 {
347   GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem;
348   gpointer ret;
349 
350   if (!gst_apple_core_video_pixel_buffer_lock (mem->gpixbuf, flags))
351     return NULL;
352 
353   if (CVPixelBufferIsPlanar (mem->gpixbuf->buf)) {
354     ret = CVPixelBufferGetBaseAddressOfPlane (mem->gpixbuf->buf, mem->plane);
355 
356     if (ret != NULL)
357       GST_DEBUG ("%p: pixbuf %p plane %" G_GSIZE_FORMAT
358           " flags %08x: mapped %p", mem, mem->gpixbuf->buf, mem->plane, flags,
359           ret);
360     else
361       GST_ERROR ("%p: invalid plane base address (NULL) for pixbuf %p plane %"
362           G_GSIZE_FORMAT, mem, mem->gpixbuf->buf, mem->plane);
363   } else {
364     ret = CVPixelBufferGetBaseAddress (mem->gpixbuf->buf);
365 
366     if (ret != NULL)
367       GST_DEBUG ("%p: pixbuf %p flags %08x: mapped %p", mem, mem->gpixbuf->buf,
368           flags, ret);
369     else
370       GST_ERROR ("%p: invalid base address (NULL) for pixbuf %p"
371           G_GSIZE_FORMAT, mem, mem->gpixbuf->buf);
372   }
373 
374   return ret;
375 }
376 
377 static void
gst_apple_core_video_mem_unmap(GstMemory * gmem)378 gst_apple_core_video_mem_unmap (GstMemory * gmem)
379 {
380   GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem;
381   (void) gst_apple_core_video_pixel_buffer_unlock (mem->gpixbuf);
382   GST_DEBUG ("%p: pixbuf %p plane %" G_GSIZE_FORMAT, mem,
383       mem->gpixbuf->buf, mem->plane);
384 }
385 
386 static GstMemory *
gst_apple_core_video_mem_share(GstMemory * gmem,gssize offset,gssize size)387 gst_apple_core_video_mem_share (GstMemory * gmem, gssize offset, gssize size)
388 {
389   GstAppleCoreVideoMemory *mem;
390   GstMemory *parent, *sub;
391 
392   mem = (GstAppleCoreVideoMemory *) gmem;
393 
394   /* find the real parent */
395   parent = gmem->parent;
396   if (parent == NULL)
397     parent = gmem;
398 
399   if (size == -1)
400     size = gmem->size - offset;
401 
402   /* the shared memory is always readonly */
403   sub =
404       GST_MEMORY_CAST (gst_apple_core_video_memory_new (GST_MINI_OBJECT_FLAGS
405           (parent) | GST_MINI_OBJECT_FLAG_LOCK_READONLY, parent, mem->gpixbuf,
406           mem->plane, gmem->maxsize, gmem->align, gmem->offset + offset, size));
407 
408   return sub;
409 }
410 
411 static gboolean
gst_apple_core_video_mem_is_span(GstMemory * mem1,GstMemory * mem2,gsize * offset)412 gst_apple_core_video_mem_is_span (GstMemory * mem1, GstMemory * mem2,
413     gsize * offset)
414 {
415   /* We may only return FALSE since:
416    * 1) Core Video gives no guarantees about planes being consecutive.
417    *    We may only know this after mapping.
418    * 2) GstAppleCoreVideoMemory instances for planes do not share a common
419    *    parent -- i.e. they're not offsets into the same parent
420    *    memory instance.
421    *
422    * It's not unlikely that planes will be stored in consecutive memory
423    * but it should be checked by the user after mapping.
424    */
425   return FALSE;
426 }
427 
428 static void
gst_apple_core_video_mem_free(GstAllocator * allocator,GstMemory * gmem)429 gst_apple_core_video_mem_free (GstAllocator * allocator, GstMemory * gmem)
430 {
431   GstAppleCoreVideoMemory *mem = (GstAppleCoreVideoMemory *) gmem;
432 
433   gst_apple_core_video_pixel_buffer_unref (mem->gpixbuf);
434 
435   g_slice_free (GstAppleCoreVideoMemory, mem);
436 }
437 
438 static void
gst_apple_core_video_allocator_class_init(GstAppleCoreVideoAllocatorClass * klass)439 gst_apple_core_video_allocator_class_init (GstAppleCoreVideoAllocatorClass *
440     klass)
441 {
442   GstAllocatorClass *allocator_class;
443 
444   allocator_class = (GstAllocatorClass *) klass;
445 
446   /* we don't do allocations, only wrap existing pixel buffers */
447   allocator_class->alloc = NULL;
448   allocator_class->free = gst_apple_core_video_mem_free;
449 }
450 
451 static void
gst_apple_core_video_allocator_init(GstAppleCoreVideoAllocator * allocator)452 gst_apple_core_video_allocator_init (GstAppleCoreVideoAllocator * allocator)
453 {
454   GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
455 
456   alloc->mem_type = GST_APPLE_CORE_VIDEO_ALLOCATOR_NAME;
457   alloc->mem_map = gst_apple_core_video_mem_map;
458   alloc->mem_unmap = gst_apple_core_video_mem_unmap;
459   alloc->mem_share = gst_apple_core_video_mem_share;
460   alloc->mem_is_span = gst_apple_core_video_mem_is_span;
461 
462   GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
463 }
464