audiotestsrc: fix a comment typo from previous commit
[platform/upstream/gstreamer.git] / sys / xvimage / xvimageallocator.c
1 /* GStreamer
2  * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
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 more details.
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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #ifdef HAVE_XSHM
25 #include <sys/types.h>
26 #include <sys/ipc.h>
27 #include <sys/shm.h>
28 #endif /* HAVE_XSHM */
29
30 #include <X11/Xlib.h>
31 #include <X11/Xutil.h>
32
33 #ifdef HAVE_XSHM
34 #include <X11/extensions/XShm.h>
35 #endif /* HAVE_XSHM */
36
37 #include <string.h>
38 #include <math.h>
39
40 /* Object header */
41 #include "xvimageallocator.h"
42
43 /* Debugging category */
44 #include <gst/gstinfo.h>
45
46 /* Helper functions */
47 #include <gst/video/video.h>
48
49
50 GST_DEBUG_CATEGORY_STATIC (gst_debug_xvimageallocator);
51 #define GST_CAT_DEFAULT gst_debug_xvimageallocator
52
53 struct _GstXvImageMemory
54 {
55   GstMemory parent;
56
57   gint im_format;
58   GstVideoRectangle crop;
59
60   XvImage *xvimage;
61
62 #ifdef HAVE_XSHM
63   XShmSegmentInfo SHMInfo;
64 #endif                          /* HAVE_XSHM */
65 };
66
67
68 struct _GstXvImageAllocator
69 {
70   GstAllocator parent;
71
72   GstXvContext *context;
73 };
74
75 struct _GstXvImageAllocatorClass
76 {
77   GstAllocatorClass parent_class;
78 };
79
80 gboolean
81 gst_xvimage_memory_is_from_context (GstMemory * mem, GstXvContext * context)
82 {
83   GstXvImageAllocator *alloc;
84
85   if (!GST_IS_XVIMAGE_ALLOCATOR (mem->allocator))
86     return FALSE;
87
88   alloc = GST_XVIMAGE_ALLOCATOR_CAST (mem->allocator);
89
90   if (alloc->context != context)
91     return FALSE;
92
93   return TRUE;
94 }
95
96 gint
97 gst_xvimage_memory_get_format (GstXvImageMemory * xvmem)
98 {
99   g_return_val_if_fail (xvmem != NULL, FALSE);
100
101   return xvmem->im_format;
102 }
103
104 XvImage *
105 gst_xvimage_memory_get_xvimage (GstXvImageMemory * xvmem)
106 {
107   g_return_val_if_fail (xvmem != NULL, FALSE);
108
109   return xvmem->xvimage;
110 }
111
112 gboolean
113 gst_xvimage_memory_get_crop (GstXvImageMemory * xvmem, GstVideoRectangle * crop)
114 {
115   g_return_val_if_fail (xvmem != NULL, FALSE);
116
117   if (crop)
118     *crop = xvmem->crop;
119
120   return TRUE;
121 }
122
123
124 /* X11 stuff */
125 static gboolean error_caught = FALSE;
126
127 static int
128 gst_xvimage_handle_xerror (Display * display, XErrorEvent * xevent)
129 {
130   char error_msg[1024];
131
132   XGetErrorText (display, xevent->error_code, error_msg, 1024);
133   GST_DEBUG ("xvimage triggered an XError. error: %s", error_msg);
134   error_caught = TRUE;
135   return 0;
136 }
137
138 static GstMemory *
139 gst_xvimage_allocator_dummy_alloc (GstAllocator * allocator, gsize size,
140     GstAllocationParams * params)
141 {
142   return NULL;
143 }
144
145 static void
146 gst_xvimage_allocator_free (GstAllocator * allocator, GstMemory * gmem)
147 {
148   GstXvImageMemory *mem = (GstXvImageMemory *) gmem;
149   GstXvImageAllocator *alloc = (GstXvImageAllocator *) allocator;
150   GstXvContext *context;
151
152   if (gmem->parent)
153     goto sub_mem;
154
155   context = alloc->context;
156
157   GST_DEBUG_OBJECT (allocator, "free memory %p", mem);
158
159   g_mutex_lock (&context->lock);
160
161 #ifdef HAVE_XSHM
162   if (context->use_xshm) {
163     if (mem->SHMInfo.shmaddr != ((void *) -1)) {
164       GST_DEBUG_OBJECT (allocator, "XServer ShmDetaching from 0x%x id 0x%lx",
165           mem->SHMInfo.shmid, mem->SHMInfo.shmseg);
166       XShmDetach (context->disp, &mem->SHMInfo);
167       XSync (context->disp, FALSE);
168       shmdt (mem->SHMInfo.shmaddr);
169       mem->SHMInfo.shmaddr = (void *) -1;
170     }
171     if (mem->xvimage)
172       XFree (mem->xvimage);
173   } else
174 #endif /* HAVE_XSHM */
175   {
176     if (mem->xvimage) {
177       g_free (mem->xvimage->data);
178       XFree (mem->xvimage);
179     }
180   }
181
182   XSync (context->disp, FALSE);
183
184   g_mutex_unlock (&context->lock);
185
186 sub_mem:
187   g_slice_free (GstXvImageMemory, mem);
188 }
189
190 static gpointer
191 gst_xvimage_memory_map (GstXvImageMemory * mem, gsize maxsize,
192     GstMapFlags flags)
193 {
194   return mem->xvimage->data + mem->parent.offset;
195 }
196
197 static gboolean
198 gst_xvimage_memory_unmap (GstXvImageMemory * mem)
199 {
200   return TRUE;
201 }
202
203 static GstXvImageMemory *
204 gst_xvimage_memory_share (GstXvImageMemory * mem, gssize offset, gsize size)
205 {
206   GstXvImageMemory *sub;
207   GstMemory *parent;
208
209   /* We can only share the complete memory */
210   if (offset != 0)
211     return NULL;
212   if (size != -1 && size != mem->xvimage->data_size)
213     return NULL;
214
215   GST_DEBUG ("share memory %p", mem);
216
217   /* find the real parent */
218   if ((parent = mem->parent.parent) == NULL)
219     parent = (GstMemory *) mem;
220
221   if (size == -1)
222     size = mem->parent.size - offset;
223
224   /* the shared memory is always readonly */
225   sub = g_slice_new (GstXvImageMemory);
226
227   gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
228       GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->parent.allocator,
229       &mem->parent, mem->parent.maxsize, mem->parent.align,
230       mem->parent.offset + offset, size);
231
232   sub->im_format = mem->im_format;
233   sub->crop = mem->crop;
234   sub->xvimage = mem->xvimage;
235 #ifdef HAVE_XSHM
236   sub->SHMInfo = mem->SHMInfo;
237 #endif
238
239   return sub;
240 }
241
242 static GstXvImageMemory *
243 gst_xvimage_memory_copy (GstMemory * gmem, gssize offset, gsize size)
244 {
245   GstXvImageMemory *mem, *copy;
246
247   mem = (GstXvImageMemory *) gmem;
248
249   /* We can only copy the complete memory */
250   if (offset != 0)
251     return NULL;
252   if (size != -1 && size != mem->xvimage->data_size)
253     return NULL;
254
255   GST_DEBUG ("copy memory %p", mem);
256
257   copy = (GstXvImageMemory *)
258       gst_xvimage_allocator_alloc (GST_XVIMAGE_ALLOCATOR_CAST (gmem->allocator),
259       mem->im_format, mem->xvimage->width, mem->xvimage->height, &mem->crop,
260       NULL);
261
262   memcpy (copy->xvimage->data + copy->parent.offset,
263       mem->xvimage->data + mem->parent.offset, mem->xvimage->data_size);
264
265   return copy;
266 }
267
268 #define gst_xvimage_allocator_parent_class parent_class
269 G_DEFINE_TYPE (GstXvImageAllocator, gst_xvimage_allocator, GST_TYPE_ALLOCATOR);
270
271 static void gst_xvimage_allocator_finalize (GObject * object);
272
273 #define GST_XVIMAGE_ALLOCATOR_NAME "xvimage"
274
275 static void
276 gst_xvimage_allocator_class_init (GstXvImageAllocatorClass * klass)
277 {
278   GObjectClass *gobject_class;
279   GstAllocatorClass *allocator_class;
280
281   gobject_class = (GObjectClass *) klass;
282   allocator_class = (GstAllocatorClass *) klass;
283
284   gobject_class->finalize = gst_xvimage_allocator_finalize;
285
286   allocator_class->alloc = gst_xvimage_allocator_dummy_alloc;
287   allocator_class->free = gst_xvimage_allocator_free;
288
289   GST_DEBUG_CATEGORY_INIT (gst_debug_xvimageallocator, "xvimageallocator", 0,
290       "xvimageallocator object");
291 }
292
293 static void
294 gst_xvimage_allocator_init (GstXvImageAllocator * allocator)
295 {
296   GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
297
298   alloc->mem_type = GST_XVIMAGE_ALLOCATOR_NAME;
299   alloc->mem_map = (GstMemoryMapFunction) gst_xvimage_memory_map;
300   alloc->mem_unmap = (GstMemoryUnmapFunction) gst_xvimage_memory_unmap;
301   alloc->mem_share = (GstMemoryShareFunction) gst_xvimage_memory_share;
302   alloc->mem_copy = (GstMemoryShareFunction) gst_xvimage_memory_copy;
303   /* fallback is_span */
304
305   GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
306 }
307
308 static void
309 gst_xvimage_allocator_finalize (GObject * object)
310 {
311   GstXvImageAllocator *alloc = GST_XVIMAGE_ALLOCATOR (object);
312
313   GST_DEBUG_OBJECT (object, "finalize");
314
315   gst_xvcontext_unref (alloc->context);
316
317   G_OBJECT_CLASS (parent_class)->finalize (object);
318 }
319
320 GstXvImageAllocator *
321 gst_xvimage_allocator_new (GstXvContext * context)
322 {
323   GstXvImageAllocator *alloc;
324
325   g_return_val_if_fail (GST_IS_XVCONTEXT (context), NULL);
326
327   alloc = g_object_new (GST_TYPE_XVIMAGE_ALLOCATOR, NULL);
328   alloc->context = gst_xvcontext_ref (context);
329
330   return alloc;
331 }
332
333 GstXvContext *
334 gst_xvimage_allocator_peek_context (GstXvImageAllocator * allocator)
335 {
336   g_return_val_if_fail (GST_IS_XVIMAGE_ALLOCATOR (allocator), NULL);
337
338   return allocator->context;
339 }
340
341 GstMemory *
342 gst_xvimage_allocator_alloc (GstXvImageAllocator * allocator, gint im_format,
343     gint padded_width, gint padded_height, GstVideoRectangle * crop,
344     GError ** error)
345 {
346   int (*handler) (Display *, XErrorEvent *);
347   gboolean success = FALSE;
348   GstXvContext *context;
349   gint align = 15, offset;
350   GstXvImageMemory *mem;
351
352   context = allocator->context;
353
354   mem = g_slice_new (GstXvImageMemory);
355
356   mem->im_format = im_format;
357 #ifdef HAVE_XSHM
358   mem->SHMInfo.shmaddr = ((void *) -1);
359   mem->SHMInfo.shmid = -1;
360 #endif
361   mem->crop = *crop;
362
363   GST_DEBUG_OBJECT (allocator, "creating image %p (%dx%d) cropped %dx%d-%dx%d",
364       mem, padded_width, padded_height, crop->x, crop->y, crop->w, crop->h);
365
366   g_mutex_lock (&context->lock);
367
368   /* Setting an error handler to catch failure */
369   error_caught = FALSE;
370   handler = XSetErrorHandler (gst_xvimage_handle_xerror);
371
372 #ifdef HAVE_XSHM
373   if (context->use_xshm) {
374     int expected_size;
375
376     mem->xvimage = XvShmCreateImage (context->disp,
377         context->xv_port_id, im_format, NULL, padded_width, padded_height,
378         &mem->SHMInfo);
379     if (!mem->xvimage || error_caught) {
380       g_mutex_unlock (&context->lock);
381
382       /* Reset error flag */
383       error_caught = FALSE;
384
385       /* Push a warning */
386       GST_WARNING_OBJECT (allocator,
387           "could not XShmCreateImage a %dx%d image", padded_width,
388           padded_height);
389
390       /* Retry without XShm */
391       context->use_xshm = FALSE;
392
393       /* Hold X mutex again to try without XShm */
394       g_mutex_lock (&context->lock);
395       goto no_xshm;
396     }
397
398     /* we have to use the returned data_size for our shm size */
399     GST_LOG_OBJECT (allocator, "XShm image size is %d",
400         mem->xvimage->data_size);
401
402     /* calculate the expected size.  This is only for sanity checking the
403      * number we get from X. */
404     switch (im_format) {
405       case GST_MAKE_FOURCC ('I', '4', '2', '0'):
406       case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):
407       {
408         gint pitches[3];
409         gint offsets[3];
410         guint plane;
411
412         offsets[0] = 0;
413         pitches[0] = GST_ROUND_UP_4 (padded_width);
414         offsets[1] = offsets[0] + pitches[0] * GST_ROUND_UP_2 (padded_height);
415         pitches[1] = GST_ROUND_UP_8 (padded_width) / 2;
416         offsets[2] =
417             offsets[1] + pitches[1] * GST_ROUND_UP_2 (padded_height) / 2;
418         pitches[2] = GST_ROUND_UP_8 (pitches[0]) / 2;
419
420         expected_size =
421             offsets[2] + pitches[2] * GST_ROUND_UP_2 (padded_height) / 2;
422
423         for (plane = 0; plane < mem->xvimage->num_planes; plane++) {
424           GST_DEBUG_OBJECT (allocator,
425               "Plane %u has a expected pitch of %d bytes, " "offset of %d",
426               plane, pitches[plane], offsets[plane]);
427         }
428         break;
429       }
430       case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
431       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
432         expected_size = padded_height * GST_ROUND_UP_4 (padded_width * 2);
433         break;
434       default:
435         expected_size = 0;
436         break;
437     }
438     if (expected_size != 0 && mem->xvimage->data_size != expected_size) {
439       GST_WARNING_OBJECT (allocator,
440           "unexpected XShm image size (got %" G_GSIZE_FORMAT ", expected %d)",
441           mem->xvimage->data_size, expected_size);
442     }
443
444     /* Be verbose about our XvImage stride */
445     {
446       guint plane;
447
448       for (plane = 0; plane < mem->xvimage->num_planes; plane++) {
449         GST_DEBUG_OBJECT (allocator, "Plane %u has a pitch of %d bytes, "
450             "offset of %d", plane, mem->xvimage->pitches[plane],
451             mem->xvimage->offsets[plane]);
452       }
453     }
454
455     /* get shared memory */
456     mem->SHMInfo.shmid =
457         shmget (IPC_PRIVATE, mem->xvimage->data_size + align, IPC_CREAT | 0777);
458     if (mem->SHMInfo.shmid == -1)
459       goto shmget_failed;
460
461     /* attach */
462     mem->SHMInfo.shmaddr = shmat (mem->SHMInfo.shmid, NULL, 0);
463     if (mem->SHMInfo.shmaddr == ((void *) -1))
464       goto shmat_failed;
465
466     /* now we can set up the image data */
467     mem->xvimage->data = mem->SHMInfo.shmaddr;
468     mem->SHMInfo.readOnly = FALSE;
469
470     if (XShmAttach (context->disp, &mem->SHMInfo) == 0)
471       goto xattach_failed;
472
473     XSync (context->disp, FALSE);
474
475     /* Delete the shared memory segment as soon as we everyone is attached.
476      * This way, it will be deleted as soon as we detach later, and not
477      * leaked if we crash. */
478     shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
479
480     GST_DEBUG_OBJECT (allocator, "XServer ShmAttached to 0x%x, id 0x%lx",
481         mem->SHMInfo.shmid, mem->SHMInfo.shmseg);
482   } else
483   no_xshm:
484 #endif /* HAVE_XSHM */
485   {
486     mem->xvimage = XvCreateImage (context->disp,
487         context->xv_port_id, im_format, NULL, padded_width, padded_height);
488     if (!mem->xvimage || error_caught)
489       goto create_failed;
490
491     /* we have to use the returned data_size for our image size */
492     mem->xvimage->data = g_malloc (mem->xvimage->data_size + align);
493
494     XSync (context->disp, FALSE);
495   }
496
497   if ((offset = ((guintptr) mem->xvimage->data & align)))
498     offset = (align + 1) - offset;
499
500   GST_DEBUG_OBJECT (allocator, "memory %p, align %d, offset %d",
501       mem->xvimage->data, align, offset);
502
503   /* Reset error handler */
504   error_caught = FALSE;
505   XSetErrorHandler (handler);
506
507   gst_memory_init (GST_MEMORY_CAST (mem), 0,
508       GST_ALLOCATOR_CAST (allocator), NULL, mem->xvimage->data_size + align,
509       align, offset, mem->xvimage->data_size);
510
511   g_mutex_unlock (&context->lock);
512
513   success = TRUE;
514
515 beach:
516   if (!success) {
517     g_slice_free (GstXvImageMemory, mem);
518     mem = NULL;
519   }
520
521   return GST_MEMORY_CAST (mem);
522
523   /* ERRORS */
524 create_failed:
525   {
526     g_mutex_unlock (&context->lock);
527     /* Reset error handler */
528     error_caught = FALSE;
529     XSetErrorHandler (handler);
530     /* Push an error */
531     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
532         "could not XvShmCreateImage a %dx%d image", padded_width,
533         padded_height);
534     goto beach;
535   }
536 #ifdef HAVE_XSHM
537 shmget_failed:
538   {
539     g_mutex_unlock (&context->lock);
540     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
541         "could not get shared memory of %d bytes", mem->xvimage->data_size);
542     goto beach;
543   }
544 shmat_failed:
545   {
546     g_mutex_unlock (&context->lock);
547     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
548         "Failed to shmat: %s", g_strerror (errno));
549     /* Clean up the shared memory segment */
550     shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
551     goto beach;
552   }
553 xattach_failed:
554   {
555     /* Clean up the shared memory segment */
556     shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
557     g_mutex_unlock (&context->lock);
558
559     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
560         "Failed to XShmAttach");
561     goto beach;
562   }
563 #endif
564 }
565
566 /* We are called with the x_lock taken */
567 static void
568 gst_xwindow_draw_borders (GstXWindow * window, GstVideoRectangle * rect)
569 {
570   gint t1, t2;
571   GstXvContext *context;
572
573   g_return_if_fail (window != NULL);
574   g_return_if_fail (rect != NULL);
575
576   context = window->context;
577
578   XSetForeground (context->disp, window->gc, context->black);
579
580   /* Left border */
581   if (rect->x > window->render_rect.x) {
582     XFillRectangle (context->disp, window->win, window->gc,
583         window->render_rect.x, window->render_rect.y,
584         rect->x - window->render_rect.x, window->render_rect.h);
585   }
586
587   /* Right border */
588   t1 = rect->x + rect->w;
589   t2 = window->render_rect.x + window->render_rect.w;
590   if (t1 < t2) {
591     XFillRectangle (context->disp, window->win, window->gc,
592         t1, window->render_rect.y, t2 - t1, window->render_rect.h);
593   }
594
595   /* Top border */
596   if (rect->y > window->render_rect.y) {
597     XFillRectangle (context->disp, window->win, window->gc,
598         window->render_rect.x, window->render_rect.y,
599         window->render_rect.w, rect->y - window->render_rect.y);
600   }
601
602   /* Bottom border */
603   t1 = rect->y + rect->h;
604   t2 = window->render_rect.y + window->render_rect.h;
605   if (t1 < t2) {
606     XFillRectangle (context->disp, window->win, window->gc,
607         window->render_rect.x, t1, window->render_rect.w, t2 - t1);
608   }
609 }
610
611 void
612 gst_xvimage_memory_render (GstXvImageMemory * mem, GstVideoRectangle * src_crop,
613     GstXWindow * window, GstVideoRectangle * dst_crop, gboolean draw_border)
614 {
615   GstXvContext *context;
616   XvImage *xvimage;
617
618   context = window->context;
619
620   g_mutex_lock (&context->lock);
621   xvimage = gst_xvimage_memory_get_xvimage (mem);
622
623   if (draw_border) {
624     gst_xwindow_draw_borders (window, dst_crop);
625   }
626 #ifdef HAVE_XSHM
627   if (context->use_xshm) {
628     GST_LOG ("XvShmPutImage with image %dx%d and window %dx%d, from xvimage %"
629         GST_PTR_FORMAT, src_crop->w, src_crop->h,
630         window->render_rect.w, window->render_rect.h, mem);
631
632     XvShmPutImage (context->disp,
633         context->xv_port_id,
634         window->win,
635         window->gc, xvimage,
636         src_crop->x, src_crop->y, src_crop->w, src_crop->h,
637         dst_crop->x, dst_crop->y, dst_crop->w, dst_crop->h, FALSE);
638   } else
639 #endif /* HAVE_XSHM */
640   {
641     XvPutImage (context->disp,
642         context->xv_port_id,
643         window->win,
644         window->gc, xvimage,
645         src_crop->x, src_crop->y, src_crop->w, src_crop->h,
646         dst_crop->x, dst_crop->y, dst_crop->w, dst_crop->h);
647   }
648   XSync (context->disp, FALSE);
649
650   g_mutex_unlock (&context->lock);
651 }