1 /* GStreamer
2  * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3  *               <2009>,<2010> Stefan Kost <stefan.kost@nokia.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /* for developers: there are two useful tools : xvinfo and xvattr */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 /* Object header */
28 #include "xvcontext.h"
29 
30 /* Debugging category */
31 #include <gst/gstinfo.h>
32 
33 /* for XkbKeycodeToKeysym */
34 #include <X11/XKBlib.h>
35 
36 GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_context);
37 #define GST_CAT_DEFAULT gst_debug_xv_context
38 
39 void
gst_xvcontext_config_clear(GstXvContextConfig * config)40 gst_xvcontext_config_clear (GstXvContextConfig * config)
41 {
42   if (config->display_name) {
43     g_free (config->display_name);
44     config->display_name = NULL;
45   }
46   config->adaptor_nr = -1;
47 }
48 
49 GST_DEFINE_MINI_OBJECT_TYPE (GstXvContext, gst_xvcontext);
50 
51 typedef struct
52 {
53   unsigned long flags;
54   unsigned long functions;
55   unsigned long decorations;
56   long input_mode;
57   unsigned long status;
58 }
59 MotifWmHints, MwmHints;
60 
61 #define MWM_HINTS_DECORATIONS   (1L << 1)
62 
63 
64 static void
gst_lookup_xv_port_from_adaptor(GstXvContext * context,XvAdaptorInfo * adaptors,guint adaptor_nr)65 gst_lookup_xv_port_from_adaptor (GstXvContext * context,
66     XvAdaptorInfo * adaptors, guint adaptor_nr)
67 {
68   gint j;
69   gint res;
70 
71   /* Do we support XvImageMask ? */
72   if (!(adaptors[adaptor_nr].type & XvImageMask)) {
73     GST_DEBUG ("XV Adaptor %s has no support for XvImageMask",
74         adaptors[adaptor_nr].name);
75     return;
76   }
77 
78   /* We found such an adaptor, looking for an available port */
79   for (j = 0; j < adaptors[adaptor_nr].num_ports && !context->xv_port_id; j++) {
80     /* We try to grab the port */
81     res = XvGrabPort (context->disp, adaptors[adaptor_nr].base_id + j, 0);
82     if (Success == res) {
83       context->xv_port_id = adaptors[adaptor_nr].base_id + j;
84       GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_nr].name,
85           adaptors[adaptor_nr].num_ports);
86     } else {
87       GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j,
88           adaptors[adaptor_nr].name, res);
89     }
90   }
91 }
92 
93 /* This function generates a caps with all supported format by the first
94    Xv grabable port we find. We store each one of the supported formats in a
95    format list and append the format to a newly created caps that we return
96    If this function does not return NULL because of an error, it also grabs
97    the port via XvGrabPort */
98 static GstCaps *
gst_xvcontext_get_xv_support(GstXvContext * context,const GstXvContextConfig * config,GError ** error)99 gst_xvcontext_get_xv_support (GstXvContext * context,
100     const GstXvContextConfig * config, GError ** error)
101 {
102   gint i;
103   XvAdaptorInfo *adaptors;
104   gint nb_formats;
105   XvImageFormatValues *formats = NULL;
106   guint nb_encodings;
107   XvEncodingInfo *encodings = NULL;
108   gulong max_w = G_MAXINT, max_h = G_MAXINT;
109   GstCaps *caps = NULL;
110   GstCaps *rgb_caps = NULL;
111 
112   g_return_val_if_fail (context != NULL, NULL);
113 
114   /* First let's check that XVideo extension is available */
115   if (!XQueryExtension (context->disp, "XVideo", &i, &i, &i))
116     goto no_xv;
117 
118   /* Then we get adaptors list */
119   if (Success != XvQueryAdaptors (context->disp, context->root,
120           &context->nb_adaptors, &adaptors))
121     goto no_adaptors;
122 
123   context->xv_port_id = 0;
124 
125   GST_DEBUG ("Found %u XV adaptor(s)", context->nb_adaptors);
126 
127   context->adaptors =
128       (gchar **) g_malloc0 (context->nb_adaptors * sizeof (gchar *));
129 
130   /* Now fill up our adaptor name array */
131   for (i = 0; i < context->nb_adaptors; i++) {
132     context->adaptors[i] = g_strdup (adaptors[i].name);
133   }
134 
135   if (config->adaptor_nr != -1 && config->adaptor_nr < context->nb_adaptors) {
136     /* Find xv port from user defined adaptor */
137     gst_lookup_xv_port_from_adaptor (context, adaptors, config->adaptor_nr);
138   }
139 
140   if (!context->xv_port_id) {
141     /* Now search for an adaptor that supports XvImageMask */
142     for (i = 0; i < context->nb_adaptors && !context->xv_port_id; i++) {
143       gst_lookup_xv_port_from_adaptor (context, adaptors, i);
144       context->adaptor_nr = i;
145     }
146   }
147 
148   XvFreeAdaptorInfo (adaptors);
149 
150   if (!context->xv_port_id)
151     goto no_ports;
152 
153   /* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */
154   {
155     int count, todo = 4;
156     XvAttribute *const attr = XvQueryPortAttributes (context->disp,
157         context->xv_port_id, &count);
158     static const char autopaint[] = "XV_AUTOPAINT_COLORKEY";
159     static const char dbl_buffer[] = "XV_DOUBLE_BUFFER";
160     static const char colorkey[] = "XV_COLORKEY";
161     static const char iturbt709[] = "XV_ITURBT_709";
162 
163     GST_DEBUG ("Checking %d Xv port attributes", count);
164 
165     context->have_autopaint_colorkey = FALSE;
166     context->have_double_buffer = FALSE;
167     context->have_colorkey = FALSE;
168     context->have_iturbt709 = FALSE;
169 
170     for (i = 0; ((i < count) && todo); i++) {
171       GST_DEBUG ("Got attribute %s", attr[i].name);
172 
173       if (!strcmp (attr[i].name, autopaint)) {
174         const Atom atom = XInternAtom (context->disp, autopaint, False);
175 
176         /* turn on autopaint colorkey */
177         XvSetPortAttribute (context->disp, context->xv_port_id, atom,
178             (config->autopaint_colorkey ? 1 : 0));
179         todo--;
180         context->have_autopaint_colorkey = TRUE;
181       } else if (!strcmp (attr[i].name, dbl_buffer)) {
182         const Atom atom = XInternAtom (context->disp, dbl_buffer, False);
183 
184         XvSetPortAttribute (context->disp, context->xv_port_id, atom,
185             (config->double_buffer ? 1 : 0));
186         todo--;
187         context->have_double_buffer = TRUE;
188       } else if (!strcmp (attr[i].name, colorkey)) {
189         /* Set the colorkey, default is something that is dark but hopefully
190          * won't randomly appear on the screen elsewhere (ie not black or greys)
191          * can be overridden by setting "colorkey" property
192          */
193         const Atom atom = XInternAtom (context->disp, colorkey, False);
194         guint32 ckey = 0;
195         gboolean set_attr = TRUE;
196         guint cr, cg, cb;
197 
198         /* set a colorkey in the right format RGB565/RGB888
199          * We only handle these 2 cases, because they're the only types of
200          * devices we've encountered. If we don't recognise it, leave it alone
201          */
202         cr = (config->colorkey >> 16);
203         cg = (config->colorkey >> 8) & 0xFF;
204         cb = (config->colorkey) & 0xFF;
205         switch (context->depth) {
206           case 16:             /* RGB 565 */
207             cr >>= 3;
208             cg >>= 2;
209             cb >>= 3;
210             ckey = (cr << 11) | (cg << 5) | cb;
211             break;
212           case 24:
213           case 32:             /* RGB 888 / ARGB 8888 */
214             ckey = (cr << 16) | (cg << 8) | cb;
215             break;
216           default:
217             GST_DEBUG ("Unknown bit depth %d for Xv Colorkey - not adjusting",
218                 context->depth);
219             set_attr = FALSE;
220             break;
221         }
222 
223         if (set_attr) {
224           ckey = CLAMP (ckey, (guint32) attr[i].min_value,
225               (guint32) attr[i].max_value);
226           GST_LOG ("Setting color key for display depth %d to 0x%x",
227               context->depth, ckey);
228 
229           XvSetPortAttribute (context->disp, context->xv_port_id, atom,
230               (gint) ckey);
231         }
232         todo--;
233         context->have_colorkey = TRUE;
234       } else if (!strcmp (attr[i].name, iturbt709)) {
235         todo--;
236         context->have_iturbt709 = TRUE;
237       }
238     }
239 
240     XFree (attr);
241   }
242 
243   /* Get the list of encodings supported by the adapter and look for the
244    * XV_IMAGE encoding so we can determine the maximum width and height
245    * supported */
246   XvQueryEncodings (context->disp, context->xv_port_id, &nb_encodings,
247       &encodings);
248 
249   for (i = 0; i < nb_encodings; i++) {
250     GST_LOG ("Encoding %d, name %s, max wxh %lux%lu rate %d/%d",
251         i, encodings[i].name, encodings[i].width, encodings[i].height,
252         encodings[i].rate.numerator, encodings[i].rate.denominator);
253     if (strcmp (encodings[i].name, "XV_IMAGE") == 0) {
254       max_w = encodings[i].width;
255       max_h = encodings[i].height;
256     }
257   }
258 
259   XvFreeEncodingInfo (encodings);
260 
261   /* We get all image formats supported by our port */
262   formats = XvListImageFormats (context->disp,
263       context->xv_port_id, &nb_formats);
264   caps = gst_caps_new_empty ();
265   for (i = 0; i < nb_formats; i++) {
266     GstCaps *format_caps = NULL;
267     gboolean is_rgb_format = FALSE;
268     GstVideoFormat vformat;
269 
270     /* We set the image format of the context to an existing one. This
271        is just some valid image format for making our xshm calls check before
272        caps negotiation really happens. */
273     context->im_format = formats[i].id;
274 
275     switch (formats[i].type) {
276       case XvRGB:
277       {
278         XvImageFormatValues *fmt = &(formats[i]);
279         gint endianness;
280 
281         endianness =
282             (fmt->byte_order == LSBFirst ? G_LITTLE_ENDIAN : G_BIG_ENDIAN);
283 
284         vformat = gst_video_format_from_masks (fmt->depth, fmt->bits_per_pixel,
285             endianness, fmt->red_mask, fmt->green_mask, fmt->blue_mask, 0);
286         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
287           break;
288 
289         format_caps = gst_caps_new_simple ("video/x-raw",
290             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
291             "width", GST_TYPE_INT_RANGE, 1, max_w,
292             "height", GST_TYPE_INT_RANGE, 1, max_h,
293             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
294 
295         is_rgb_format = TRUE;
296         break;
297       }
298       case XvYUV:
299       {
300         vformat = gst_video_format_from_fourcc (formats[i].id);
301         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
302           break;
303 
304         format_caps = gst_caps_new_simple ("video/x-raw",
305             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
306             "width", GST_TYPE_INT_RANGE, 1, max_w,
307             "height", GST_TYPE_INT_RANGE, 1, max_h,
308             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
309         break;
310       }
311       default:
312         vformat = GST_VIDEO_FORMAT_UNKNOWN;
313         g_assert_not_reached ();
314         break;
315     }
316 
317     if (format_caps) {
318       GstXvImageFormat *format = NULL;
319 
320       format = g_new0 (GstXvImageFormat, 1);
321       if (format) {
322         format->format = formats[i].id;
323         format->vformat = vformat;
324         format->caps = gst_caps_copy (format_caps);
325         context->formats_list = g_list_append (context->formats_list, format);
326       }
327 
328       if (is_rgb_format) {
329         if (rgb_caps == NULL)
330           rgb_caps = format_caps;
331         else
332           gst_caps_append (rgb_caps, format_caps);
333       } else
334         gst_caps_append (caps, format_caps);
335     }
336   }
337 
338   /* Collected all caps into either the caps or rgb_caps structures.
339    * Append rgb_caps on the end of YUV, so that YUV is always preferred */
340   if (rgb_caps)
341     gst_caps_append (caps, rgb_caps);
342 
343   if (formats)
344     XFree (formats);
345 
346   GST_DEBUG ("Generated the following caps: %" GST_PTR_FORMAT, caps);
347 
348   if (gst_caps_is_empty (caps))
349     goto no_caps;
350 
351   return caps;
352 
353   /* ERRORS */
354 no_xv:
355   {
356     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
357         ("XVideo extension is not available"));
358     return NULL;
359   }
360 no_adaptors:
361   {
362     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
363         ("Failed getting XV adaptors list"));
364     return NULL;
365   }
366 no_ports:
367   {
368     context->adaptor_nr = -1;
369     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_BUSY,
370         ("No Xv Port available"));
371     return NULL;
372   }
373 no_caps:
374   {
375     gst_caps_unref (caps);
376     g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE,
377         ("No supported format found"));
378     return NULL;
379   }
380 }
381 
382 /* This function calculates the pixel aspect ratio based on the properties
383  * in the context structure and stores it there. */
384 static void
gst_xvcontext_calculate_pixel_aspect_ratio(GstXvContext * context)385 gst_xvcontext_calculate_pixel_aspect_ratio (GstXvContext * context)
386 {
387   static const gint par[][2] = {
388     {1, 1},                     /* regular screen */
389     {16, 15},                   /* PAL TV */
390     {11, 10},                   /* 525 line Rec.601 video */
391     {54, 59},                   /* 625 line Rec.601 video */
392     {64, 45},                   /* 1280x1024 on 16:9 display */
393     {5, 3},                     /* 1280x1024 on 4:3 display */
394     {4, 3}                      /*  800x600 on 16:9 display */
395   };
396   gint i;
397   gint index;
398   gdouble ratio;
399   gdouble delta;
400 
401 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
402 
403   /* first calculate the "real" ratio based on the X values;
404    * which is the "physical" w/h divided by the w/h in pixels of the display */
405   ratio = (gdouble) (context->widthmm * context->height)
406       / (context->heightmm * context->width);
407 
408   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
409    * override here */
410   if (context->width == 720 && context->height == 576) {
411     ratio = 4.0 * 576 / (3.0 * 720);
412   }
413   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
414 
415   /* now find the one from par[][2] with the lowest delta to the real one */
416   delta = DELTA (0);
417   index = 0;
418 
419   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
420     gdouble this_delta = DELTA (i);
421 
422     if (this_delta < delta) {
423       index = i;
424       delta = this_delta;
425     }
426   }
427 
428   GST_DEBUG ("Decided on index %d (%d/%d)", index,
429       par[index][0], par[index][1]);
430 
431   g_free (context->par);
432   context->par = g_new0 (GValue, 1);
433   g_value_init (context->par, GST_TYPE_FRACTION);
434   gst_value_set_fraction (context->par, par[index][0], par[index][1]);
435   GST_DEBUG ("set context PAR to %d/%d",
436       gst_value_get_fraction_numerator (context->par),
437       gst_value_get_fraction_denominator (context->par));
438 }
439 
440 #ifdef HAVE_XSHM
441 /* X11 stuff */
442 static gboolean error_caught = FALSE;
443 
444 static int
gst_xvimage_handle_xerror(Display * display,XErrorEvent * xevent)445 gst_xvimage_handle_xerror (Display * display, XErrorEvent * xevent)
446 {
447   char error_msg[1024];
448 
449   XGetErrorText (display, xevent->error_code, error_msg, 1024);
450   GST_DEBUG ("xvimage triggered an XError. error: %s", error_msg);
451   error_caught = TRUE;
452   return 0;
453 }
454 
455 /* This function checks that it is actually really possible to create an image
456    using XShm */
457 static gboolean
gst_xvcontext_check_xshm_calls(GstXvContext * context)458 gst_xvcontext_check_xshm_calls (GstXvContext * context)
459 {
460   XvImage *xvimage;
461   XShmSegmentInfo SHMInfo;
462   size_t size;
463   int (*handler) (Display *, XErrorEvent *);
464   gboolean result = FALSE;
465   gboolean did_attach = FALSE;
466 
467   g_return_val_if_fail (context != NULL, FALSE);
468 
469   /* Sync to ensure any older errors are already processed */
470   XSync (context->disp, FALSE);
471 
472   /* Set defaults so we don't free these later unnecessarily */
473   SHMInfo.shmaddr = ((void *) -1);
474   SHMInfo.shmid = -1;
475 
476   /* Setting an error handler to catch failure */
477   error_caught = FALSE;
478   handler = XSetErrorHandler (gst_xvimage_handle_xerror);
479 
480   /* Trying to create a 1x1 picture */
481   GST_DEBUG ("XvShmCreateImage of 1x1");
482   xvimage = XvShmCreateImage (context->disp, context->xv_port_id,
483       context->im_format, NULL, 1, 1, &SHMInfo);
484 
485   /* Might cause an error, sync to ensure it is noticed */
486   XSync (context->disp, FALSE);
487   if (!xvimage || error_caught) {
488     GST_WARNING ("could not XvShmCreateImage a 1x1 image");
489     goto beach;
490   }
491   size = xvimage->data_size;
492   SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
493   if (SHMInfo.shmid == -1) {
494     GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
495         size);
496     goto beach;
497   }
498 
499   SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0);
500   if (SHMInfo.shmaddr == ((void *) -1)) {
501     GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
502     /* Clean up the shared memory segment */
503     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
504     goto beach;
505   }
506 
507   xvimage->data = SHMInfo.shmaddr;
508   SHMInfo.readOnly = FALSE;
509 
510   if (XShmAttach (context->disp, &SHMInfo) == 0) {
511     GST_WARNING ("Failed to XShmAttach");
512     /* Clean up the shared memory segment */
513     shmctl (SHMInfo.shmid, IPC_RMID, NULL);
514     goto beach;
515   }
516 
517   /* Sync to ensure we see any errors we caused */
518   XSync (context->disp, FALSE);
519 
520   /* Delete the shared memory segment as soon as everyone is attached.
521    * This way, it will be deleted as soon as we detach later, and not
522    * leaked if we crash. */
523   shmctl (SHMInfo.shmid, IPC_RMID, NULL);
524 
525   if (!error_caught) {
526     GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid,
527         SHMInfo.shmseg);
528 
529     did_attach = TRUE;
530     /* store whether we succeeded in result */
531     result = TRUE;
532   } else {
533     GST_WARNING ("MIT-SHM extension check failed at XShmAttach. "
534         "Not using shared memory.");
535   }
536 
537 beach:
538   /* Sync to ensure we swallow any errors we caused and reset error_caught */
539   XSync (context->disp, FALSE);
540 
541   error_caught = FALSE;
542   XSetErrorHandler (handler);
543 
544   if (did_attach) {
545     GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx",
546         SHMInfo.shmid, SHMInfo.shmseg);
547     XShmDetach (context->disp, &SHMInfo);
548     XSync (context->disp, FALSE);
549   }
550   if (SHMInfo.shmaddr != ((void *) -1))
551     shmdt (SHMInfo.shmaddr);
552   if (xvimage)
553     XFree (xvimage);
554   return result;
555 }
556 #endif /* HAVE_XSHM */
557 
558 static GstXvContext *
gst_xvcontext_copy(GstXvContext * context)559 gst_xvcontext_copy (GstXvContext * context)
560 {
561   return NULL;
562 }
563 
564 static void
gst_xvcontext_free(GstXvContext * context)565 gst_xvcontext_free (GstXvContext * context)
566 {
567   GList *formats_list, *channels_list;
568   gint i = 0;
569 
570   GST_LOG ("free %p", context);
571 
572   formats_list = context->formats_list;
573 
574   while (formats_list) {
575     GstXvImageFormat *format = formats_list->data;
576 
577     gst_caps_unref (format->caps);
578     g_free (format);
579     formats_list = g_list_next (formats_list);
580   }
581 
582   if (context->formats_list)
583     g_list_free (context->formats_list);
584 
585   channels_list = context->channels_list;
586 
587   while (channels_list) {
588     GstColorBalanceChannel *channel = channels_list->data;
589 
590     g_object_unref (channel);
591     channels_list = g_list_next (channels_list);
592   }
593 
594   if (context->channels_list)
595     g_list_free (context->channels_list);
596 
597   if (context->caps)
598     gst_caps_unref (context->caps);
599   if (context->last_caps)
600     gst_caps_unref (context->last_caps);
601 
602   for (i = 0; i < context->nb_adaptors; i++) {
603     g_free (context->adaptors[i]);
604   }
605 
606   g_free (context->adaptors);
607 
608   g_free (context->par);
609 
610   GST_DEBUG ("Closing display and freeing X Context");
611 
612   if (context->xv_port_id)
613     XvUngrabPort (context->disp, context->xv_port_id, 0);
614 
615   if (context->disp)
616     XCloseDisplay (context->disp);
617 
618   g_mutex_clear (&context->lock);
619 
620   g_slice_free1 (sizeof (GstXvContext), context);
621 }
622 
623 
624 /* This function gets the X Display and global info about it. Everything is
625    stored in our object and will be cleaned when the object is disposed. Note
626    here that caps for supported format are generated without any window or
627    image creation */
628 GstXvContext *
gst_xvcontext_new(GstXvContextConfig * config,GError ** error)629 gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
630 {
631   GstXvContext *context = NULL;
632   XPixmapFormatValues *px_formats = NULL;
633   gint nb_formats = 0, i, j, N_attr;
634   XvAttribute *xv_attr;
635   Atom prop_atom;
636   const char *channels[4] = { "XV_HUE", "XV_SATURATION",
637     "XV_BRIGHTNESS", "XV_CONTRAST"
638   };
639 
640   g_return_val_if_fail (config != NULL, NULL);
641 
642   context = g_slice_new0 (GstXvContext);
643 
644   gst_mini_object_init (GST_MINI_OBJECT_CAST (context), 0,
645       gst_xvcontext_get_type (),
646       (GstMiniObjectCopyFunction) gst_xvcontext_copy,
647       (GstMiniObjectDisposeFunction) NULL,
648       (GstMiniObjectFreeFunction) gst_xvcontext_free);
649 
650   g_mutex_init (&context->lock);
651   context->im_format = 0;
652   context->adaptor_nr = -1;
653 
654   if (!(context->disp = XOpenDisplay (config->display_name)))
655     goto no_display;
656 
657   context->screen = DefaultScreenOfDisplay (context->disp);
658   context->screen_num = DefaultScreen (context->disp);
659   context->visual = DefaultVisual (context->disp, context->screen_num);
660   context->root = DefaultRootWindow (context->disp);
661   context->white = XWhitePixel (context->disp, context->screen_num);
662   context->black = XBlackPixel (context->disp, context->screen_num);
663   context->depth = DefaultDepthOfScreen (context->screen);
664 
665   context->width = DisplayWidth (context->disp, context->screen_num);
666   context->height = DisplayHeight (context->disp, context->screen_num);
667   context->widthmm = DisplayWidthMM (context->disp, context->screen_num);
668   context->heightmm = DisplayHeightMM (context->disp, context->screen_num);
669 
670   GST_DEBUG ("X reports %dx%d pixels and %d mm x %d mm",
671       context->width, context->height, context->widthmm, context->heightmm);
672 
673   gst_xvcontext_calculate_pixel_aspect_ratio (context);
674   /* We get supported pixmap formats at supported depth */
675   px_formats = XListPixmapFormats (context->disp, &nb_formats);
676 
677   if (!px_formats)
678     goto no_pixel_formats;
679 
680   /* We get bpp value corresponding to our running depth */
681   for (i = 0; i < nb_formats; i++) {
682     if (px_formats[i].depth == context->depth)
683       context->bpp = px_formats[i].bits_per_pixel;
684   }
685 
686   XFree (px_formats);
687 
688   context->endianness =
689       (ImageByteOrder (context->disp) ==
690       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
691 
692   /* our caps system handles 24/32bpp RGB as big-endian. */
693   if ((context->bpp == 24 || context->bpp == 32) &&
694       context->endianness == G_LITTLE_ENDIAN) {
695     context->endianness = G_BIG_ENDIAN;
696     context->visual->red_mask = GUINT32_TO_BE (context->visual->red_mask);
697     context->visual->green_mask = GUINT32_TO_BE (context->visual->green_mask);
698     context->visual->blue_mask = GUINT32_TO_BE (context->visual->blue_mask);
699     if (context->bpp == 24) {
700       context->visual->red_mask >>= 8;
701       context->visual->green_mask >>= 8;
702       context->visual->blue_mask >>= 8;
703     }
704   }
705 
706   if (!(context->caps = gst_xvcontext_get_xv_support (context, config, error)))
707     goto no_caps;
708 
709   /* Search for XShm extension support */
710 #ifdef HAVE_XSHM
711   if (XShmQueryExtension (context->disp) &&
712       gst_xvcontext_check_xshm_calls (context)) {
713     context->use_xshm = TRUE;
714     GST_DEBUG ("xvimagesink is using XShm extension");
715   } else
716 #endif /* HAVE_XSHM */
717   {
718     context->use_xshm = FALSE;
719     GST_DEBUG ("xvimagesink is not using XShm extension");
720   }
721 
722   xv_attr = XvQueryPortAttributes (context->disp, context->xv_port_id, &N_attr);
723 
724   /* Generate the channels list */
725   for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
726     XvAttribute *matching_attr = NULL;
727 
728     /* Retrieve the property atom if it exists. If it doesn't exist,
729      * the attribute itself must not either, so we can skip */
730     prop_atom = XInternAtom (context->disp, channels[i], True);
731     if (prop_atom == None)
732       continue;
733 
734     if (xv_attr != NULL) {
735       for (j = 0; j < N_attr && matching_attr == NULL; ++j)
736         if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
737           matching_attr = xv_attr + j;
738     }
739 
740     if (matching_attr) {
741       GstColorBalanceChannel *channel;
742 
743       channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
744       channel->label = g_strdup (channels[i]);
745       channel->min_value = matching_attr->min_value;
746       channel->max_value = matching_attr->max_value;
747 
748       context->channels_list = g_list_append (context->channels_list, channel);
749 
750       /* If the colorbalance settings have not been touched we get Xv values
751          as defaults and update our internal variables */
752       if (!config->cb_changed) {
753         gint val;
754 
755         XvGetPortAttribute (context->disp, context->xv_port_id,
756             prop_atom, &val);
757         /* Normalize val to [-1000, 1000] */
758         val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
759             (double) (channel->max_value - channel->min_value));
760 
761         if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
762           config->hue = val;
763         else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
764           config->saturation = val;
765         else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
766           config->brightness = val;
767         else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
768           config->contrast = val;
769       }
770     }
771   }
772 
773   if (xv_attr)
774     XFree (xv_attr);
775 
776   return context;
777 
778   /* ERRORS */
779 no_display:
780   {
781     gst_xvcontext_unref (context);
782     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
783         "Could not open display %s", config->display_name);
784     return NULL;
785   }
786 no_pixel_formats:
787   {
788     gst_xvcontext_unref (context);
789     g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
790         ("Could not get pixel formats"));
791     return NULL;
792   }
793 no_caps:
794   {
795     gst_xvcontext_unref (context);
796     return NULL;
797   }
798 }
799 
800 void
gst_xvcontext_set_synchronous(GstXvContext * context,gboolean synchronous)801 gst_xvcontext_set_synchronous (GstXvContext * context, gboolean synchronous)
802 {
803   /* call XSynchronize with the current value of synchronous */
804   GST_DEBUG ("XSynchronize called with %s", synchronous ? "TRUE" : "FALSE");
805   g_mutex_lock (&context->lock);
806   XSynchronize (context->disp, synchronous);
807   g_mutex_unlock (&context->lock);
808 }
809 
810 void
gst_xvcontext_update_colorbalance(GstXvContext * context,GstXvContextConfig * config)811 gst_xvcontext_update_colorbalance (GstXvContext * context,
812     GstXvContextConfig * config)
813 {
814   GList *channels = NULL;
815 
816   /* Don't set the attributes if they haven't been changed, to avoid
817    * rounding errors changing the values */
818   if (!config->cb_changed)
819     return;
820 
821   /* For each channel of the colorbalance we calculate the correct value
822      doing range conversion and then set the Xv port attribute to match our
823      values. */
824   channels = context->channels_list;
825 
826   while (channels) {
827     if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) {
828       GstColorBalanceChannel *channel = NULL;
829       Atom prop_atom;
830       gint value = 0;
831       gdouble convert_coef;
832 
833       channel = GST_COLOR_BALANCE_CHANNEL (channels->data);
834       g_object_ref (channel);
835 
836       /* Our range conversion coef */
837       convert_coef = (channel->max_value - channel->min_value) / 2000.0;
838 
839       if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
840         value = config->hue;
841       } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
842         value = config->saturation;
843       } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
844         value = config->contrast;
845       } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
846         value = config->brightness;
847       } else {
848         g_warning ("got an unknown channel %s", channel->label);
849         g_object_unref (channel);
850         return;
851       }
852 
853       /* Committing to Xv port */
854       g_mutex_lock (&context->lock);
855       prop_atom = XInternAtom (context->disp, channel->label, True);
856       if (prop_atom != None) {
857         int xv_value;
858         xv_value =
859             floor (0.5 + (value + 1000) * convert_coef + channel->min_value);
860         XvSetPortAttribute (context->disp,
861             context->xv_port_id, prop_atom, xv_value);
862       }
863       g_mutex_unlock (&context->lock);
864 
865       g_object_unref (channel);
866     }
867     channels = g_list_next (channels);
868   }
869 }
870 
871 /* This function tries to get a format matching with a given caps in the
872    supported list of formats we generated in gst_xvimagesink_get_xv_support */
873 gint
gst_xvcontext_get_format_from_info(GstXvContext * context,GstVideoInfo * info)874 gst_xvcontext_get_format_from_info (GstXvContext * context, GstVideoInfo * info)
875 {
876   GList *list = NULL;
877 
878   list = context->formats_list;
879 
880   while (list) {
881     GstXvImageFormat *format = list->data;
882 
883     if (format && format->vformat == GST_VIDEO_INFO_FORMAT (info))
884       return format->format;
885 
886     list = g_list_next (list);
887   }
888   return -1;
889 }
890 
891 void
gst_xvcontext_set_colorimetry(GstXvContext * context,GstVideoColorimetry * colorimetry)892 gst_xvcontext_set_colorimetry (GstXvContext * context,
893     GstVideoColorimetry * colorimetry)
894 {
895   Atom prop_atom;
896   int xv_value;
897 
898   if (!context->have_iturbt709)
899     return;
900 
901   switch (colorimetry->matrix) {
902     case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
903     case GST_VIDEO_COLOR_MATRIX_BT709:
904       xv_value = 1;
905       break;
906     default:
907       xv_value = 0;
908       break;
909   }
910 
911   g_mutex_lock (&context->lock);
912   prop_atom = XInternAtom (context->disp, "XV_ITURBT_709", True);
913   if (prop_atom != None) {
914     XvSetPortAttribute (context->disp,
915         context->xv_port_id, prop_atom, xv_value);
916   }
917   g_mutex_unlock (&context->lock);
918 }
919 
920 GstXWindow *
gst_xvcontext_create_xwindow(GstXvContext * context,gint width,gint height)921 gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height)
922 {
923   GstXWindow *window;
924   Atom wm_delete;
925   Atom hints_atom = None;
926 
927   g_return_val_if_fail (GST_IS_XVCONTEXT (context), NULL);
928 
929   window = g_slice_new0 (GstXWindow);
930 
931   window->context = gst_xvcontext_ref (context);
932   window->render_rect.x = window->render_rect.y = 0;
933   window->render_rect.w = width;
934   window->render_rect.h = height;
935   window->have_render_rect = FALSE;
936 
937   window->width = width;
938   window->height = height;
939   window->internal = TRUE;
940 
941   g_mutex_lock (&context->lock);
942 
943   window->win = XCreateSimpleWindow (context->disp,
944       context->root, 0, 0, width, height, 0, 0, context->black);
945 
946   /* We have to do that to prevent X from redrawing the background on
947    * ConfigureNotify. This takes away flickering of video when resizing. */
948   XSetWindowBackgroundPixmap (context->disp, window->win, None);
949 
950   /* Tell the window manager we'd like delete client messages instead of
951    * being killed */
952   wm_delete = XInternAtom (context->disp, "WM_DELETE_WINDOW", True);
953   if (wm_delete != None) {
954     (void) XSetWMProtocols (context->disp, window->win, &wm_delete, 1);
955   }
956 
957   hints_atom = XInternAtom (context->disp, "_MOTIF_WM_HINTS", True);
958   if (hints_atom != None) {
959     MotifWmHints *hints;
960 
961     hints = g_malloc0 (sizeof (MotifWmHints));
962 
963     hints->flags |= MWM_HINTS_DECORATIONS;
964     hints->decorations = 1 << 0;
965 
966     XChangeProperty (context->disp, window->win,
967         hints_atom, hints_atom, 32, PropModeReplace,
968         (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
969 
970     XSync (context->disp, FALSE);
971 
972     g_free (hints);
973   }
974 
975   window->gc = XCreateGC (context->disp, window->win, 0, NULL);
976 
977   XMapRaised (context->disp, window->win);
978 
979   XSync (context->disp, FALSE);
980 
981   g_mutex_unlock (&context->lock);
982 
983   return window;
984 }
985 
986 GstXWindow *
gst_xvcontext_create_xwindow_from_xid(GstXvContext * context,XID xid)987 gst_xvcontext_create_xwindow_from_xid (GstXvContext * context, XID xid)
988 {
989   GstXWindow *window;
990   XWindowAttributes attr;
991 
992   window = g_slice_new0 (GstXWindow);
993   window->win = xid;
994   window->context = gst_xvcontext_ref (context);
995 
996   /* Set the event we want to receive and create a GC */
997   g_mutex_lock (&context->lock);
998 
999   XGetWindowAttributes (context->disp, window->win, &attr);
1000 
1001   window->width = attr.width;
1002   window->height = attr.height;
1003   window->internal = FALSE;
1004 
1005   window->have_render_rect = FALSE;
1006   window->render_rect.x = window->render_rect.y = 0;
1007   window->render_rect.w = attr.width;
1008   window->render_rect.h = attr.height;
1009 
1010   window->gc = XCreateGC (context->disp, window->win, 0, NULL);
1011   g_mutex_unlock (&context->lock);
1012 
1013   return window;
1014 }
1015 
1016 void
gst_xwindow_destroy(GstXWindow * window)1017 gst_xwindow_destroy (GstXWindow * window)
1018 {
1019   GstXvContext *context;
1020 
1021   g_return_if_fail (window != NULL);
1022 
1023   context = window->context;
1024 
1025   g_mutex_lock (&context->lock);
1026 
1027   /* If we did not create that window we just free the GC and let it live */
1028   if (window->internal)
1029     XDestroyWindow (context->disp, window->win);
1030   else
1031     XSelectInput (context->disp, window->win, 0);
1032 
1033   XFreeGC (context->disp, window->gc);
1034 
1035   XSync (context->disp, FALSE);
1036 
1037   g_mutex_unlock (&context->lock);
1038 
1039   gst_xvcontext_unref (context);
1040 
1041   g_slice_free1 (sizeof (GstXWindow), window);
1042 }
1043 
1044 void
gst_xwindow_set_event_handling(GstXWindow * window,gboolean handle_events)1045 gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events)
1046 {
1047   GstXvContext *context;
1048 
1049   g_return_if_fail (window != NULL);
1050 
1051   context = window->context;
1052 
1053   g_mutex_lock (&context->lock);
1054   if (handle_events) {
1055     if (window->internal) {
1056       XSelectInput (context->disp, window->win,
1057           ExposureMask | StructureNotifyMask | PointerMotionMask |
1058           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1059     } else {
1060       XSelectInput (context->disp, window->win,
1061           ExposureMask | StructureNotifyMask | PointerMotionMask |
1062           KeyPressMask | KeyReleaseMask);
1063     }
1064   } else {
1065     XSelectInput (context->disp, window->win, 0);
1066   }
1067   g_mutex_unlock (&context->lock);
1068 }
1069 
1070 void
gst_xwindow_set_title(GstXWindow * window,const gchar * title)1071 gst_xwindow_set_title (GstXWindow * window, const gchar * title)
1072 {
1073   GstXvContext *context;
1074 
1075   g_return_if_fail (window != NULL);
1076 
1077   context = window->context;
1078 
1079   /* we have a window */
1080   if (window->internal && title) {
1081     XTextProperty xproperty;
1082     XClassHint *hint = XAllocClassHint ();
1083 
1084     if ((XStringListToTextProperty (((char **) &title), 1, &xproperty)) != 0) {
1085       XSetWMName (context->disp, window->win, &xproperty);
1086       XFree (xproperty.value);
1087 
1088       if (hint) {
1089         hint->res_name = (char *) title;
1090         hint->res_class = (char *) "GStreamer";
1091         XSetClassHint (context->disp, window->win, hint);
1092       }
1093       XFree (hint);
1094     }
1095   }
1096 }
1097 
1098 void
gst_xwindow_update_geometry(GstXWindow * window)1099 gst_xwindow_update_geometry (GstXWindow * window)
1100 {
1101   XWindowAttributes attr;
1102   GstXvContext *context;
1103 
1104   g_return_if_fail (window != NULL);
1105 
1106   context = window->context;
1107 
1108   /* Update the window geometry */
1109   g_mutex_lock (&context->lock);
1110   XGetWindowAttributes (context->disp, window->win, &attr);
1111 
1112   window->width = attr.width;
1113   window->height = attr.height;
1114 
1115   if (!window->have_render_rect) {
1116     window->render_rect.x = window->render_rect.y = 0;
1117     window->render_rect.w = attr.width;
1118     window->render_rect.h = attr.height;
1119   }
1120 
1121   g_mutex_unlock (&context->lock);
1122 }
1123 
1124 
1125 void
gst_xwindow_clear(GstXWindow * window)1126 gst_xwindow_clear (GstXWindow * window)
1127 {
1128   GstXvContext *context;
1129 
1130   g_return_if_fail (window != NULL);
1131 
1132   context = window->context;
1133 
1134   g_mutex_lock (&context->lock);
1135 
1136   XvStopVideo (context->disp, context->xv_port_id, window->win);
1137 
1138   XSync (context->disp, FALSE);
1139 
1140   g_mutex_unlock (&context->lock);
1141 }
1142 
1143 void
gst_xwindow_set_render_rectangle(GstXWindow * window,gint x,gint y,gint width,gint height)1144 gst_xwindow_set_render_rectangle (GstXWindow * window,
1145     gint x, gint y, gint width, gint height)
1146 {
1147   g_return_if_fail (window != NULL);
1148 
1149   if (width >= 0 && height >= 0) {
1150     window->render_rect.x = x;
1151     window->render_rect.y = y;
1152     window->render_rect.w = width;
1153     window->render_rect.h = height;
1154     window->have_render_rect = TRUE;
1155   } else {
1156     window->render_rect.x = 0;
1157     window->render_rect.y = 0;
1158     window->render_rect.w = window->width;
1159     window->render_rect.h = window->height;
1160     window->have_render_rect = FALSE;
1161   }
1162 }
1163