1 /* GStreamer
2  * Copyright (C) <2005> Luca Ognibene <luogni@tin.it>
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 #include "ximageutil.h"
25 
26 GType
gst_meta_ximage_api_get_type(void)27 gst_meta_ximage_api_get_type (void)
28 {
29   static volatile GType type;
30   static const gchar *tags[] = { "memory", NULL };
31 
32   if (g_once_init_enter (&type)) {
33     GType _type = gst_meta_api_type_register ("GstMetaXImageSrcAPI", tags);
34     g_once_init_leave (&type, _type);
35   }
36   return type;
37 }
38 
39 static gboolean
gst_meta_ximage_init(GstMeta * meta,gpointer params,GstBuffer * buffer)40 gst_meta_ximage_init (GstMeta * meta, gpointer params, GstBuffer * buffer)
41 {
42   GstMetaXImage *emeta = (GstMetaXImage *) meta;
43 
44   emeta->parent = NULL;
45   emeta->ximage = NULL;
46 #ifdef HAVE_XSHM
47   emeta->SHMInfo.shmaddr = ((void *) -1);
48   emeta->SHMInfo.shmid = -1;
49   emeta->SHMInfo.readOnly = TRUE;
50 #endif
51   emeta->width = emeta->height = emeta->size = 0;
52   emeta->return_func = NULL;
53 
54   return TRUE;
55 }
56 
57 const GstMetaInfo *
gst_meta_ximage_get_info(void)58 gst_meta_ximage_get_info (void)
59 {
60   static const GstMetaInfo *meta_ximage_info = NULL;
61 
62   if (g_once_init_enter (&meta_ximage_info)) {
63     const GstMetaInfo *meta =
64         gst_meta_register (gst_meta_ximage_api_get_type (), "GstMetaXImageSrc",
65         sizeof (GstMetaXImage), (GstMetaInitFunction) gst_meta_ximage_init,
66         (GstMetaFreeFunction) NULL, (GstMetaTransformFunction) NULL);
67     g_once_init_leave (&meta_ximage_info, meta);
68   }
69   return meta_ximage_info;
70 }
71 
72 #ifdef HAVE_XSHM
73 static gboolean error_caught = FALSE;
74 
75 static int
ximageutil_handle_xerror(Display * display,XErrorEvent * xevent)76 ximageutil_handle_xerror (Display * display, XErrorEvent * xevent)
77 {
78   char error_msg[1024];
79 
80   XGetErrorText (display, xevent->error_code, error_msg, 1024);
81   GST_DEBUG ("ximageutil failed to use XShm calls. error: %s", error_msg);
82   error_caught = TRUE;
83   return 0;
84 }
85 
86 /* This function checks that it is actually really possible to create an image
87    using XShm */
88 gboolean
ximageutil_check_xshm_calls(GstXContext * xcontext)89 ximageutil_check_xshm_calls (GstXContext * xcontext)
90 {
91   XImage *ximage;
92   XShmSegmentInfo SHMInfo;
93   size_t size;
94   int (*handler) (Display *, XErrorEvent *);
95   gboolean result = FALSE;
96   gboolean did_attach = FALSE;
97 
98   g_return_val_if_fail (xcontext != NULL, FALSE);
99 
100   /* Sync to ensure any older errors are already processed */
101   XSync (xcontext->disp, FALSE);
102 
103   /* Set defaults so we don't free these later unnecessarily */
104   SHMInfo.shmaddr = ((void *) -1);
105   SHMInfo.shmid = -1;
106 
107   /* Setting an error handler to catch failure */
108   error_caught = FALSE;
109   handler = XSetErrorHandler (ximageutil_handle_xerror);
110 
111   /* Trying to create a 1x1 ximage */
112   GST_DEBUG ("XShmCreateImage of 1x1");
113 
114   ximage = XShmCreateImage (xcontext->disp, xcontext->visual,
115       xcontext->depth, ZPixmap, NULL, &SHMInfo, 1, 1);
116 
117   /* Might cause an error, sync to ensure it is noticed */
118   XSync (xcontext->disp, FALSE);
119   if (!ximage || error_caught) {
120     GST_WARNING ("could not XShmCreateImage a 1x1 image");
121     goto beach;
122   }
123   size = ximage->height * ximage->bytes_per_line;
124 
125   SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
126   if (SHMInfo.shmid == -1) {
127     GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
128         size);
129     goto beach;
130   }
131 
132   SHMInfo.shmaddr = shmat (SHMInfo.shmid, 0, 0);
133   if (SHMInfo.shmaddr == ((void *) -1)) {
134     GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
135     goto beach;
136   }
137 
138   /* Delete the SHM segment. It will actually go away automatically
139    * when we detach now */
140   shmctl (SHMInfo.shmid, IPC_RMID, 0);
141 
142   ximage->data = SHMInfo.shmaddr;
143   SHMInfo.readOnly = FALSE;
144 
145   if (XShmAttach (xcontext->disp, &SHMInfo) == 0) {
146     GST_WARNING ("Failed to XShmAttach");
147     goto beach;
148   }
149 
150   /* Sync to ensure we see any errors we caused */
151   XSync (xcontext->disp, FALSE);
152 
153   if (!error_caught) {
154     did_attach = TRUE;
155     /* store whether we succeeded in result */
156     result = TRUE;
157   }
158 beach:
159   /* Sync to ensure we swallow any errors we caused and reset error_caught */
160   XSync (xcontext->disp, FALSE);
161   error_caught = FALSE;
162   XSetErrorHandler (handler);
163 
164   if (did_attach) {
165     XShmDetach (xcontext->disp, &SHMInfo);
166     XSync (xcontext->disp, FALSE);
167   }
168   if (SHMInfo.shmaddr != ((void *) -1))
169     shmdt (SHMInfo.shmaddr);
170   if (ximage)
171     XDestroyImage (ximage);
172   return result;
173 }
174 #endif /* HAVE_XSHM */
175 
176 /* This function gets the X Display and global info about it. Everything is
177    stored in our object and will be cleaned when the object is disposed. Note
178    here that caps for supported format are generated without any window or
179    image creation */
180 GstXContext *
ximageutil_xcontext_get(GstElement * parent,const gchar * display_name)181 ximageutil_xcontext_get (GstElement * parent, const gchar * display_name)
182 {
183   GstXContext *xcontext = NULL;
184   XPixmapFormatValues *px_formats = NULL;
185   gint nb_formats = 0, i;
186 
187   xcontext = g_new0 (GstXContext, 1);
188 
189   xcontext->disp = XOpenDisplay (display_name);
190   GST_DEBUG_OBJECT (parent, "opened display %p", xcontext->disp);
191   if (!xcontext->disp) {
192     g_free (xcontext);
193     return NULL;
194   }
195   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
196   xcontext->visual = DefaultVisualOfScreen (xcontext->screen);
197   xcontext->root = RootWindowOfScreen (xcontext->screen);
198   xcontext->white = WhitePixelOfScreen (xcontext->screen);
199   xcontext->black = BlackPixelOfScreen (xcontext->screen);
200   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
201 
202   xcontext->width = WidthOfScreen (xcontext->screen);
203   xcontext->height = HeightOfScreen (xcontext->screen);
204 
205   xcontext->widthmm = WidthMMOfScreen (xcontext->screen);
206   xcontext->heightmm = HeightMMOfScreen (xcontext->screen);
207 
208   xcontext->caps = NULL;
209 
210   GST_DEBUG_OBJECT (parent, "X reports %dx%d pixels and %d mm x %d mm",
211       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
212   ximageutil_calculate_pixel_aspect_ratio (xcontext);
213 
214   /* We get supported pixmap formats at supported depth */
215   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
216 
217   if (!px_formats) {
218     XCloseDisplay (xcontext->disp);
219     g_free (xcontext);
220     return NULL;
221   }
222 
223   /* We get bpp value corresponding to our running depth */
224   for (i = 0; i < nb_formats; i++) {
225     if (px_formats[i].depth == xcontext->depth)
226       xcontext->bpp = px_formats[i].bits_per_pixel;
227   }
228 
229   XFree (px_formats);
230 
231   xcontext->endianness =
232       (ImageByteOrder (xcontext->disp) ==
233       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
234 
235 #ifdef HAVE_XSHM
236   /* Search for XShm extension support */
237   if (XShmQueryExtension (xcontext->disp) &&
238       ximageutil_check_xshm_calls (xcontext)) {
239     xcontext->use_xshm = TRUE;
240     GST_DEBUG ("ximageutil is using XShm extension");
241   } else {
242     xcontext->use_xshm = FALSE;
243     GST_DEBUG ("ximageutil is not using XShm extension");
244   }
245 #endif /* HAVE_XSHM */
246 
247   /* our caps system handles 24/32bpp RGB as big-endian. */
248   if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
249       xcontext->endianness == G_LITTLE_ENDIAN) {
250     xcontext->endianness = G_BIG_ENDIAN;
251     xcontext->r_mask_output = GUINT32_TO_BE (xcontext->visual->red_mask);
252     xcontext->g_mask_output = GUINT32_TO_BE (xcontext->visual->green_mask);
253     xcontext->b_mask_output = GUINT32_TO_BE (xcontext->visual->blue_mask);
254     if (xcontext->bpp == 24) {
255       xcontext->r_mask_output >>= 8;
256       xcontext->g_mask_output >>= 8;
257       xcontext->b_mask_output >>= 8;
258     }
259   } else {
260     xcontext->r_mask_output = xcontext->visual->red_mask;
261     xcontext->g_mask_output = xcontext->visual->green_mask;
262     xcontext->b_mask_output = xcontext->visual->blue_mask;
263   }
264 
265   return xcontext;
266 }
267 
268 /* This function cleans the X context. Closing the Display and unrefing the
269    caps for supported formats. */
270 void
ximageutil_xcontext_clear(GstXContext * xcontext)271 ximageutil_xcontext_clear (GstXContext * xcontext)
272 {
273   g_return_if_fail (xcontext != NULL);
274 
275   if (xcontext->caps != NULL)
276     gst_caps_unref (xcontext->caps);
277 
278   XCloseDisplay (xcontext->disp);
279 
280   g_free (xcontext);
281 }
282 
283 /* This function calculates the pixel aspect ratio based on the properties
284  * in the xcontext structure and stores it there. */
285 void
ximageutil_calculate_pixel_aspect_ratio(GstXContext * xcontext)286 ximageutil_calculate_pixel_aspect_ratio (GstXContext * xcontext)
287 {
288   gint par[][2] = {
289     {1, 1},                     /* regular screen */
290     {16, 15},                   /* PAL TV */
291     {11, 10},                   /* 525 line Rec.601 video */
292     {54, 59}                    /* 625 line Rec.601 video */
293   };
294   gint i;
295   gint index;
296   gdouble ratio;
297   gdouble delta;
298 
299 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
300 
301   /* first calculate the "real" ratio based on the X values;
302    * which is the "physical" w/h divided by the w/h in pixels of the display */
303   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
304       / (xcontext->heightmm * xcontext->width);
305 
306   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
307    * override here */
308   if (xcontext->width == 720 && xcontext->height == 576) {
309     ratio = 4.0 * 576 / (3.0 * 720);
310   }
311   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
312 
313   /* now find the one from par[][2] with the lowest delta to the real one */
314   delta = DELTA (0);
315   index = 0;
316 
317   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
318     gdouble this_delta = DELTA (i);
319 
320     if (this_delta < delta) {
321       index = i;
322       delta = this_delta;
323     }
324   }
325 
326   GST_DEBUG ("Decided on index %d (%d/%d)", index,
327       par[index][0], par[index][1]);
328 
329   xcontext->par_n = par[index][0];
330   xcontext->par_d = par[index][1];
331   GST_DEBUG ("set xcontext PAR to %d/%d\n", xcontext->par_n, xcontext->par_d);
332 }
333 
334 static gboolean
gst_ximagesrc_buffer_dispose(GstBuffer * ximage)335 gst_ximagesrc_buffer_dispose (GstBuffer * ximage)
336 {
337   GstElement *parent;
338   GstMetaXImage *meta;
339   gboolean ret = TRUE;
340 
341   meta = GST_META_XIMAGE_GET (ximage);
342 
343   parent = meta->parent;
344   if (parent == NULL) {
345     g_warning ("XImageSrcBuffer->ximagesrc == NULL");
346     goto beach;
347   }
348 
349   if (meta->return_func)
350     ret = meta->return_func (parent, ximage);
351 
352 beach:
353   return ret;
354 }
355 
356 void
gst_ximage_buffer_free(GstBuffer * ximage)357 gst_ximage_buffer_free (GstBuffer * ximage)
358 {
359   GstMetaXImage *meta;
360 
361   meta = GST_META_XIMAGE_GET (ximage);
362 
363   /* make sure it is not recycled */
364   meta->width = -1;
365   meta->height = -1;
366   gst_buffer_unref (ximage);
367 }
368 
369 /* This function handles GstXImageSrcBuffer creation depending on XShm availability */
370 GstBuffer *
gst_ximageutil_ximage_new(GstXContext * xcontext,GstElement * parent,int width,int height,BufferReturnFunc return_func)371 gst_ximageutil_ximage_new (GstXContext * xcontext,
372     GstElement * parent, int width, int height, BufferReturnFunc return_func)
373 {
374   GstBuffer *ximage = NULL;
375   GstMetaXImage *meta;
376   gboolean succeeded = FALSE;
377 
378   ximage = gst_buffer_new ();
379   GST_MINI_OBJECT_CAST (ximage)->dispose =
380       (GstMiniObjectDisposeFunction) gst_ximagesrc_buffer_dispose;
381 
382   meta = GST_META_XIMAGE_ADD (ximage);
383   meta->width = width;
384   meta->height = height;
385 
386 #ifdef HAVE_XSHM
387   meta->SHMInfo.shmaddr = ((void *) -1);
388   meta->SHMInfo.shmid = -1;
389 
390   if (xcontext->use_xshm) {
391     meta->ximage = XShmCreateImage (xcontext->disp,
392         xcontext->visual, xcontext->depth,
393         ZPixmap, NULL, &meta->SHMInfo, meta->width, meta->height);
394     if (!meta->ximage) {
395       GST_WARNING_OBJECT (parent,
396           "could not XShmCreateImage a %dx%d image", meta->width, meta->height);
397 
398       /* Retry without XShm */
399       xcontext->use_xshm = FALSE;
400       goto no_xshm;
401     }
402 
403     /* we have to use the returned bytes_per_line for our shm size */
404     meta->size = meta->ximage->bytes_per_line * meta->ximage->height;
405     meta->SHMInfo.shmid = shmget (IPC_PRIVATE, meta->size, IPC_CREAT | 0777);
406     if (meta->SHMInfo.shmid == -1)
407       goto beach;
408 
409     meta->SHMInfo.shmaddr = shmat (meta->SHMInfo.shmid, 0, 0);
410     if (meta->SHMInfo.shmaddr == ((void *) -1))
411       goto beach;
412 
413     /* Delete the SHM segment. It will actually go away automatically
414      * when we detach now */
415     shmctl (meta->SHMInfo.shmid, IPC_RMID, 0);
416 
417     meta->ximage->data = meta->SHMInfo.shmaddr;
418     meta->SHMInfo.readOnly = FALSE;
419 
420     if (XShmAttach (xcontext->disp, &meta->SHMInfo) == 0)
421       goto beach;
422 
423     XSync (xcontext->disp, FALSE);
424   } else
425   no_xshm:
426 #endif /* HAVE_XSHM */
427   {
428     meta->ximage = XCreateImage (xcontext->disp,
429         xcontext->visual,
430         xcontext->depth,
431         ZPixmap, 0, NULL, meta->width, meta->height, xcontext->bpp, 0);
432     if (!meta->ximage)
433       goto beach;
434 
435     /* we have to use the returned bytes_per_line for our image size */
436     meta->size = meta->ximage->bytes_per_line * meta->ximage->height;
437     meta->ximage->data = g_malloc (meta->size);
438 
439     XSync (xcontext->disp, FALSE);
440   }
441   succeeded = TRUE;
442 
443   gst_buffer_append_memory (ximage,
444       gst_memory_new_wrapped (GST_MEMORY_FLAG_NO_SHARE, meta->ximage->data,
445           meta->size, 0, meta->size, NULL, NULL));
446 
447   /* Keep a ref to our src */
448   meta->parent = gst_object_ref (parent);
449   meta->return_func = return_func;
450 beach:
451   if (!succeeded) {
452     gst_ximage_buffer_free (ximage);
453     ximage = NULL;
454   }
455 
456   return ximage;
457 }
458 
459 /* This function destroys a GstXImageBuffer handling XShm availability */
460 void
gst_ximageutil_ximage_destroy(GstXContext * xcontext,GstBuffer * ximage)461 gst_ximageutil_ximage_destroy (GstXContext * xcontext, GstBuffer * ximage)
462 {
463   GstMetaXImage *meta;
464 
465   meta = GST_META_XIMAGE_GET (ximage);
466 
467   /* We might have some buffers destroyed after changing state to NULL */
468   if (!xcontext)
469     goto beach;
470 
471   g_return_if_fail (ximage != NULL);
472 
473 #ifdef HAVE_XSHM
474   if (xcontext->use_xshm) {
475     if (meta->SHMInfo.shmaddr != ((void *) -1)) {
476       XShmDetach (xcontext->disp, &meta->SHMInfo);
477       XSync (xcontext->disp, 0);
478       shmdt (meta->SHMInfo.shmaddr);
479     }
480     if (meta->ximage)
481       XDestroyImage (meta->ximage);
482 
483   } else
484 #endif /* HAVE_XSHM */
485   {
486     if (meta->ximage) {
487       XDestroyImage (meta->ximage);
488     }
489   }
490 
491   XSync (xcontext->disp, FALSE);
492 beach:
493   if (meta->parent) {
494     /* Release the ref to our parent */
495     gst_object_unref (meta->parent);
496     meta->parent = NULL;
497   }
498 
499   return;
500 }
501