1 /*
2  * Copyright (C) 2009 Ole André Vadla Ravnås <oleavr@soundrop.com>
3  * Copyright (C) 2014 Collabora Ltd.
4  *   Authors:    Matthieu Bouron <matthieu.bouron@collabora.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "corevideobuffer.h"
23 #include "coremediabuffer.h"
24 #include "corevideomemory.h"
25 
26 static const GstMetaInfo *gst_core_media_meta_get_info (void);
27 
28 static void
gst_core_media_meta_add(GstBuffer * buffer,CMSampleBufferRef sample_buf,CVImageBufferRef image_buf,CMBlockBufferRef block_buf)29 gst_core_media_meta_add (GstBuffer * buffer, CMSampleBufferRef sample_buf,
30     CVImageBufferRef image_buf, CMBlockBufferRef block_buf)
31 {
32   GstCoreMediaMeta *meta;
33 
34   meta =
35       (GstCoreMediaMeta *) gst_buffer_add_meta (buffer,
36       gst_core_media_meta_get_info (), NULL);
37   CFRetain (sample_buf);
38   if (image_buf)
39     CVBufferRetain (image_buf);
40   if (block_buf)
41     CFRetain (block_buf);
42   meta->sample_buf = sample_buf;
43   meta->image_buf = image_buf;
44   meta->block_buf = block_buf;
45   if (image_buf != NULL && CFGetTypeID (image_buf) == CVPixelBufferGetTypeID ())
46     meta->pixel_buf = (CVPixelBufferRef) image_buf;
47   else
48     meta->pixel_buf = NULL;
49 }
50 
51 static gboolean
gst_core_media_meta_init(GstCoreMediaMeta * meta,gpointer params,GstBuffer * buf)52 gst_core_media_meta_init (GstCoreMediaMeta * meta, gpointer params,
53     GstBuffer * buf)
54 {
55   meta->sample_buf = NULL;
56   meta->image_buf = NULL;
57   meta->pixel_buf = NULL;
58   meta->block_buf = NULL;
59 
60   return TRUE;
61 }
62 
63 static void
gst_core_media_meta_free(GstCoreMediaMeta * meta,GstBuffer * buf)64 gst_core_media_meta_free (GstCoreMediaMeta * meta, GstBuffer * buf)
65 {
66   if (meta->image_buf != NULL) {
67     CVBufferRelease (meta->image_buf);
68   }
69 
70   if (meta->block_buf != NULL) {
71     CFRelease (meta->block_buf);
72   }
73 
74   CFRelease (meta->sample_buf);
75 }
76 
77 static gboolean
gst_core_media_meta_transform(GstBuffer * transbuf,GstCoreMediaMeta * meta,GstBuffer * buffer,GQuark type,GstMetaTransformCopy * data)78 gst_core_media_meta_transform (GstBuffer * transbuf, GstCoreMediaMeta * meta,
79     GstBuffer * buffer, GQuark type, GstMetaTransformCopy * data)
80 {
81   if (!data->region) {
82     /* only copy if the complete data is copied as well */
83     gst_core_media_meta_add (transbuf, meta->sample_buf, meta->image_buf,
84         meta->block_buf);
85   } else {
86     GST_WARNING_OBJECT (transbuf,
87         "dropping Core Media metadata due to partial buffer");
88   }
89 
90   return TRUE;                  /* retval unused */
91 }
92 
93 GType
gst_core_media_meta_api_get_type(void)94 gst_core_media_meta_api_get_type (void)
95 {
96   static volatile GType type;
97   static const gchar *tags[] = { "memory", NULL };
98 
99   if (g_once_init_enter (&type)) {
100     GType _type = gst_meta_api_type_register ("GstCoreMediaMetaAPI", tags);
101     g_once_init_leave (&type, _type);
102   }
103   return type;
104 }
105 
106 static const GstMetaInfo *
gst_core_media_meta_get_info(void)107 gst_core_media_meta_get_info (void)
108 {
109   static const GstMetaInfo *core_media_meta_info = NULL;
110 
111   if (g_once_init_enter (&core_media_meta_info)) {
112     const GstMetaInfo *meta = gst_meta_register (GST_CORE_MEDIA_META_API_TYPE,
113         "GstCoreMediaMeta", sizeof (GstCoreMediaMeta),
114         (GstMetaInitFunction) gst_core_media_meta_init,
115         (GstMetaFreeFunction) gst_core_media_meta_free,
116         (GstMetaTransformFunction) gst_core_media_meta_transform);
117     g_once_init_leave (&core_media_meta_info, meta);
118   }
119   return core_media_meta_info;
120 }
121 
122 static GstVideoFormat
gst_core_media_buffer_get_video_format(OSType format)123 gst_core_media_buffer_get_video_format (OSType format)
124 {
125   switch (format) {
126     case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
127       return GST_VIDEO_FORMAT_NV12;
128     case kCVPixelFormatType_422YpCbCr8_yuvs:
129       return GST_VIDEO_FORMAT_YUY2;
130     case kCVPixelFormatType_422YpCbCr8:
131       return GST_VIDEO_FORMAT_UYVY;
132     case kCVPixelFormatType_32BGRA:
133       return GST_VIDEO_FORMAT_BGRA;
134     case kCVPixelFormatType_32RGBA:
135       return GST_VIDEO_FORMAT_RGBA;
136     default:
137       GST_WARNING ("Unknown OSType format: %d", (gint) format);
138       return GST_VIDEO_FORMAT_UNKNOWN;
139   }
140 }
141 
142 static gboolean
gst_core_media_buffer_wrap_block_buffer(GstBuffer * buf,CMBlockBufferRef block_buf)143 gst_core_media_buffer_wrap_block_buffer (GstBuffer * buf,
144     CMBlockBufferRef block_buf)
145 {
146   OSStatus status;
147   gchar *data = NULL;
148   size_t offset = 0, length_at_offset, total_length;
149 
150   /* CMBlockBuffer can contain multiple non-continuous memory blocks */
151   do {
152     status =
153         CMBlockBufferGetDataPointer (block_buf, offset, &length_at_offset,
154         &total_length, &data);
155     if (status != kCMBlockBufferNoErr) {
156       return FALSE;
157     }
158 
159     /* retaining the CMBlockBuffer so it won't go away for the lifetime of the GstMemory */
160     gst_buffer_append_memory (buf,
161         gst_memory_new_wrapped (0, data, length_at_offset, 0, length_at_offset,
162             (gpointer) CFRetain (block_buf), (GDestroyNotify) CFRelease));
163 
164     offset += length_at_offset;
165   } while (offset < total_length);
166 
167   return TRUE;
168 }
169 
170 static GstBuffer *
gst_core_media_buffer_new_from_buffer(GstBuffer * buf,GstVideoInfo * info)171 gst_core_media_buffer_new_from_buffer (GstBuffer * buf, GstVideoInfo * info)
172 {
173   gboolean ret;
174   GstBuffer *copy_buf;
175   GstVideoFrame dest, src;
176   GstAllocator *allocator;
177 
178   allocator = gst_allocator_find (GST_ALLOCATOR_SYSMEM);
179   if (!allocator) {
180     GST_ERROR ("Could not find SYSMEM allocator");
181     return NULL;
182   }
183 
184   copy_buf = gst_buffer_new_allocate (allocator, info->size, NULL);
185 
186   gst_object_unref (allocator);
187 
188   if (!gst_video_frame_map (&dest, info, copy_buf, GST_MAP_WRITE)) {
189     GST_ERROR ("Could not map destination frame");
190     goto error;
191   }
192 
193   if (!gst_video_frame_map (&src, info, buf, GST_MAP_READ)) {
194     GST_ERROR ("Could not map source frame");
195     gst_video_frame_unmap (&dest);
196     goto error;
197   }
198 
199   ret = gst_video_frame_copy (&dest, &src);
200 
201   gst_video_frame_unmap (&dest);
202   gst_video_frame_unmap (&src);
203 
204   if (!ret) {
205     GST_ERROR ("Could not copy frame");
206     goto error;
207   }
208 
209   return copy_buf;
210 
211 error:
212   if (copy_buf) {
213     gst_buffer_unref (copy_buf);
214   }
215   return NULL;
216 }
217 
218 static gboolean
gst_video_info_init_from_pixel_buffer(GstVideoInfo * info,CVPixelBufferRef pixel_buf)219 gst_video_info_init_from_pixel_buffer (GstVideoInfo * info,
220     CVPixelBufferRef pixel_buf)
221 {
222   size_t width, height;
223   OSType format_type;
224   GstVideoFormat video_format;
225 
226   width = CVPixelBufferGetWidth (pixel_buf);
227   height = CVPixelBufferGetHeight (pixel_buf);
228   format_type = CVPixelBufferGetPixelFormatType (pixel_buf);
229   video_format = gst_core_media_buffer_get_video_format (format_type);
230 
231   if (video_format == GST_VIDEO_FORMAT_UNKNOWN) {
232     return FALSE;
233   }
234 
235   gst_video_info_init (info);
236   gst_video_info_set_format (info, video_format, width, height);
237 
238   return TRUE;
239 }
240 
241 GstBuffer *
gst_core_media_buffer_new(CMSampleBufferRef sample_buf,gboolean use_video_meta,GstVideoTextureCache * cache)242 gst_core_media_buffer_new (CMSampleBufferRef sample_buf,
243     gboolean use_video_meta, GstVideoTextureCache * cache)
244 {
245   CVImageBufferRef image_buf;
246   CMBlockBufferRef block_buf;
247   GstBuffer *buf;
248 
249   image_buf = CMSampleBufferGetImageBuffer (sample_buf);
250   block_buf = CMSampleBufferGetDataBuffer (sample_buf);
251 
252   buf = gst_buffer_new ();
253 
254   gst_core_media_meta_add (buf, sample_buf, image_buf, block_buf);
255 
256   if (image_buf != NULL && CFGetTypeID (image_buf) == CVPixelBufferGetTypeID ()) {
257     GstVideoInfo info;
258     gboolean has_padding = FALSE;
259     CVPixelBufferRef pixel_buf = (CVPixelBufferRef) image_buf;
260 
261     if (!gst_video_info_init_from_pixel_buffer (&info, pixel_buf)) {
262       goto error;
263     }
264 
265     gst_core_video_wrap_pixel_buffer (buf, &info, pixel_buf, cache,
266         &has_padding);
267 
268     /* If the video meta API is not supported, remove padding by
269      * copying the core media buffer to a system memory buffer */
270     if (has_padding && !use_video_meta) {
271       GstBuffer *copy_buf;
272       copy_buf = gst_core_media_buffer_new_from_buffer (buf, &info);
273       if (!copy_buf) {
274         goto error;
275       }
276 
277       gst_buffer_unref (buf);
278       buf = copy_buf;
279     }
280 
281   } else if (block_buf != NULL) {
282     if (!gst_core_media_buffer_wrap_block_buffer (buf, block_buf)) {
283       goto error;
284     }
285   } else {
286     goto error;
287   }
288 
289   return buf;
290 
291 error:
292   if (buf) {
293     gst_buffer_unref (buf);
294   }
295   return NULL;
296 }
297 
298 CVPixelBufferRef
gst_core_media_buffer_get_pixel_buffer(GstBuffer * buf)299 gst_core_media_buffer_get_pixel_buffer (GstBuffer * buf)
300 {
301   GstCoreMediaMeta *meta = (GstCoreMediaMeta *) gst_buffer_get_meta (buf,
302       GST_CORE_MEDIA_META_API_TYPE);
303   g_return_val_if_fail (meta != NULL, NULL);
304 
305   return CVPixelBufferRetain (meta->pixel_buf);
306 }
307