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