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