docs: fix up some more GstXOverlay -> GstVideoOverlay
[platform/upstream/gst-plugins-base.git] / sys / xvimage / xvimagesink.c
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 /**
22  * SECTION:element-xvimagesink
23  *
24  * XvImageSink renders video frames to a drawable (XWindow) on a local display
25  * using the XVideo extension. Rendering to a remote display is theoretically
26  * possible but i doubt that the XVideo extension is actually available when
27  * connecting to a remote display. This element can receive a Window ID from the
28  * application through the #GstVideoOverlay interface and will then render
29  * video frames in this drawable. If no Window ID was provided by the
30  * application, the element will create its own internal window and render
31  * into it.
32  *
33  * <refsect2>
34  * <title>Scaling</title>
35  * <para>
36  * The XVideo extension, when it's available, handles hardware accelerated
37  * scaling of video frames. This means that the element will just accept
38  * incoming video frames no matter their geometry and will then put them to the
39  * drawable scaling them on the fly. Using the #GstXvImageSink:force-aspect-ratio
40  * property it is possible to enforce scaling with a constant aspect ratio,
41  * which means drawing black borders around the video frame.
42  * </para>
43  * </refsect2>
44  * <refsect2>
45  * <title>Events</title>
46  * <para>
47  * XvImageSink creates a thread to handle events coming from the drawable. There
48  * are several kind of events that can be grouped in 2 big categories: input
49  * events and window state related events. Input events will be translated to
50  * navigation events and pushed upstream for other elements to react on them.
51  * This includes events such as pointer moves, key press/release, clicks etc...
52  * Other events are used to handle the drawable appearance even when the data
53  * is not flowing (GST_STATE_PAUSED). That means that even when the element is
54  * paused, it will receive expose events from the drawable and draw the latest
55  * frame with correct borders/aspect-ratio.
56  * </para>
57  * </refsect2>
58  * <refsect2>
59  * <title>Pixel aspect ratio</title>
60  * <para>
61  * When changing state to GST_STATE_READY, XvImageSink will open a connection to
62  * the display specified in the #GstXvImageSink:display property or the
63  * default display if nothing specified. Once this connection is open it will
64  * inspect the display configuration including the physical display geometry and
65  * then calculate the pixel aspect ratio. When receiving video frames with a
66  * different pixel aspect ratio, XvImageSink will use hardware scaling to
67  * display the video frames correctly on display's pixel aspect ratio.
68  * Sometimes the calculated pixel aspect ratio can be wrong, it is
69  * then possible to enforce a specific pixel aspect ratio using the
70  * #GstXvImageSink:pixel-aspect-ratio property.
71  * </para>
72  * </refsect2>
73  * <refsect2>
74  * <title>Examples</title>
75  * |[
76  * gst-launch -v videotestsrc ! xvimagesink
77  * ]| A pipeline to test hardware scaling.
78  * When the test video signal appears you can resize the window and see that
79  * video frames are scaled through hardware (no extra CPU cost).
80  * |[
81  * gst-launch -v videotestsrc ! xvimagesink force-aspect-ratio=true
82  * ]| Same pipeline with #GstXvImageSink:force-aspect-ratio property set to true
83  * You can observe the borders drawn around the scaled image respecting aspect
84  * ratio.
85  * |[
86  * gst-launch -v videotestsrc ! navigationtest ! xvimagesink
87  * ]| A pipeline to test navigation events.
88  * While moving the mouse pointer over the test signal you will see a black box
89  * following the mouse pointer. If you press the mouse button somewhere on the
90  * video and release it somewhere else a green box will appear where you pressed
91  * the button and a red one where you released it. (The navigationtest element
92  * is part of gst-plugins-good.) You can observe here that even if the images
93  * are scaled through hardware the pointer coordinates are converted back to the
94  * original video frame geometry so that the box can be drawn to the correct
95  * position. This also handles borders correctly, limiting coordinates to the
96  * image area
97  * |[
98  * gst-launch -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! xvimagesink
99  * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
100  * videotestsrc, in most cases the pixel aspect ratio of the display will be
101  * 1/1. This means that XvImageSink will have to do the scaling to convert
102  * incoming frames to a size that will match the display pixel aspect ratio
103  * (from 320x240 to 320x180 in this case). Note that you might have to escape
104  * some characters for your shell like '\(fraction\)'.
105  * |[
106  * gst-launch -v videotestsrc ! xvimagesink hue=100 saturation=-100 brightness=100
107  * ]| Demonstrates how to use the colorbalance interface.
108  * </refsect2>
109  */
110
111 /* for developers: there are two useful tools : xvinfo and xvattr */
112
113 #ifdef HAVE_CONFIG_H
114 #include "config.h"
115 #endif
116
117 /* Our interfaces */
118 #include <gst/video/navigation.h>
119 #include <gst/video/videooverlay.h>
120 #include <gst/video/colorbalance.h>
121 /* Helper functions */
122 #include <gst/video/gstvideometa.h>
123
124 /* Object header */
125 #include "xvimagesink.h"
126
127 /* Debugging category */
128 #include <gst/gstinfo.h>
129
130 /* for XkbKeycodeToKeysym */
131 #include <X11/XKBlib.h>
132
133 GST_DEBUG_CATEGORY_EXTERN (gst_debug_xvimagesink);
134 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
135 #define GST_CAT_DEFAULT gst_debug_xvimagesink
136
137 typedef struct
138 {
139   unsigned long flags;
140   unsigned long functions;
141   unsigned long decorations;
142   long input_mode;
143   unsigned long status;
144 }
145 MotifWmHints, MwmHints;
146
147 #define MWM_HINTS_DECORATIONS   (1L << 1)
148
149 static void gst_xvimagesink_reset (GstXvImageSink * xvimagesink);
150 static void gst_xvimagesink_xwindow_update_geometry (GstXvImageSink *
151     xvimagesink);
152 static void gst_xvimagesink_expose (GstVideoOverlay * overlay);
153
154 /* Default template - initiated with class struct to allow gst-register to work
155    without X running */
156 static GstStaticPadTemplate gst_xvimagesink_sink_template_factory =
157 GST_STATIC_PAD_TEMPLATE ("sink",
158     GST_PAD_SINK,
159     GST_PAD_ALWAYS,
160     GST_STATIC_CAPS ("video/x-raw, "
161         "framerate = (fraction) [ 0, MAX ], "
162         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
163     );
164
165 enum
166 {
167   PROP_0,
168   PROP_CONTRAST,
169   PROP_BRIGHTNESS,
170   PROP_HUE,
171   PROP_SATURATION,
172   PROP_DISPLAY,
173   PROP_SYNCHRONOUS,
174   PROP_PIXEL_ASPECT_RATIO,
175   PROP_FORCE_ASPECT_RATIO,
176   PROP_HANDLE_EVENTS,
177   PROP_DEVICE,
178   PROP_DEVICE_NAME,
179   PROP_HANDLE_EXPOSE,
180   PROP_DOUBLE_BUFFER,
181   PROP_AUTOPAINT_COLORKEY,
182   PROP_COLORKEY,
183   PROP_DRAW_BORDERS,
184   PROP_WINDOW_WIDTH,
185   PROP_WINDOW_HEIGHT
186 };
187
188 /* ============================================================= */
189 /*                                                               */
190 /*                       Public Methods                          */
191 /*                                                               */
192 /* ============================================================= */
193
194 /* =========================================== */
195 /*                                             */
196 /*          Object typing & Creation           */
197 /*                                             */
198 /* =========================================== */
199 static void gst_xvimagesink_navigation_init (GstNavigationInterface * iface);
200 static void gst_xvimagesink_video_overlay_init (GstVideoOverlayInterface *
201     iface);
202 static void gst_xvimagesink_colorbalance_init (GstColorBalanceInterface *
203     iface);
204 #define gst_xvimagesink_parent_class parent_class
205 G_DEFINE_TYPE_WITH_CODE (GstXvImageSink, gst_xvimagesink, GST_TYPE_VIDEO_SINK,
206     G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
207         gst_xvimagesink_navigation_init);
208     G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
209         gst_xvimagesink_video_overlay_init);
210     G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
211         gst_xvimagesink_colorbalance_init));
212
213
214 /* ============================================================= */
215 /*                                                               */
216 /*                       Private Methods                         */
217 /*                                                               */
218 /* ============================================================= */
219
220
221 /* We are called with the x_lock taken */
222 static void
223 gst_xvimagesink_xwindow_draw_borders (GstXvImageSink * xvimagesink,
224     GstXWindow * xwindow, GstVideoRectangle rect)
225 {
226   gint t1, t2;
227
228   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
229   g_return_if_fail (xwindow != NULL);
230
231   XSetForeground (xvimagesink->xcontext->disp, xwindow->gc,
232       xvimagesink->xcontext->black);
233
234   /* Left border */
235   if (rect.x > xvimagesink->render_rect.x) {
236     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
237         xvimagesink->render_rect.x, xvimagesink->render_rect.y,
238         rect.x - xvimagesink->render_rect.x, xvimagesink->render_rect.h);
239   }
240
241   /* Right border */
242   t1 = rect.x + rect.w;
243   t2 = xvimagesink->render_rect.x + xvimagesink->render_rect.w;
244   if (t1 < t2) {
245     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
246         t1, xvimagesink->render_rect.y, t2 - t1, xvimagesink->render_rect.h);
247   }
248
249   /* Top border */
250   if (rect.y > xvimagesink->render_rect.y) {
251     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
252         xvimagesink->render_rect.x, xvimagesink->render_rect.y,
253         xvimagesink->render_rect.w, rect.y - xvimagesink->render_rect.y);
254   }
255
256   /* Bottom border */
257   t1 = rect.y + rect.h;
258   t2 = xvimagesink->render_rect.y + xvimagesink->render_rect.h;
259   if (t1 < t2) {
260     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
261         xvimagesink->render_rect.x, t1, xvimagesink->render_rect.w, t2 - t1);
262   }
263 }
264
265 /* This function puts a GstXvImage on a GstXvImageSink's window. Returns FALSE
266  * if no window was available  */
267 static gboolean
268 gst_xvimagesink_xvimage_put (GstXvImageSink * xvimagesink, GstBuffer * xvimage)
269 {
270   GstXvImageMeta *meta;
271   GstVideoCropMeta *crop;
272   GstVideoRectangle result;
273   gboolean draw_border = FALSE;
274   GstVideoRectangle src, dst;
275
276   /* We take the flow_lock. If expose is in there we don't want to run
277      concurrently from the data flow thread */
278   g_mutex_lock (&xvimagesink->flow_lock);
279
280   if (G_UNLIKELY (xvimagesink->xwindow == NULL)) {
281     g_mutex_unlock (&xvimagesink->flow_lock);
282     return FALSE;
283   }
284
285   /* Draw borders when displaying the first frame. After this
286      draw borders only on expose event or after a size change. */
287   if (!xvimagesink->cur_image || xvimagesink->redraw_border) {
288     draw_border = TRUE;
289   }
290
291   /* Store a reference to the last image we put, lose the previous one */
292   if (xvimage && xvimagesink->cur_image != xvimage) {
293     if (xvimagesink->cur_image) {
294       GST_LOG_OBJECT (xvimagesink, "unreffing %p", xvimagesink->cur_image);
295       gst_buffer_unref (xvimagesink->cur_image);
296     }
297     GST_LOG_OBJECT (xvimagesink, "reffing %p as our current image", xvimage);
298     xvimagesink->cur_image = gst_buffer_ref (xvimage);
299   }
300
301   /* Expose sends a NULL image, we take the latest frame */
302   if (!xvimage) {
303     if (xvimagesink->cur_image) {
304       draw_border = TRUE;
305       xvimage = xvimagesink->cur_image;
306     } else {
307       g_mutex_unlock (&xvimagesink->flow_lock);
308       return TRUE;
309     }
310   }
311
312   meta = gst_buffer_get_xvimage_meta (xvimage);
313
314   crop = gst_buffer_get_video_crop_meta (xvimage);
315
316   if (crop) {
317     src.x = crop->x + meta->x;
318     src.y = crop->y + meta->y;
319     src.w = crop->width;
320     src.h = crop->height;
321     GST_LOG_OBJECT (xvimagesink,
322         "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
323   } else {
324     src.x = meta->x;
325     src.y = meta->y;
326     src.w = meta->width;
327     src.h = meta->height;
328   }
329
330   if (xvimagesink->keep_aspect) {
331     GstVideoRectangle s;
332
333     /* We take the size of the source material as it was negotiated and
334      * corrected for DAR. This size can be different from the cropped size in
335      * which case the image will be scaled to fit the negotiated size. */
336     s.w = GST_VIDEO_SINK_WIDTH (xvimagesink);
337     s.h = GST_VIDEO_SINK_HEIGHT (xvimagesink);
338     dst.w = xvimagesink->render_rect.w;
339     dst.h = xvimagesink->render_rect.h;
340
341     gst_video_sink_center_rect (s, dst, &result, TRUE);
342     result.x += xvimagesink->render_rect.x;
343     result.y += xvimagesink->render_rect.y;
344   } else {
345     memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
346   }
347
348   g_mutex_lock (&xvimagesink->x_lock);
349
350   if (draw_border && xvimagesink->draw_borders) {
351     gst_xvimagesink_xwindow_draw_borders (xvimagesink, xvimagesink->xwindow,
352         result);
353     xvimagesink->redraw_border = FALSE;
354   }
355 #ifdef HAVE_XSHM
356   if (xvimagesink->xcontext->use_xshm) {
357     GST_LOG_OBJECT (xvimagesink,
358         "XvShmPutImage with image %dx%d and window %dx%d, from xvimage %"
359         GST_PTR_FORMAT, meta->width, meta->height,
360         xvimagesink->render_rect.w, xvimagesink->render_rect.h, xvimage);
361
362     XvShmPutImage (xvimagesink->xcontext->disp,
363         xvimagesink->xcontext->xv_port_id,
364         xvimagesink->xwindow->win,
365         xvimagesink->xwindow->gc, meta->xvimage,
366         src.x, src.y, src.w, src.h,
367         result.x, result.y, result.w, result.h, FALSE);
368   } else
369 #endif /* HAVE_XSHM */
370   {
371     XvPutImage (xvimagesink->xcontext->disp,
372         xvimagesink->xcontext->xv_port_id,
373         xvimagesink->xwindow->win,
374         xvimagesink->xwindow->gc, meta->xvimage,
375         src.x, src.y, src.w, src.h, result.x, result.y, result.w, result.h);
376   }
377
378   XSync (xvimagesink->xcontext->disp, FALSE);
379
380   g_mutex_unlock (&xvimagesink->x_lock);
381
382   g_mutex_unlock (&xvimagesink->flow_lock);
383
384   return TRUE;
385 }
386
387 static gboolean
388 gst_xvimagesink_xwindow_decorate (GstXvImageSink * xvimagesink,
389     GstXWindow * window)
390 {
391   Atom hints_atom = None;
392   MotifWmHints *hints;
393
394   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), FALSE);
395   g_return_val_if_fail (window != NULL, FALSE);
396
397   g_mutex_lock (&xvimagesink->x_lock);
398
399   hints_atom = XInternAtom (xvimagesink->xcontext->disp, "_MOTIF_WM_HINTS",
400       True);
401   if (hints_atom == None) {
402     g_mutex_unlock (&xvimagesink->x_lock);
403     return FALSE;
404   }
405
406   hints = g_malloc0 (sizeof (MotifWmHints));
407
408   hints->flags |= MWM_HINTS_DECORATIONS;
409   hints->decorations = 1 << 0;
410
411   XChangeProperty (xvimagesink->xcontext->disp, window->win,
412       hints_atom, hints_atom, 32, PropModeReplace,
413       (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
414
415   XSync (xvimagesink->xcontext->disp, FALSE);
416
417   g_mutex_unlock (&xvimagesink->x_lock);
418
419   g_free (hints);
420
421   return TRUE;
422 }
423
424 static void
425 gst_xvimagesink_xwindow_set_title (GstXvImageSink * xvimagesink,
426     GstXWindow * xwindow, const gchar * media_title)
427 {
428   if (media_title) {
429     g_free (xvimagesink->media_title);
430     xvimagesink->media_title = g_strdup (media_title);
431   }
432   if (xwindow) {
433     /* we have a window */
434     if (xwindow->internal) {
435       XTextProperty xproperty;
436       const gchar *app_name;
437       const gchar *title = NULL;
438       gchar *title_mem = NULL;
439
440       /* set application name as a title */
441       app_name = g_get_application_name ();
442
443       if (app_name && xvimagesink->media_title) {
444         title = title_mem = g_strconcat (xvimagesink->media_title, " : ",
445             app_name, NULL);
446       } else if (app_name) {
447         title = app_name;
448       } else if (xvimagesink->media_title) {
449         title = xvimagesink->media_title;
450       }
451
452       if (title) {
453         if ((XStringListToTextProperty (((char **) &title), 1,
454                     &xproperty)) != 0) {
455           XSetWMName (xvimagesink->xcontext->disp, xwindow->win, &xproperty);
456           XFree (xproperty.value);
457         }
458
459         g_free (title_mem);
460       }
461     }
462   }
463 }
464
465 /* This function handles a GstXWindow creation
466  * The width and height are the actual pixel size on the display */
467 static GstXWindow *
468 gst_xvimagesink_xwindow_new (GstXvImageSink * xvimagesink,
469     gint width, gint height)
470 {
471   GstXWindow *xwindow = NULL;
472   XGCValues values;
473
474   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
475
476   xwindow = g_new0 (GstXWindow, 1);
477
478   xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
479   xvimagesink->render_rect.w = width;
480   xvimagesink->render_rect.h = height;
481
482   xwindow->width = width;
483   xwindow->height = height;
484   xwindow->internal = TRUE;
485
486   g_mutex_lock (&xvimagesink->x_lock);
487
488   xwindow->win = XCreateSimpleWindow (xvimagesink->xcontext->disp,
489       xvimagesink->xcontext->root,
490       0, 0, width, height, 0, 0, xvimagesink->xcontext->black);
491
492   /* We have to do that to prevent X from redrawing the background on
493    * ConfigureNotify. This takes away flickering of video when resizing. */
494   XSetWindowBackgroundPixmap (xvimagesink->xcontext->disp, xwindow->win, None);
495
496   /* set application name as a title */
497   gst_xvimagesink_xwindow_set_title (xvimagesink, xwindow, NULL);
498
499   if (xvimagesink->handle_events) {
500     Atom wm_delete;
501
502     XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask |
503         StructureNotifyMask | PointerMotionMask | KeyPressMask |
504         KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
505
506     /* Tell the window manager we'd like delete client messages instead of
507      * being killed */
508     wm_delete = XInternAtom (xvimagesink->xcontext->disp,
509         "WM_DELETE_WINDOW", True);
510     if (wm_delete != None) {
511       (void) XSetWMProtocols (xvimagesink->xcontext->disp, xwindow->win,
512           &wm_delete, 1);
513     }
514   }
515
516   xwindow->gc = XCreateGC (xvimagesink->xcontext->disp,
517       xwindow->win, 0, &values);
518
519   XMapRaised (xvimagesink->xcontext->disp, xwindow->win);
520
521   XSync (xvimagesink->xcontext->disp, FALSE);
522
523   g_mutex_unlock (&xvimagesink->x_lock);
524
525   gst_xvimagesink_xwindow_decorate (xvimagesink, xwindow);
526
527   gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (xvimagesink),
528       xwindow->win);
529
530   return xwindow;
531 }
532
533 /* This function destroys a GstXWindow */
534 static void
535 gst_xvimagesink_xwindow_destroy (GstXvImageSink * xvimagesink,
536     GstXWindow * xwindow)
537 {
538   g_return_if_fail (xwindow != NULL);
539   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
540
541   g_mutex_lock (&xvimagesink->x_lock);
542
543   /* If we did not create that window we just free the GC and let it live */
544   if (xwindow->internal)
545     XDestroyWindow (xvimagesink->xcontext->disp, xwindow->win);
546   else
547     XSelectInput (xvimagesink->xcontext->disp, xwindow->win, 0);
548
549   XFreeGC (xvimagesink->xcontext->disp, xwindow->gc);
550
551   XSync (xvimagesink->xcontext->disp, FALSE);
552
553   g_mutex_unlock (&xvimagesink->x_lock);
554
555   g_free (xwindow);
556 }
557
558 static void
559 gst_xvimagesink_xwindow_update_geometry (GstXvImageSink * xvimagesink)
560 {
561   XWindowAttributes attr;
562
563   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
564
565   /* Update the window geometry */
566   g_mutex_lock (&xvimagesink->x_lock);
567   if (G_UNLIKELY (xvimagesink->xwindow == NULL)) {
568     g_mutex_unlock (&xvimagesink->x_lock);
569     return;
570   }
571
572   XGetWindowAttributes (xvimagesink->xcontext->disp,
573       xvimagesink->xwindow->win, &attr);
574
575   xvimagesink->xwindow->width = attr.width;
576   xvimagesink->xwindow->height = attr.height;
577
578   if (!xvimagesink->have_render_rect) {
579     xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
580     xvimagesink->render_rect.w = attr.width;
581     xvimagesink->render_rect.h = attr.height;
582   }
583
584   g_mutex_unlock (&xvimagesink->x_lock);
585 }
586
587 static void
588 gst_xvimagesink_xwindow_clear (GstXvImageSink * xvimagesink,
589     GstXWindow * xwindow)
590 {
591   g_return_if_fail (xwindow != NULL);
592   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
593
594   g_mutex_lock (&xvimagesink->x_lock);
595
596   XvStopVideo (xvimagesink->xcontext->disp, xvimagesink->xcontext->xv_port_id,
597       xwindow->win);
598
599   XSync (xvimagesink->xcontext->disp, FALSE);
600
601   g_mutex_unlock (&xvimagesink->x_lock);
602 }
603
604 /* This function commits our internal colorbalance settings to our grabbed Xv
605    port. If the xcontext is not initialized yet it simply returns */
606 static void
607 gst_xvimagesink_update_colorbalance (GstXvImageSink * xvimagesink)
608 {
609   GList *channels = NULL;
610
611   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
612
613   /* If we haven't initialized the X context we can't update anything */
614   if (xvimagesink->xcontext == NULL)
615     return;
616
617   /* Don't set the attributes if they haven't been changed, to avoid
618    * rounding errors changing the values */
619   if (!xvimagesink->cb_changed)
620     return;
621
622   /* For each channel of the colorbalance we calculate the correct value
623      doing range conversion and then set the Xv port attribute to match our
624      values. */
625   channels = xvimagesink->xcontext->channels_list;
626
627   while (channels) {
628     if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) {
629       GstColorBalanceChannel *channel = NULL;
630       Atom prop_atom;
631       gint value = 0;
632       gdouble convert_coef;
633
634       channel = GST_COLOR_BALANCE_CHANNEL (channels->data);
635       g_object_ref (channel);
636
637       /* Our range conversion coef */
638       convert_coef = (channel->max_value - channel->min_value) / 2000.0;
639
640       if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
641         value = xvimagesink->hue;
642       } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
643         value = xvimagesink->saturation;
644       } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
645         value = xvimagesink->contrast;
646       } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
647         value = xvimagesink->brightness;
648       } else {
649         g_warning ("got an unknown channel %s", channel->label);
650         g_object_unref (channel);
651         return;
652       }
653
654       /* Committing to Xv port */
655       g_mutex_lock (&xvimagesink->x_lock);
656       prop_atom =
657           XInternAtom (xvimagesink->xcontext->disp, channel->label, True);
658       if (prop_atom != None) {
659         int xv_value;
660         xv_value =
661             floor (0.5 + (value + 1000) * convert_coef + channel->min_value);
662         XvSetPortAttribute (xvimagesink->xcontext->disp,
663             xvimagesink->xcontext->xv_port_id, prop_atom, xv_value);
664       }
665       g_mutex_unlock (&xvimagesink->x_lock);
666
667       g_object_unref (channel);
668     }
669     channels = g_list_next (channels);
670   }
671 }
672
673 /* This function handles XEvents that might be in the queue. It generates
674    GstEvent that will be sent upstream in the pipeline to handle interactivity
675    and navigation. It will also listen for configure events on the window to
676    trigger caps renegotiation so on the fly software scaling can work. */
677 static void
678 gst_xvimagesink_handle_xevents (GstXvImageSink * xvimagesink)
679 {
680   XEvent e;
681   guint pointer_x = 0, pointer_y = 0;
682   gboolean pointer_moved = FALSE;
683   gboolean exposed = FALSE, configured = FALSE;
684
685   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
686
687   /* Handle Interaction, produces navigation events */
688
689   /* We get all pointer motion events, only the last position is
690      interesting. */
691   g_mutex_lock (&xvimagesink->flow_lock);
692   g_mutex_lock (&xvimagesink->x_lock);
693   while (XCheckWindowEvent (xvimagesink->xcontext->disp,
694           xvimagesink->xwindow->win, PointerMotionMask, &e)) {
695     g_mutex_unlock (&xvimagesink->x_lock);
696     g_mutex_unlock (&xvimagesink->flow_lock);
697
698     switch (e.type) {
699       case MotionNotify:
700         pointer_x = e.xmotion.x;
701         pointer_y = e.xmotion.y;
702         pointer_moved = TRUE;
703         break;
704       default:
705         break;
706     }
707     g_mutex_lock (&xvimagesink->flow_lock);
708     g_mutex_lock (&xvimagesink->x_lock);
709   }
710
711   if (pointer_moved) {
712     g_mutex_unlock (&xvimagesink->x_lock);
713     g_mutex_unlock (&xvimagesink->flow_lock);
714
715     GST_DEBUG ("xvimagesink pointer moved over window at %d,%d",
716         pointer_x, pointer_y);
717     gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
718         "mouse-move", 0, e.xbutton.x, e.xbutton.y);
719
720     g_mutex_lock (&xvimagesink->flow_lock);
721     g_mutex_lock (&xvimagesink->x_lock);
722   }
723
724   /* We get all events on our window to throw them upstream */
725   while (XCheckWindowEvent (xvimagesink->xcontext->disp,
726           xvimagesink->xwindow->win,
727           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask,
728           &e)) {
729     KeySym keysym;
730     const char *key_str = NULL;
731
732     /* We lock only for the X function call */
733     g_mutex_unlock (&xvimagesink->x_lock);
734     g_mutex_unlock (&xvimagesink->flow_lock);
735
736     switch (e.type) {
737       case ButtonPress:
738         /* Mouse button pressed over our window. We send upstream
739            events for interactivity/navigation */
740         GST_DEBUG ("xvimagesink button %d pressed over window at %d,%d",
741             e.xbutton.button, e.xbutton.x, e.xbutton.y);
742         gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
743             "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
744         break;
745       case ButtonRelease:
746         /* Mouse button released over our window. We send upstream
747            events for interactivity/navigation */
748         GST_DEBUG ("xvimagesink button %d released over window at %d,%d",
749             e.xbutton.button, e.xbutton.x, e.xbutton.y);
750         gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
751             "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
752         break;
753       case KeyPress:
754       case KeyRelease:
755         /* Key pressed/released over our window. We send upstream
756            events for interactivity/navigation */
757         g_mutex_lock (&xvimagesink->x_lock);
758         keysym = XkbKeycodeToKeysym (xvimagesink->xcontext->disp,
759             e.xkey.keycode, 0, 0);
760         if (keysym != NoSymbol) {
761           key_str = XKeysymToString (keysym);
762         } else {
763           key_str = "unknown";
764         }
765         g_mutex_unlock (&xvimagesink->x_lock);
766         GST_DEBUG_OBJECT (xvimagesink,
767             "key %d pressed over window at %d,%d (%s)",
768             e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
769         gst_navigation_send_key_event (GST_NAVIGATION (xvimagesink),
770             e.type == KeyPress ? "key-press" : "key-release", key_str);
771         break;
772       default:
773         GST_DEBUG_OBJECT (xvimagesink, "xvimagesink unhandled X event (%d)",
774             e.type);
775     }
776     g_mutex_lock (&xvimagesink->flow_lock);
777     g_mutex_lock (&xvimagesink->x_lock);
778   }
779
780   /* Handle Expose */
781   while (XCheckWindowEvent (xvimagesink->xcontext->disp,
782           xvimagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
783     switch (e.type) {
784       case Expose:
785         exposed = TRUE;
786         break;
787       case ConfigureNotify:
788         g_mutex_unlock (&xvimagesink->x_lock);
789         gst_xvimagesink_xwindow_update_geometry (xvimagesink);
790         g_mutex_lock (&xvimagesink->x_lock);
791         configured = TRUE;
792         break;
793       default:
794         break;
795     }
796   }
797
798   if (xvimagesink->handle_expose && (exposed || configured)) {
799     g_mutex_unlock (&xvimagesink->x_lock);
800     g_mutex_unlock (&xvimagesink->flow_lock);
801
802     gst_xvimagesink_expose (GST_VIDEO_OVERLAY (xvimagesink));
803
804     g_mutex_lock (&xvimagesink->flow_lock);
805     g_mutex_lock (&xvimagesink->x_lock);
806   }
807
808   /* Handle Display events */
809   while (XPending (xvimagesink->xcontext->disp)) {
810     XNextEvent (xvimagesink->xcontext->disp, &e);
811
812     switch (e.type) {
813       case ClientMessage:{
814         Atom wm_delete;
815
816         wm_delete = XInternAtom (xvimagesink->xcontext->disp,
817             "WM_DELETE_WINDOW", True);
818         if (wm_delete != None && wm_delete == (Atom) e.xclient.data.l[0]) {
819           /* Handle window deletion by posting an error on the bus */
820           GST_ELEMENT_ERROR (xvimagesink, RESOURCE, NOT_FOUND,
821               ("Output window was closed"), (NULL));
822
823           g_mutex_unlock (&xvimagesink->x_lock);
824           gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
825           xvimagesink->xwindow = NULL;
826           g_mutex_lock (&xvimagesink->x_lock);
827         }
828         break;
829       }
830       default:
831         break;
832     }
833   }
834
835   g_mutex_unlock (&xvimagesink->x_lock);
836   g_mutex_unlock (&xvimagesink->flow_lock);
837 }
838
839 static void
840 gst_lookup_xv_port_from_adaptor (GstXContext * xcontext,
841     XvAdaptorInfo * adaptors, int adaptor_no)
842 {
843   gint j;
844   gint res;
845
846   /* Do we support XvImageMask ? */
847   if (!(adaptors[adaptor_no].type & XvImageMask)) {
848     GST_DEBUG ("XV Adaptor %s has no support for XvImageMask",
849         adaptors[adaptor_no].name);
850     return;
851   }
852
853   /* We found such an adaptor, looking for an available port */
854   for (j = 0; j < adaptors[adaptor_no].num_ports && !xcontext->xv_port_id; j++) {
855     /* We try to grab the port */
856     res = XvGrabPort (xcontext->disp, adaptors[adaptor_no].base_id + j, 0);
857     if (Success == res) {
858       xcontext->xv_port_id = adaptors[adaptor_no].base_id + j;
859       GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_no].name,
860           adaptors[adaptor_no].num_ports);
861     } else {
862       GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j,
863           adaptors[adaptor_no].name, res);
864     }
865   }
866 }
867
868 /* This function generates a caps with all supported format by the first
869    Xv grabable port we find. We store each one of the supported formats in a
870    format list and append the format to a newly created caps that we return
871    If this function does not return NULL because of an error, it also grabs
872    the port via XvGrabPort */
873 static GstCaps *
874 gst_xvimagesink_get_xv_support (GstXvImageSink * xvimagesink,
875     GstXContext * xcontext)
876 {
877   gint i;
878   XvAdaptorInfo *adaptors;
879   gint nb_formats;
880   XvImageFormatValues *formats = NULL;
881   guint nb_encodings;
882   XvEncodingInfo *encodings = NULL;
883   gulong max_w = G_MAXINT, max_h = G_MAXINT;
884   GstCaps *caps = NULL;
885   GstCaps *rgb_caps = NULL;
886
887   g_return_val_if_fail (xcontext != NULL, NULL);
888
889   /* First let's check that XVideo extension is available */
890   if (!XQueryExtension (xcontext->disp, "XVideo", &i, &i, &i)) {
891     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS,
892         ("Could not initialise Xv output"),
893         ("XVideo extension is not available"));
894     return NULL;
895   }
896
897   /* Then we get adaptors list */
898   if (Success != XvQueryAdaptors (xcontext->disp, xcontext->root,
899           &xcontext->nb_adaptors, &adaptors)) {
900     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS,
901         ("Could not initialise Xv output"),
902         ("Failed getting XV adaptors list"));
903     return NULL;
904   }
905
906   xcontext->xv_port_id = 0;
907
908   GST_DEBUG ("Found %u XV adaptor(s)", xcontext->nb_adaptors);
909
910   xcontext->adaptors =
911       (gchar **) g_malloc0 (xcontext->nb_adaptors * sizeof (gchar *));
912
913   /* Now fill up our adaptor name array */
914   for (i = 0; i < xcontext->nb_adaptors; i++) {
915     xcontext->adaptors[i] = g_strdup (adaptors[i].name);
916   }
917
918   if (xvimagesink->adaptor_no != -1 &&
919       xvimagesink->adaptor_no < xcontext->nb_adaptors) {
920     /* Find xv port from user defined adaptor */
921     gst_lookup_xv_port_from_adaptor (xcontext, adaptors,
922         xvimagesink->adaptor_no);
923   }
924
925   if (!xcontext->xv_port_id) {
926     /* Now search for an adaptor that supports XvImageMask */
927     for (i = 0; i < xcontext->nb_adaptors && !xcontext->xv_port_id; i++) {
928       gst_lookup_xv_port_from_adaptor (xcontext, adaptors, i);
929       xvimagesink->adaptor_no = i;
930     }
931   }
932
933   XvFreeAdaptorInfo (adaptors);
934
935   if (!xcontext->xv_port_id) {
936     xvimagesink->adaptor_no = -1;
937     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, BUSY,
938         ("Could not initialise Xv output"), ("No port available"));
939     return NULL;
940   }
941
942   /* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */
943   {
944     int count, todo = 3;
945     XvAttribute *const attr = XvQueryPortAttributes (xcontext->disp,
946         xcontext->xv_port_id, &count);
947     static const char autopaint[] = "XV_AUTOPAINT_COLORKEY";
948     static const char dbl_buffer[] = "XV_DOUBLE_BUFFER";
949     static const char colorkey[] = "XV_COLORKEY";
950
951     GST_DEBUG_OBJECT (xvimagesink, "Checking %d Xv port attributes", count);
952
953     xvimagesink->have_autopaint_colorkey = FALSE;
954     xvimagesink->have_double_buffer = FALSE;
955     xvimagesink->have_colorkey = FALSE;
956
957     for (i = 0; ((i < count) && todo); i++)
958       if (!strcmp (attr[i].name, autopaint)) {
959         const Atom atom = XInternAtom (xcontext->disp, autopaint, False);
960
961         /* turn on autopaint colorkey */
962         XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom,
963             (xvimagesink->autopaint_colorkey ? 1 : 0));
964         todo--;
965         xvimagesink->have_autopaint_colorkey = TRUE;
966       } else if (!strcmp (attr[i].name, dbl_buffer)) {
967         const Atom atom = XInternAtom (xcontext->disp, dbl_buffer, False);
968
969         XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom,
970             (xvimagesink->double_buffer ? 1 : 0));
971         todo--;
972         xvimagesink->have_double_buffer = TRUE;
973       } else if (!strcmp (attr[i].name, colorkey)) {
974         /* Set the colorkey, default is something that is dark but hopefully
975          * won't randomly appear on the screen elsewhere (ie not black or greys)
976          * can be overridden by setting "colorkey" property
977          */
978         const Atom atom = XInternAtom (xcontext->disp, colorkey, False);
979         guint32 ckey = 0;
980         gboolean set_attr = TRUE;
981         guint cr, cg, cb;
982
983         /* set a colorkey in the right format RGB565/RGB888
984          * We only handle these 2 cases, because they're the only types of
985          * devices we've encountered. If we don't recognise it, leave it alone
986          */
987         cr = (xvimagesink->colorkey >> 16);
988         cg = (xvimagesink->colorkey >> 8) & 0xFF;
989         cb = (xvimagesink->colorkey) & 0xFF;
990         switch (xcontext->depth) {
991           case 16:             /* RGB 565 */
992             cr >>= 3;
993             cg >>= 2;
994             cb >>= 3;
995             ckey = (cr << 11) | (cg << 5) | cb;
996             break;
997           case 24:
998           case 32:             /* RGB 888 / ARGB 8888 */
999             ckey = (cr << 16) | (cg << 8) | cb;
1000             break;
1001           default:
1002             GST_DEBUG_OBJECT (xvimagesink,
1003                 "Unknown bit depth %d for Xv Colorkey - not adjusting",
1004                 xcontext->depth);
1005             set_attr = FALSE;
1006             break;
1007         }
1008
1009         if (set_attr) {
1010           ckey = CLAMP (ckey, (guint32) attr[i].min_value,
1011               (guint32) attr[i].max_value);
1012           GST_LOG_OBJECT (xvimagesink,
1013               "Setting color key for display depth %d to 0x%x",
1014               xcontext->depth, ckey);
1015
1016           XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom,
1017               (gint) ckey);
1018         }
1019         todo--;
1020         xvimagesink->have_colorkey = TRUE;
1021       }
1022
1023     XFree (attr);
1024   }
1025
1026   /* Get the list of encodings supported by the adapter and look for the
1027    * XV_IMAGE encoding so we can determine the maximum width and height
1028    * supported */
1029   XvQueryEncodings (xcontext->disp, xcontext->xv_port_id, &nb_encodings,
1030       &encodings);
1031
1032   for (i = 0; i < nb_encodings; i++) {
1033     GST_LOG_OBJECT (xvimagesink,
1034         "Encoding %d, name %s, max wxh %lux%lu rate %d/%d",
1035         i, encodings[i].name, encodings[i].width, encodings[i].height,
1036         encodings[i].rate.numerator, encodings[i].rate.denominator);
1037     if (strcmp (encodings[i].name, "XV_IMAGE") == 0) {
1038       max_w = encodings[i].width;
1039       max_h = encodings[i].height;
1040     }
1041   }
1042
1043   XvFreeEncodingInfo (encodings);
1044
1045   /* We get all image formats supported by our port */
1046   formats = XvListImageFormats (xcontext->disp,
1047       xcontext->xv_port_id, &nb_formats);
1048   caps = gst_caps_new_empty ();
1049   for (i = 0; i < nb_formats; i++) {
1050     GstCaps *format_caps = NULL;
1051     gboolean is_rgb_format = FALSE;
1052     GstVideoFormat vformat;
1053
1054     /* We set the image format of the xcontext to an existing one. This
1055        is just some valid image format for making our xshm calls check before
1056        caps negotiation really happens. */
1057     xcontext->im_format = formats[i].id;
1058
1059     switch (formats[i].type) {
1060       case XvRGB:
1061       {
1062         XvImageFormatValues *fmt = &(formats[i]);
1063         gint endianness;
1064
1065         endianness =
1066             (fmt->byte_order == LSBFirst ? G_LITTLE_ENDIAN : G_BIG_ENDIAN);
1067
1068         vformat = gst_video_format_from_masks (fmt->depth, fmt->bits_per_pixel,
1069             endianness, fmt->red_mask, fmt->green_mask, fmt->blue_mask, 0);
1070         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
1071           break;
1072
1073         format_caps = gst_caps_new_simple ("video/x-raw",
1074             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
1075             "width", GST_TYPE_INT_RANGE, 1, max_w,
1076             "height", GST_TYPE_INT_RANGE, 1, max_h,
1077             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
1078
1079         is_rgb_format = TRUE;
1080         break;
1081       }
1082       case XvYUV:
1083       {
1084         vformat = gst_video_format_from_fourcc (formats[i].id);
1085         if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
1086           break;
1087
1088         format_caps = gst_caps_new_simple ("video/x-raw",
1089             "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
1090             "width", GST_TYPE_INT_RANGE, 1, max_w,
1091             "height", GST_TYPE_INT_RANGE, 1, max_h,
1092             "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
1093         break;
1094       }
1095       default:
1096         vformat = GST_VIDEO_FORMAT_UNKNOWN;
1097         g_assert_not_reached ();
1098         break;
1099     }
1100
1101     if (format_caps) {
1102       GstXvImageFormat *format = NULL;
1103
1104       format = g_new0 (GstXvImageFormat, 1);
1105       if (format) {
1106         format->format = formats[i].id;
1107         format->vformat = vformat;
1108         format->caps = gst_caps_copy (format_caps);
1109         xcontext->formats_list = g_list_append (xcontext->formats_list, format);
1110       }
1111
1112       if (is_rgb_format) {
1113         if (rgb_caps == NULL)
1114           rgb_caps = format_caps;
1115         else
1116           gst_caps_append (rgb_caps, format_caps);
1117       } else
1118         gst_caps_append (caps, format_caps);
1119     }
1120   }
1121
1122   /* Collected all caps into either the caps or rgb_caps structures.
1123    * Append rgb_caps on the end of YUV, so that YUV is always preferred */
1124   if (rgb_caps)
1125     gst_caps_append (caps, rgb_caps);
1126
1127   if (formats)
1128     XFree (formats);
1129
1130   GST_DEBUG ("Generated the following caps: %" GST_PTR_FORMAT, caps);
1131
1132   if (gst_caps_is_empty (caps)) {
1133     gst_caps_unref (caps);
1134     XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0);
1135     GST_ELEMENT_ERROR (xvimagesink, STREAM, WRONG_TYPE, (NULL),
1136         ("No supported format found"));
1137     return NULL;
1138   }
1139
1140   return caps;
1141 }
1142
1143 static gpointer
1144 gst_xvimagesink_event_thread (GstXvImageSink * xvimagesink)
1145 {
1146   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
1147
1148   GST_OBJECT_LOCK (xvimagesink);
1149   while (xvimagesink->running) {
1150     GST_OBJECT_UNLOCK (xvimagesink);
1151
1152     if (xvimagesink->xwindow) {
1153       gst_xvimagesink_handle_xevents (xvimagesink);
1154     }
1155     /* FIXME: do we want to align this with the framerate or anything else? */
1156     g_usleep (G_USEC_PER_SEC / 20);
1157
1158     GST_OBJECT_LOCK (xvimagesink);
1159   }
1160   GST_OBJECT_UNLOCK (xvimagesink);
1161
1162   return NULL;
1163 }
1164
1165 static void
1166 gst_xvimagesink_manage_event_thread (GstXvImageSink * xvimagesink)
1167 {
1168   GThread *thread = NULL;
1169
1170   /* don't start the thread too early */
1171   if (xvimagesink->xcontext == NULL) {
1172     return;
1173   }
1174
1175   GST_OBJECT_LOCK (xvimagesink);
1176   if (xvimagesink->handle_expose || xvimagesink->handle_events) {
1177     if (!xvimagesink->event_thread) {
1178       /* Setup our event listening thread */
1179       GST_DEBUG_OBJECT (xvimagesink, "run xevent thread, expose %d, events %d",
1180           xvimagesink->handle_expose, xvimagesink->handle_events);
1181       xvimagesink->running = TRUE;
1182       xvimagesink->event_thread = g_thread_try_new ("xvimagesink-events",
1183           (GThreadFunc) gst_xvimagesink_event_thread, xvimagesink, NULL);
1184     }
1185   } else {
1186     if (xvimagesink->event_thread) {
1187       GST_DEBUG_OBJECT (xvimagesink, "stop xevent thread, expose %d, events %d",
1188           xvimagesink->handle_expose, xvimagesink->handle_events);
1189       xvimagesink->running = FALSE;
1190       /* grab thread and mark it as NULL */
1191       thread = xvimagesink->event_thread;
1192       xvimagesink->event_thread = NULL;
1193     }
1194   }
1195   GST_OBJECT_UNLOCK (xvimagesink);
1196
1197   /* Wait for our event thread to finish */
1198   if (thread)
1199     g_thread_join (thread);
1200
1201 }
1202
1203
1204 /* This function calculates the pixel aspect ratio based on the properties
1205  * in the xcontext structure and stores it there. */
1206 static void
1207 gst_xvimagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
1208 {
1209   static const gint par[][2] = {
1210     {1, 1},                     /* regular screen */
1211     {16, 15},                   /* PAL TV */
1212     {11, 10},                   /* 525 line Rec.601 video */
1213     {54, 59},                   /* 625 line Rec.601 video */
1214     {64, 45},                   /* 1280x1024 on 16:9 display */
1215     {5, 3},                     /* 1280x1024 on 4:3 display */
1216     {4, 3}                      /*  800x600 on 16:9 display */
1217   };
1218   gint i;
1219   gint index;
1220   gdouble ratio;
1221   gdouble delta;
1222
1223 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
1224
1225   /* first calculate the "real" ratio based on the X values;
1226    * which is the "physical" w/h divided by the w/h in pixels of the display */
1227   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
1228       / (xcontext->heightmm * xcontext->width);
1229
1230   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
1231    * override here */
1232   if (xcontext->width == 720 && xcontext->height == 576) {
1233     ratio = 4.0 * 576 / (3.0 * 720);
1234   }
1235   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
1236
1237   /* now find the one from par[][2] with the lowest delta to the real one */
1238   delta = DELTA (0);
1239   index = 0;
1240
1241   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
1242     gdouble this_delta = DELTA (i);
1243
1244     if (this_delta < delta) {
1245       index = i;
1246       delta = this_delta;
1247     }
1248   }
1249
1250   GST_DEBUG ("Decided on index %d (%d/%d)", index,
1251       par[index][0], par[index][1]);
1252
1253   g_free (xcontext->par);
1254   xcontext->par = g_new0 (GValue, 1);
1255   g_value_init (xcontext->par, GST_TYPE_FRACTION);
1256   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
1257   GST_DEBUG ("set xcontext PAR to %d/%d",
1258       gst_value_get_fraction_numerator (xcontext->par),
1259       gst_value_get_fraction_denominator (xcontext->par));
1260 }
1261
1262 /* This function gets the X Display and global info about it. Everything is
1263    stored in our object and will be cleaned when the object is disposed. Note
1264    here that caps for supported format are generated without any window or
1265    image creation */
1266 static GstXContext *
1267 gst_xvimagesink_xcontext_get (GstXvImageSink * xvimagesink)
1268 {
1269   GstXContext *xcontext = NULL;
1270   XPixmapFormatValues *px_formats = NULL;
1271   gint nb_formats = 0, i, j, N_attr;
1272   XvAttribute *xv_attr;
1273   Atom prop_atom;
1274   const char *channels[4] = { "XV_HUE", "XV_SATURATION",
1275     "XV_BRIGHTNESS", "XV_CONTRAST"
1276   };
1277
1278   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
1279
1280   xcontext = g_new0 (GstXContext, 1);
1281   xcontext->im_format = 0;
1282
1283   g_mutex_lock (&xvimagesink->x_lock);
1284
1285   xcontext->disp = XOpenDisplay (xvimagesink->display_name);
1286
1287   if (!xcontext->disp) {
1288     g_mutex_unlock (&xvimagesink->x_lock);
1289     g_free (xcontext);
1290     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1291         ("Could not initialise Xv output"), ("Could not open display"));
1292     return NULL;
1293   }
1294
1295   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1296   xcontext->screen_num = DefaultScreen (xcontext->disp);
1297   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1298   xcontext->root = DefaultRootWindow (xcontext->disp);
1299   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1300   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1301   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1302
1303   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1304   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1305   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1306   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1307
1308   GST_DEBUG_OBJECT (xvimagesink, "X reports %dx%d pixels and %d mm x %d mm",
1309       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1310
1311   gst_xvimagesink_calculate_pixel_aspect_ratio (xcontext);
1312   /* We get supported pixmap formats at supported depth */
1313   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1314
1315   if (!px_formats) {
1316     XCloseDisplay (xcontext->disp);
1317     g_mutex_unlock (&xvimagesink->x_lock);
1318     g_free (xcontext->par);
1319     g_free (xcontext);
1320     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS,
1321         ("Could not initialise Xv output"), ("Could not get pixel formats"));
1322     return NULL;
1323   }
1324
1325   /* We get bpp value corresponding to our running depth */
1326   for (i = 0; i < nb_formats; i++) {
1327     if (px_formats[i].depth == xcontext->depth)
1328       xcontext->bpp = px_formats[i].bits_per_pixel;
1329   }
1330
1331   XFree (px_formats);
1332
1333   xcontext->endianness =
1334       (ImageByteOrder (xcontext->disp) ==
1335       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1336
1337   /* our caps system handles 24/32bpp RGB as big-endian. */
1338   if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
1339       xcontext->endianness == G_LITTLE_ENDIAN) {
1340     xcontext->endianness = G_BIG_ENDIAN;
1341     xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask);
1342     xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask);
1343     xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask);
1344     if (xcontext->bpp == 24) {
1345       xcontext->visual->red_mask >>= 8;
1346       xcontext->visual->green_mask >>= 8;
1347       xcontext->visual->blue_mask >>= 8;
1348     }
1349   }
1350
1351   xcontext->caps = gst_xvimagesink_get_xv_support (xvimagesink, xcontext);
1352
1353   /* Search for XShm extension support */
1354 #ifdef HAVE_XSHM
1355   if (XShmQueryExtension (xcontext->disp) &&
1356       gst_xvimagesink_check_xshm_calls (xvimagesink, xcontext)) {
1357     xcontext->use_xshm = TRUE;
1358     GST_DEBUG ("xvimagesink is using XShm extension");
1359   } else
1360 #endif /* HAVE_XSHM */
1361   {
1362     xcontext->use_xshm = FALSE;
1363     GST_DEBUG ("xvimagesink is not using XShm extension");
1364   }
1365
1366   if (!xcontext->caps) {
1367     XCloseDisplay (xcontext->disp);
1368     g_mutex_unlock (&xvimagesink->x_lock);
1369     g_free (xcontext->par);
1370     g_free (xcontext);
1371     /* GST_ELEMENT_ERROR is thrown by gst_xvimagesink_get_xv_support */
1372     return NULL;
1373   }
1374
1375   xv_attr = XvQueryPortAttributes (xcontext->disp,
1376       xcontext->xv_port_id, &N_attr);
1377
1378
1379   /* Generate the channels list */
1380   for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
1381     XvAttribute *matching_attr = NULL;
1382
1383     /* Retrieve the property atom if it exists. If it doesn't exist,
1384      * the attribute itself must not either, so we can skip */
1385     prop_atom = XInternAtom (xcontext->disp, channels[i], True);
1386     if (prop_atom == None)
1387       continue;
1388
1389     if (xv_attr != NULL) {
1390       for (j = 0; j < N_attr && matching_attr == NULL; ++j)
1391         if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
1392           matching_attr = xv_attr + j;
1393     }
1394
1395     if (matching_attr) {
1396       GstColorBalanceChannel *channel;
1397
1398       channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
1399       channel->label = g_strdup (channels[i]);
1400       channel->min_value = matching_attr ? matching_attr->min_value : -1000;
1401       channel->max_value = matching_attr ? matching_attr->max_value : 1000;
1402
1403       xcontext->channels_list = g_list_append (xcontext->channels_list,
1404           channel);
1405
1406       /* If the colorbalance settings have not been touched we get Xv values
1407          as defaults and update our internal variables */
1408       if (!xvimagesink->cb_changed) {
1409         gint val;
1410
1411         XvGetPortAttribute (xcontext->disp, xcontext->xv_port_id,
1412             prop_atom, &val);
1413         /* Normalize val to [-1000, 1000] */
1414         val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
1415             (double) (channel->max_value - channel->min_value));
1416
1417         if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
1418           xvimagesink->hue = val;
1419         else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
1420           xvimagesink->saturation = val;
1421         else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
1422           xvimagesink->brightness = val;
1423         else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
1424           xvimagesink->contrast = val;
1425       }
1426     }
1427   }
1428
1429   if (xv_attr)
1430     XFree (xv_attr);
1431
1432   g_mutex_unlock (&xvimagesink->x_lock);
1433
1434   return xcontext;
1435 }
1436
1437 /* This function cleans the X context. Closing the Display, releasing the XV
1438    port and unrefing the caps for supported formats. */
1439 static void
1440 gst_xvimagesink_xcontext_clear (GstXvImageSink * xvimagesink)
1441 {
1442   GList *formats_list, *channels_list;
1443   GstXContext *xcontext;
1444   gint i = 0;
1445
1446   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
1447
1448   GST_OBJECT_LOCK (xvimagesink);
1449   if (xvimagesink->xcontext == NULL) {
1450     GST_OBJECT_UNLOCK (xvimagesink);
1451     return;
1452   }
1453
1454   /* Take the XContext from the sink and clean it up */
1455   xcontext = xvimagesink->xcontext;
1456   xvimagesink->xcontext = NULL;
1457
1458   GST_OBJECT_UNLOCK (xvimagesink);
1459
1460
1461   formats_list = xcontext->formats_list;
1462
1463   while (formats_list) {
1464     GstXvImageFormat *format = formats_list->data;
1465
1466     gst_caps_unref (format->caps);
1467     g_free (format);
1468     formats_list = g_list_next (formats_list);
1469   }
1470
1471   if (xcontext->formats_list)
1472     g_list_free (xcontext->formats_list);
1473
1474   channels_list = xcontext->channels_list;
1475
1476   while (channels_list) {
1477     GstColorBalanceChannel *channel = channels_list->data;
1478
1479     g_object_unref (channel);
1480     channels_list = g_list_next (channels_list);
1481   }
1482
1483   if (xcontext->channels_list)
1484     g_list_free (xcontext->channels_list);
1485
1486   gst_caps_unref (xcontext->caps);
1487   if (xcontext->last_caps)
1488     gst_caps_replace (&xcontext->last_caps, NULL);
1489
1490   for (i = 0; i < xcontext->nb_adaptors; i++) {
1491     g_free (xcontext->adaptors[i]);
1492   }
1493
1494   g_free (xcontext->adaptors);
1495
1496   g_free (xcontext->par);
1497
1498   g_mutex_lock (&xvimagesink->x_lock);
1499
1500   GST_DEBUG_OBJECT (xvimagesink, "Closing display and freeing X Context");
1501
1502   XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0);
1503
1504   XCloseDisplay (xcontext->disp);
1505
1506   g_mutex_unlock (&xvimagesink->x_lock);
1507
1508   g_free (xcontext);
1509 }
1510
1511 /* Element stuff */
1512
1513 static GstCaps *
1514 gst_xvimagesink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1515 {
1516   GstXvImageSink *xvimagesink;
1517   GstCaps *caps;
1518
1519   xvimagesink = GST_XVIMAGESINK (bsink);
1520
1521   if (xvimagesink->xcontext) {
1522     if (filter)
1523       return gst_caps_intersect_full (filter, xvimagesink->xcontext->caps,
1524           GST_CAPS_INTERSECT_FIRST);
1525     else
1526       return gst_caps_ref (xvimagesink->xcontext->caps);
1527   }
1528
1529   caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (xvimagesink));
1530   if (filter) {
1531     GstCaps *intersection;
1532
1533     intersection =
1534         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1535     gst_caps_unref (caps);
1536     caps = intersection;
1537   }
1538   return caps;
1539 }
1540
1541 static gboolean
1542 gst_xvimagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1543 {
1544   GstXvImageSink *xvimagesink;
1545   GstStructure *structure;
1546   GstBufferPool *newpool, *oldpool;
1547   GstVideoInfo info;
1548   guint32 im_format = 0;
1549   gint video_par_n, video_par_d;        /* video's PAR */
1550   gint display_par_n, display_par_d;    /* display's PAR */
1551   guint num, den;
1552   gint size;
1553   static GstAllocationParams params = { 0, 15, 0, 0, };
1554
1555   xvimagesink = GST_XVIMAGESINK (bsink);
1556
1557   GST_DEBUG_OBJECT (xvimagesink,
1558       "In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %"
1559       GST_PTR_FORMAT, xvimagesink->xcontext->caps, caps);
1560
1561   if (!gst_caps_can_intersect (xvimagesink->xcontext->caps, caps))
1562     goto incompatible_caps;
1563
1564   if (!gst_video_info_from_caps (&info, caps))
1565     goto invalid_format;
1566
1567   xvimagesink->fps_n = info.fps_n;
1568   xvimagesink->fps_d = info.fps_d;
1569
1570   xvimagesink->video_width = info.width;
1571   xvimagesink->video_height = info.height;
1572
1573   im_format = gst_xvimagesink_get_format_from_info (xvimagesink, &info);
1574   if (im_format == -1)
1575     goto invalid_format;
1576
1577   size = info.size;
1578
1579   /* get aspect ratio from caps if it's present, and
1580    * convert video width and height to a display width and height
1581    * using wd / hd = wv / hv * PARv / PARd */
1582
1583   /* get video's PAR */
1584   video_par_n = info.par_n;
1585   video_par_d = info.par_d;
1586
1587   /* get display's PAR */
1588   if (xvimagesink->par) {
1589     display_par_n = gst_value_get_fraction_numerator (xvimagesink->par);
1590     display_par_d = gst_value_get_fraction_denominator (xvimagesink->par);
1591   } else {
1592     display_par_n = 1;
1593     display_par_d = 1;
1594   }
1595
1596   if (!gst_video_calculate_display_ratio (&num, &den, info.width,
1597           info.height, video_par_n, video_par_d, display_par_n, display_par_d))
1598     goto no_disp_ratio;
1599
1600   GST_DEBUG_OBJECT (xvimagesink,
1601       "video width/height: %dx%d, calculated display ratio: %d/%d",
1602       info.width, info.height, num, den);
1603
1604   /* now find a width x height that respects this display ratio.
1605    * prefer those that have one of w/h the same as the incoming video
1606    * using wd / hd = num / den */
1607
1608   /* start with same height, because of interlaced video */
1609   /* check hd / den is an integer scale factor, and scale wd with the PAR */
1610   if (info.height % den == 0) {
1611     GST_DEBUG_OBJECT (xvimagesink, "keeping video height");
1612     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1613         gst_util_uint64_scale_int (info.height, num, den);
1614     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1615   } else if (info.width % num == 0) {
1616     GST_DEBUG_OBJECT (xvimagesink, "keeping video width");
1617     GST_VIDEO_SINK_WIDTH (xvimagesink) = info.width;
1618     GST_VIDEO_SINK_HEIGHT (xvimagesink) = (guint)
1619         gst_util_uint64_scale_int (info.width, den, num);
1620   } else {
1621     GST_DEBUG_OBJECT (xvimagesink, "approximating while keeping video height");
1622     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1623         gst_util_uint64_scale_int (info.height, num, den);
1624     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1625   }
1626   GST_DEBUG_OBJECT (xvimagesink, "scaling to %dx%d",
1627       GST_VIDEO_SINK_WIDTH (xvimagesink), GST_VIDEO_SINK_HEIGHT (xvimagesink));
1628
1629   /* Notify application to set xwindow id now */
1630   g_mutex_lock (&xvimagesink->flow_lock);
1631   if (!xvimagesink->xwindow) {
1632     g_mutex_unlock (&xvimagesink->flow_lock);
1633     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (xvimagesink));
1634   } else {
1635     g_mutex_unlock (&xvimagesink->flow_lock);
1636   }
1637
1638   /* Creating our window and our image with the display size in pixels */
1639   if (GST_VIDEO_SINK_WIDTH (xvimagesink) <= 0 ||
1640       GST_VIDEO_SINK_HEIGHT (xvimagesink) <= 0)
1641     goto no_display_size;
1642
1643   g_mutex_lock (&xvimagesink->flow_lock);
1644   if (!xvimagesink->xwindow) {
1645     xvimagesink->xwindow = gst_xvimagesink_xwindow_new (xvimagesink,
1646         GST_VIDEO_SINK_WIDTH (xvimagesink),
1647         GST_VIDEO_SINK_HEIGHT (xvimagesink));
1648   }
1649
1650   xvimagesink->info = info;
1651
1652   /* After a resize, we want to redraw the borders in case the new frame size
1653    * doesn't cover the same area */
1654   xvimagesink->redraw_border = TRUE;
1655
1656   /* create a new pool for the new configuration */
1657   newpool = gst_xvimage_buffer_pool_new (xvimagesink);
1658
1659   structure = gst_buffer_pool_get_config (newpool);
1660   gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
1661   gst_buffer_pool_config_set_allocator (structure, NULL, &params);
1662   if (!gst_buffer_pool_set_config (newpool, structure))
1663     goto config_failed;
1664
1665   oldpool = xvimagesink->pool;
1666   /* we don't activate the pool yet, this will be done by downstream after it
1667    * has configured the pool. If downstream does not want our pool we will
1668    * activate it when we render into it */
1669   xvimagesink->pool = newpool;
1670   g_mutex_unlock (&xvimagesink->flow_lock);
1671
1672   /* unref the old sink */
1673   if (oldpool) {
1674     /* we don't deactivate, some elements might still be using it, it will
1675      * be deactivated when the last ref is gone */
1676     gst_object_unref (oldpool);
1677   }
1678
1679   return TRUE;
1680
1681   /* ERRORS */
1682 incompatible_caps:
1683   {
1684     GST_ERROR_OBJECT (xvimagesink, "caps incompatible");
1685     return FALSE;
1686   }
1687 invalid_format:
1688   {
1689     GST_DEBUG_OBJECT (xvimagesink,
1690         "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
1691     return FALSE;
1692   }
1693 no_disp_ratio:
1694   {
1695     GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL),
1696         ("Error calculating the output display ratio of the video."));
1697     return FALSE;
1698   }
1699 no_display_size:
1700   {
1701     GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL),
1702         ("Error calculating the output display ratio of the video."));
1703     return FALSE;
1704   }
1705 config_failed:
1706   {
1707     GST_ERROR_OBJECT (xvimagesink, "failed to set config.");
1708     g_mutex_unlock (&xvimagesink->flow_lock);
1709     return FALSE;
1710   }
1711 }
1712
1713 static GstStateChangeReturn
1714 gst_xvimagesink_change_state (GstElement * element, GstStateChange transition)
1715 {
1716   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1717   GstXvImageSink *xvimagesink;
1718   GstXContext *xcontext = NULL;
1719
1720   xvimagesink = GST_XVIMAGESINK (element);
1721
1722   switch (transition) {
1723     case GST_STATE_CHANGE_NULL_TO_READY:
1724       /* Initializing the XContext */
1725       if (xvimagesink->xcontext == NULL) {
1726         xcontext = gst_xvimagesink_xcontext_get (xvimagesink);
1727         if (xcontext == NULL) {
1728           ret = GST_STATE_CHANGE_FAILURE;
1729           goto beach;
1730         }
1731         GST_OBJECT_LOCK (xvimagesink);
1732         if (xcontext)
1733           xvimagesink->xcontext = xcontext;
1734         GST_OBJECT_UNLOCK (xvimagesink);
1735       }
1736
1737       /* update object's par with calculated one if not set yet */
1738       if (!xvimagesink->par) {
1739         xvimagesink->par = g_new0 (GValue, 1);
1740         gst_value_init_and_copy (xvimagesink->par, xvimagesink->xcontext->par);
1741         GST_DEBUG_OBJECT (xvimagesink, "set calculated PAR on object's PAR");
1742       }
1743       /* call XSynchronize with the current value of synchronous */
1744       GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s",
1745           xvimagesink->synchronous ? "TRUE" : "FALSE");
1746       XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous);
1747       gst_xvimagesink_update_colorbalance (xvimagesink);
1748       gst_xvimagesink_manage_event_thread (xvimagesink);
1749       break;
1750     case GST_STATE_CHANGE_READY_TO_PAUSED:
1751       break;
1752     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1753       break;
1754     case GST_STATE_CHANGE_PAUSED_TO_READY:
1755       break;
1756     default:
1757       break;
1758   }
1759
1760   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1761
1762   switch (transition) {
1763     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1764       break;
1765     case GST_STATE_CHANGE_PAUSED_TO_READY:
1766       xvimagesink->fps_n = 0;
1767       xvimagesink->fps_d = 1;
1768       GST_VIDEO_SINK_WIDTH (xvimagesink) = 0;
1769       GST_VIDEO_SINK_HEIGHT (xvimagesink) = 0;
1770       g_mutex_lock (&xvimagesink->flow_lock);
1771       if (xvimagesink->pool)
1772         gst_buffer_pool_set_active (xvimagesink->pool, FALSE);
1773       g_mutex_unlock (&xvimagesink->flow_lock);
1774       break;
1775     case GST_STATE_CHANGE_READY_TO_NULL:
1776       gst_xvimagesink_reset (xvimagesink);
1777       break;
1778     default:
1779       break;
1780   }
1781
1782 beach:
1783   return ret;
1784 }
1785
1786 static void
1787 gst_xvimagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1788     GstClockTime * start, GstClockTime * end)
1789 {
1790   GstXvImageSink *xvimagesink;
1791
1792   xvimagesink = GST_XVIMAGESINK (bsink);
1793
1794   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1795     *start = GST_BUFFER_TIMESTAMP (buf);
1796     if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1797       *end = *start + GST_BUFFER_DURATION (buf);
1798     } else {
1799       if (xvimagesink->fps_n > 0) {
1800         *end = *start +
1801             gst_util_uint64_scale_int (GST_SECOND, xvimagesink->fps_d,
1802             xvimagesink->fps_n);
1803       }
1804     }
1805   }
1806 }
1807
1808 static GstFlowReturn
1809 gst_xvimagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1810 {
1811   GstFlowReturn res;
1812   GstXvImageSink *xvimagesink;
1813   GstXvImageMeta *meta;
1814   GstBuffer *to_put;
1815
1816   xvimagesink = GST_XVIMAGESINK (vsink);
1817
1818   meta = gst_buffer_get_xvimage_meta (buf);
1819
1820   if (meta && meta->sink == xvimagesink) {
1821     /* If this buffer has been allocated using our buffer management we simply
1822        put the ximage which is in the PRIVATE pointer */
1823     GST_LOG_OBJECT (xvimagesink, "buffer %p from our pool, writing directly",
1824         buf);
1825     to_put = buf;
1826     res = GST_FLOW_OK;
1827   } else {
1828     GstVideoFrame src, dest;
1829     GstBufferPoolAcquireParams params = { 0, };
1830
1831     /* Else we have to copy the data into our private image, */
1832     /* if we have one... */
1833     GST_LOG_OBJECT (xvimagesink, "buffer %p not from our pool, copying", buf);
1834
1835     /* we should have a pool, configured in setcaps */
1836     if (xvimagesink->pool == NULL)
1837       goto no_pool;
1838
1839     if (!gst_buffer_pool_set_active (xvimagesink->pool, TRUE))
1840       goto activate_failed;
1841
1842     /* take a buffer from our pool, if there is no buffer in the pool something
1843      * is seriously wrong, waiting for the pool here might deadlock when we try
1844      * to go to PAUSED because we never flush the pool then. */
1845     params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
1846     res = gst_buffer_pool_acquire_buffer (xvimagesink->pool, &to_put, &params);
1847     if (res != GST_FLOW_OK)
1848       goto no_buffer;
1849
1850     GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, xvimagesink,
1851         "slow copy into bufferpool buffer %p", to_put);
1852
1853     if (!gst_video_frame_map (&src, &xvimagesink->info, buf, GST_MAP_READ))
1854       goto invalid_buffer;
1855
1856     if (!gst_video_frame_map (&dest, &xvimagesink->info, to_put, GST_MAP_WRITE)) {
1857       gst_video_frame_unmap (&src);
1858       goto invalid_buffer;
1859     }
1860
1861     gst_video_frame_copy (&dest, &src);
1862
1863     gst_video_frame_unmap (&dest);
1864     gst_video_frame_unmap (&src);
1865   }
1866
1867   if (!gst_xvimagesink_xvimage_put (xvimagesink, to_put))
1868     goto no_window;
1869
1870 done:
1871   if (to_put != buf)
1872     gst_buffer_unref (to_put);
1873
1874   return res;
1875
1876   /* ERRORS */
1877 no_pool:
1878   {
1879     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1880         ("Internal error: can't allocate images"),
1881         ("We don't have a bufferpool negotiated"));
1882     return GST_FLOW_ERROR;
1883   }
1884 no_buffer:
1885   {
1886     /* No image available. That's very bad ! */
1887     GST_WARNING_OBJECT (xvimagesink, "could not create image");
1888     return GST_FLOW_OK;
1889   }
1890 invalid_buffer:
1891   {
1892     /* No Window available to put our image into */
1893     GST_WARNING_OBJECT (xvimagesink, "could not map image");
1894     res = GST_FLOW_OK;
1895     goto done;
1896   }
1897 no_window:
1898   {
1899     /* No Window available to put our image into */
1900     GST_WARNING_OBJECT (xvimagesink, "could not output image - no window");
1901     res = GST_FLOW_ERROR;
1902     goto done;
1903   }
1904 activate_failed:
1905   {
1906     GST_ERROR_OBJECT (xvimagesink, "failed to activate bufferpool.");
1907     res = GST_FLOW_ERROR;
1908     goto done;
1909   }
1910 }
1911
1912 static gboolean
1913 gst_xvimagesink_event (GstBaseSink * sink, GstEvent * event)
1914 {
1915   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (sink);
1916
1917   switch (GST_EVENT_TYPE (event)) {
1918     case GST_EVENT_TAG:{
1919       GstTagList *l;
1920       gchar *title = NULL;
1921
1922       gst_event_parse_tag (event, &l);
1923       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1924
1925       if (title) {
1926         GST_DEBUG_OBJECT (xvimagesink, "got tags, title='%s'", title);
1927         gst_xvimagesink_xwindow_set_title (xvimagesink, xvimagesink->xwindow,
1928             title);
1929
1930         g_free (title);
1931       }
1932       break;
1933     }
1934     default:
1935       break;
1936   }
1937   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1938 }
1939
1940 static gboolean
1941 gst_xvimagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1942 {
1943   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (bsink);
1944   GstBufferPool *pool;
1945   GstStructure *config;
1946   GstCaps *caps;
1947   guint size;
1948   gboolean need_pool;
1949
1950   gst_query_parse_allocation (query, &caps, &need_pool);
1951
1952   if (caps == NULL)
1953     goto no_caps;
1954
1955   g_mutex_lock (&xvimagesink->flow_lock);
1956   if ((pool = xvimagesink->pool))
1957     gst_object_ref (pool);
1958   g_mutex_unlock (&xvimagesink->flow_lock);
1959
1960   if (pool != NULL) {
1961     GstCaps *pcaps;
1962
1963     /* we had a pool, check caps */
1964     GST_DEBUG_OBJECT (xvimagesink, "check existing pool caps");
1965     config = gst_buffer_pool_get_config (pool);
1966     gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
1967
1968     if (!gst_caps_is_equal (caps, pcaps)) {
1969       GST_DEBUG_OBJECT (xvimagesink, "pool has different caps");
1970       /* different caps, we can't use this pool */
1971       gst_object_unref (pool);
1972       pool = NULL;
1973     }
1974     gst_structure_free (config);
1975   }
1976   if (pool == NULL && need_pool) {
1977     GstVideoInfo info;
1978
1979     if (!gst_video_info_from_caps (&info, caps))
1980       goto invalid_caps;
1981
1982     GST_DEBUG_OBJECT (xvimagesink, "create new pool");
1983     pool = gst_xvimage_buffer_pool_new (xvimagesink);
1984
1985     /* the normal size of a frame */
1986     size = info.size;
1987
1988     config = gst_buffer_pool_get_config (pool);
1989     gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
1990     if (!gst_buffer_pool_set_config (pool, config))
1991       goto config_failed;
1992   }
1993   if (pool) {
1994     /* we need at least 2 buffer because we hold on to the last one */
1995     gst_query_add_allocation_pool (query, pool, size, 2, 0);
1996     gst_object_unref (pool);
1997   }
1998
1999   /* we also support various metadata */
2000   gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
2001   gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
2002
2003   return TRUE;
2004
2005   /* ERRORS */
2006 no_caps:
2007   {
2008     GST_DEBUG_OBJECT (bsink, "no caps specified");
2009     return FALSE;
2010   }
2011 invalid_caps:
2012   {
2013     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
2014     return FALSE;
2015   }
2016 config_failed:
2017   {
2018     GST_DEBUG_OBJECT (bsink, "failed setting config");
2019     gst_object_unref (pool);
2020     return FALSE;
2021   }
2022 }
2023
2024 /* Interfaces stuff */
2025 static void
2026 gst_xvimagesink_navigation_send_event (GstNavigation * navigation,
2027     GstStructure * structure)
2028 {
2029   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (navigation);
2030   GstPad *peer;
2031
2032   if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (xvimagesink)))) {
2033     GstEvent *event;
2034     GstVideoRectangle src, dst, result;
2035     gdouble x, y, xscale = 1.0, yscale = 1.0;
2036
2037     event = gst_event_new_navigation (structure);
2038
2039     /* We take the flow_lock while we look at the window */
2040     g_mutex_lock (&xvimagesink->flow_lock);
2041
2042     if (!xvimagesink->xwindow) {
2043       g_mutex_unlock (&xvimagesink->flow_lock);
2044       return;
2045     }
2046
2047     if (xvimagesink->keep_aspect) {
2048       /* We get the frame position using the calculated geometry from _setcaps
2049          that respect pixel aspect ratios */
2050       src.w = GST_VIDEO_SINK_WIDTH (xvimagesink);
2051       src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink);
2052       dst.w = xvimagesink->render_rect.w;
2053       dst.h = xvimagesink->render_rect.h;
2054
2055       gst_video_sink_center_rect (src, dst, &result, TRUE);
2056       result.x += xvimagesink->render_rect.x;
2057       result.y += xvimagesink->render_rect.y;
2058     } else {
2059       memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
2060     }
2061
2062     g_mutex_unlock (&xvimagesink->flow_lock);
2063
2064     /* We calculate scaling using the original video frames geometry to include
2065        pixel aspect ratio scaling. */
2066     xscale = (gdouble) xvimagesink->video_width / result.w;
2067     yscale = (gdouble) xvimagesink->video_height / result.h;
2068
2069     /* Converting pointer coordinates to the non scaled geometry */
2070     if (gst_structure_get_double (structure, "pointer_x", &x)) {
2071       x = MIN (x, result.x + result.w);
2072       x = MAX (x - result.x, 0);
2073       gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
2074           (gdouble) x * xscale, NULL);
2075     }
2076     if (gst_structure_get_double (structure, "pointer_y", &y)) {
2077       y = MIN (y, result.y + result.h);
2078       y = MAX (y - result.y, 0);
2079       gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
2080           (gdouble) y * yscale, NULL);
2081     }
2082
2083     gst_pad_send_event (peer, event);
2084     gst_object_unref (peer);
2085   }
2086 }
2087
2088 static void
2089 gst_xvimagesink_navigation_init (GstNavigationInterface * iface)
2090 {
2091   iface->send_event = gst_xvimagesink_navigation_send_event;
2092 }
2093
2094 static void
2095 gst_xvimagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
2096 {
2097   XID xwindow_id = id;
2098   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2099   GstXWindow *xwindow = NULL;
2100
2101   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2102
2103   g_mutex_lock (&xvimagesink->flow_lock);
2104
2105   /* If we already use that window return */
2106   if (xvimagesink->xwindow && (xwindow_id == xvimagesink->xwindow->win)) {
2107     g_mutex_unlock (&xvimagesink->flow_lock);
2108     return;
2109   }
2110
2111   /* If the element has not initialized the X11 context try to do so */
2112   if (!xvimagesink->xcontext &&
2113       !(xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink))) {
2114     g_mutex_unlock (&xvimagesink->flow_lock);
2115     /* we have thrown a GST_ELEMENT_ERROR now */
2116     return;
2117   }
2118
2119   gst_xvimagesink_update_colorbalance (xvimagesink);
2120
2121   /* If a window is there already we destroy it */
2122   if (xvimagesink->xwindow) {
2123     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2124     xvimagesink->xwindow = NULL;
2125   }
2126
2127   /* If the xid is 0 we go back to an internal window */
2128   if (xwindow_id == 0) {
2129     /* If no width/height caps nego did not happen window will be created
2130        during caps nego then */
2131     if (GST_VIDEO_SINK_WIDTH (xvimagesink)
2132         && GST_VIDEO_SINK_HEIGHT (xvimagesink)) {
2133       xwindow =
2134           gst_xvimagesink_xwindow_new (xvimagesink,
2135           GST_VIDEO_SINK_WIDTH (xvimagesink),
2136           GST_VIDEO_SINK_HEIGHT (xvimagesink));
2137     }
2138   } else {
2139     XWindowAttributes attr;
2140
2141     xwindow = g_new0 (GstXWindow, 1);
2142     xwindow->win = xwindow_id;
2143
2144     /* Set the event we want to receive and create a GC */
2145     g_mutex_lock (&xvimagesink->x_lock);
2146
2147     XGetWindowAttributes (xvimagesink->xcontext->disp, xwindow->win, &attr);
2148
2149     xwindow->width = attr.width;
2150     xwindow->height = attr.height;
2151     xwindow->internal = FALSE;
2152     if (!xvimagesink->have_render_rect) {
2153       xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
2154       xvimagesink->render_rect.w = attr.width;
2155       xvimagesink->render_rect.h = attr.height;
2156     }
2157     if (xvimagesink->handle_events) {
2158       XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask |
2159           StructureNotifyMask | PointerMotionMask | KeyPressMask |
2160           KeyReleaseMask);
2161     }
2162
2163     xwindow->gc = XCreateGC (xvimagesink->xcontext->disp,
2164         xwindow->win, 0, NULL);
2165     g_mutex_unlock (&xvimagesink->x_lock);
2166   }
2167
2168   if (xwindow)
2169     xvimagesink->xwindow = xwindow;
2170
2171   g_mutex_unlock (&xvimagesink->flow_lock);
2172 }
2173
2174 static void
2175 gst_xvimagesink_expose (GstVideoOverlay * overlay)
2176 {
2177   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2178
2179   GST_DEBUG ("doing expose");
2180   gst_xvimagesink_xwindow_update_geometry (xvimagesink);
2181   gst_xvimagesink_xvimage_put (xvimagesink, NULL);
2182 }
2183
2184 static void
2185 gst_xvimagesink_set_event_handling (GstVideoOverlay * overlay,
2186     gboolean handle_events)
2187 {
2188   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2189
2190   xvimagesink->handle_events = handle_events;
2191
2192   g_mutex_lock (&xvimagesink->flow_lock);
2193
2194   if (G_UNLIKELY (!xvimagesink->xwindow)) {
2195     g_mutex_unlock (&xvimagesink->flow_lock);
2196     return;
2197   }
2198
2199   g_mutex_lock (&xvimagesink->x_lock);
2200
2201   if (handle_events) {
2202     if (xvimagesink->xwindow->internal) {
2203       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2204           ExposureMask | StructureNotifyMask | PointerMotionMask |
2205           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
2206     } else {
2207       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2208           ExposureMask | StructureNotifyMask | PointerMotionMask |
2209           KeyPressMask | KeyReleaseMask);
2210     }
2211   } else {
2212     XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, 0);
2213   }
2214
2215   g_mutex_unlock (&xvimagesink->x_lock);
2216
2217   g_mutex_unlock (&xvimagesink->flow_lock);
2218 }
2219
2220 static void
2221 gst_xvimagesink_set_render_rectangle (GstVideoOverlay * overlay, gint x, gint y,
2222     gint width, gint height)
2223 {
2224   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2225
2226   /* FIXME: how about some locking? */
2227   if (width >= 0 && height >= 0) {
2228     xvimagesink->render_rect.x = x;
2229     xvimagesink->render_rect.y = y;
2230     xvimagesink->render_rect.w = width;
2231     xvimagesink->render_rect.h = height;
2232     xvimagesink->have_render_rect = TRUE;
2233   } else {
2234     xvimagesink->render_rect.x = 0;
2235     xvimagesink->render_rect.y = 0;
2236     xvimagesink->render_rect.w = xvimagesink->xwindow->width;
2237     xvimagesink->render_rect.h = xvimagesink->xwindow->height;
2238     xvimagesink->have_render_rect = FALSE;
2239   }
2240 }
2241
2242 static void
2243 gst_xvimagesink_video_overlay_init (GstVideoOverlayInterface * iface)
2244 {
2245   iface->set_window_handle = gst_xvimagesink_set_window_handle;
2246   iface->expose = gst_xvimagesink_expose;
2247   iface->handle_events = gst_xvimagesink_set_event_handling;
2248   iface->set_render_rectangle = gst_xvimagesink_set_render_rectangle;
2249 }
2250
2251 static const GList *
2252 gst_xvimagesink_colorbalance_list_channels (GstColorBalance * balance)
2253 {
2254   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2255
2256   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
2257
2258   if (xvimagesink->xcontext)
2259     return xvimagesink->xcontext->channels_list;
2260   else
2261     return NULL;
2262 }
2263
2264 static void
2265 gst_xvimagesink_colorbalance_set_value (GstColorBalance * balance,
2266     GstColorBalanceChannel * channel, gint value)
2267 {
2268   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2269
2270   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2271   g_return_if_fail (channel->label != NULL);
2272
2273   xvimagesink->cb_changed = TRUE;
2274
2275   /* Normalize val to [-1000, 1000] */
2276   value = floor (0.5 + -1000 + 2000 * (value - channel->min_value) /
2277       (double) (channel->max_value - channel->min_value));
2278
2279   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2280     xvimagesink->hue = value;
2281   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2282     xvimagesink->saturation = value;
2283   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2284     xvimagesink->contrast = value;
2285   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2286     xvimagesink->brightness = value;
2287   } else {
2288     g_warning ("got an unknown channel %s", channel->label);
2289     return;
2290   }
2291
2292   gst_xvimagesink_update_colorbalance (xvimagesink);
2293 }
2294
2295 static gint
2296 gst_xvimagesink_colorbalance_get_value (GstColorBalance * balance,
2297     GstColorBalanceChannel * channel)
2298 {
2299   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2300   gint value = 0;
2301
2302   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), 0);
2303   g_return_val_if_fail (channel->label != NULL, 0);
2304
2305   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2306     value = xvimagesink->hue;
2307   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2308     value = xvimagesink->saturation;
2309   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2310     value = xvimagesink->contrast;
2311   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2312     value = xvimagesink->brightness;
2313   } else {
2314     g_warning ("got an unknown channel %s", channel->label);
2315   }
2316
2317   /* Normalize val to [channel->min_value, channel->max_value] */
2318   value = channel->min_value + (channel->max_value - channel->min_value) *
2319       (value + 1000) / 2000;
2320
2321   return value;
2322 }
2323
2324 static GstColorBalanceType
2325 gst_xvimagesink_colorbalance_get_balance_type (GstColorBalance * balance)
2326 {
2327   return GST_COLOR_BALANCE_HARDWARE;
2328 }
2329
2330 static void
2331 gst_xvimagesink_colorbalance_init (GstColorBalanceInterface * iface)
2332 {
2333   iface->list_channels = gst_xvimagesink_colorbalance_list_channels;
2334   iface->set_value = gst_xvimagesink_colorbalance_set_value;
2335   iface->get_value = gst_xvimagesink_colorbalance_get_value;
2336   iface->get_balance_type = gst_xvimagesink_colorbalance_get_balance_type;
2337 }
2338
2339 #if 0
2340 static const GList *
2341 gst_xvimagesink_probe_get_properties (GstPropertyProbe * probe)
2342 {
2343   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
2344   static GList *list = NULL;
2345
2346   if (!list) {
2347     list = g_list_append (NULL, g_object_class_find_property (klass, "device"));
2348     list =
2349         g_list_append (list, g_object_class_find_property (klass,
2350             "autopaint-colorkey"));
2351     list =
2352         g_list_append (list, g_object_class_find_property (klass,
2353             "double-buffer"));
2354     list =
2355         g_list_append (list, g_object_class_find_property (klass, "colorkey"));
2356   }
2357
2358   return list;
2359 }
2360
2361 static void
2362 gst_xvimagesink_probe_probe_property (GstPropertyProbe * probe,
2363     guint prop_id, const GParamSpec * pspec)
2364 {
2365   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2366
2367   switch (prop_id) {
2368     case PROP_DEVICE:
2369     case PROP_AUTOPAINT_COLORKEY:
2370     case PROP_DOUBLE_BUFFER:
2371     case PROP_COLORKEY:
2372       GST_DEBUG_OBJECT (xvimagesink,
2373           "probing device list and get capabilities");
2374       if (!xvimagesink->xcontext) {
2375         GST_DEBUG_OBJECT (xvimagesink, "generating xcontext");
2376         xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink);
2377       }
2378       break;
2379     default:
2380       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2381       break;
2382   }
2383 }
2384
2385 static gboolean
2386 gst_xvimagesink_probe_needs_probe (GstPropertyProbe * probe,
2387     guint prop_id, const GParamSpec * pspec)
2388 {
2389   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2390   gboolean ret = FALSE;
2391
2392   switch (prop_id) {
2393     case PROP_DEVICE:
2394     case PROP_AUTOPAINT_COLORKEY:
2395     case PROP_DOUBLE_BUFFER:
2396     case PROP_COLORKEY:
2397       if (xvimagesink->xcontext != NULL) {
2398         ret = FALSE;
2399       } else {
2400         ret = TRUE;
2401       }
2402       break;
2403     default:
2404       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2405       break;
2406   }
2407
2408   return ret;
2409 }
2410
2411 static GValueArray *
2412 gst_xvimagesink_probe_get_values (GstPropertyProbe * probe,
2413     guint prop_id, const GParamSpec * pspec)
2414 {
2415   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2416   GValueArray *array = NULL;
2417
2418   if (G_UNLIKELY (!xvimagesink->xcontext)) {
2419     GST_WARNING_OBJECT (xvimagesink, "we don't have any xcontext, can't "
2420         "get values");
2421     goto beach;
2422   }
2423
2424   switch (prop_id) {
2425     case PROP_DEVICE:
2426     {
2427       guint i;
2428       GValue value = { 0 };
2429
2430       array = g_value_array_new (xvimagesink->xcontext->nb_adaptors);
2431       g_value_init (&value, G_TYPE_STRING);
2432
2433       for (i = 0; i < xvimagesink->xcontext->nb_adaptors; i++) {
2434         gchar *adaptor_id_s = g_strdup_printf ("%u", i);
2435
2436         g_value_set_string (&value, adaptor_id_s);
2437         g_value_array_append (array, &value);
2438         g_free (adaptor_id_s);
2439       }
2440       g_value_unset (&value);
2441       break;
2442     }
2443     case PROP_AUTOPAINT_COLORKEY:
2444       if (xvimagesink->have_autopaint_colorkey) {
2445         GValue value = { 0 };
2446
2447         array = g_value_array_new (2);
2448         g_value_init (&value, G_TYPE_BOOLEAN);
2449         g_value_set_boolean (&value, FALSE);
2450         g_value_array_append (array, &value);
2451         g_value_set_boolean (&value, TRUE);
2452         g_value_array_append (array, &value);
2453         g_value_unset (&value);
2454       }
2455       break;
2456     case PROP_DOUBLE_BUFFER:
2457       if (xvimagesink->have_double_buffer) {
2458         GValue value = { 0 };
2459
2460         array = g_value_array_new (2);
2461         g_value_init (&value, G_TYPE_BOOLEAN);
2462         g_value_set_boolean (&value, FALSE);
2463         g_value_array_append (array, &value);
2464         g_value_set_boolean (&value, TRUE);
2465         g_value_array_append (array, &value);
2466         g_value_unset (&value);
2467       }
2468       break;
2469     case PROP_COLORKEY:
2470       if (xvimagesink->have_colorkey) {
2471         GValue value = { 0 };
2472
2473         array = g_value_array_new (1);
2474         g_value_init (&value, GST_TYPE_INT_RANGE);
2475         gst_value_set_int_range (&value, 0, 0xffffff);
2476         g_value_array_append (array, &value);
2477         g_value_unset (&value);
2478       }
2479       break;
2480     default:
2481       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2482       break;
2483   }
2484
2485 beach:
2486   return array;
2487 }
2488
2489 static void
2490 gst_xvimagesink_property_probe_interface_init (GstPropertyProbeInterface *
2491     iface)
2492 {
2493   iface->get_properties = gst_xvimagesink_probe_get_properties;
2494   iface->probe_property = gst_xvimagesink_probe_probe_property;
2495   iface->needs_probe = gst_xvimagesink_probe_needs_probe;
2496   iface->get_values = gst_xvimagesink_probe_get_values;
2497 }
2498 #endif
2499
2500 /* =========================================== */
2501 /*                                             */
2502 /*              Init & Class init              */
2503 /*                                             */
2504 /* =========================================== */
2505
2506 static void
2507 gst_xvimagesink_set_property (GObject * object, guint prop_id,
2508     const GValue * value, GParamSpec * pspec)
2509 {
2510   GstXvImageSink *xvimagesink;
2511
2512   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2513
2514   xvimagesink = GST_XVIMAGESINK (object);
2515
2516   switch (prop_id) {
2517     case PROP_HUE:
2518       xvimagesink->hue = g_value_get_int (value);
2519       xvimagesink->cb_changed = TRUE;
2520       gst_xvimagesink_update_colorbalance (xvimagesink);
2521       break;
2522     case PROP_CONTRAST:
2523       xvimagesink->contrast = g_value_get_int (value);
2524       xvimagesink->cb_changed = TRUE;
2525       gst_xvimagesink_update_colorbalance (xvimagesink);
2526       break;
2527     case PROP_BRIGHTNESS:
2528       xvimagesink->brightness = g_value_get_int (value);
2529       xvimagesink->cb_changed = TRUE;
2530       gst_xvimagesink_update_colorbalance (xvimagesink);
2531       break;
2532     case PROP_SATURATION:
2533       xvimagesink->saturation = g_value_get_int (value);
2534       xvimagesink->cb_changed = TRUE;
2535       gst_xvimagesink_update_colorbalance (xvimagesink);
2536       break;
2537     case PROP_DISPLAY:
2538       xvimagesink->display_name = g_strdup (g_value_get_string (value));
2539       break;
2540     case PROP_SYNCHRONOUS:
2541       xvimagesink->synchronous = g_value_get_boolean (value);
2542       if (xvimagesink->xcontext) {
2543         XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous);
2544         GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s",
2545             xvimagesink->synchronous ? "TRUE" : "FALSE");
2546       }
2547       break;
2548     case PROP_PIXEL_ASPECT_RATIO:
2549       g_free (xvimagesink->par);
2550       xvimagesink->par = g_new0 (GValue, 1);
2551       g_value_init (xvimagesink->par, GST_TYPE_FRACTION);
2552       if (!g_value_transform (value, xvimagesink->par)) {
2553         g_warning ("Could not transform string to aspect ratio");
2554         gst_value_set_fraction (xvimagesink->par, 1, 1);
2555       }
2556       GST_DEBUG_OBJECT (xvimagesink, "set PAR to %d/%d",
2557           gst_value_get_fraction_numerator (xvimagesink->par),
2558           gst_value_get_fraction_denominator (xvimagesink->par));
2559       break;
2560     case PROP_FORCE_ASPECT_RATIO:
2561       xvimagesink->keep_aspect = g_value_get_boolean (value);
2562       break;
2563     case PROP_HANDLE_EVENTS:
2564       gst_xvimagesink_set_event_handling (GST_VIDEO_OVERLAY (xvimagesink),
2565           g_value_get_boolean (value));
2566       gst_xvimagesink_manage_event_thread (xvimagesink);
2567       break;
2568     case PROP_DEVICE:
2569       xvimagesink->adaptor_no = atoi (g_value_get_string (value));
2570       break;
2571     case PROP_HANDLE_EXPOSE:
2572       xvimagesink->handle_expose = g_value_get_boolean (value);
2573       gst_xvimagesink_manage_event_thread (xvimagesink);
2574       break;
2575     case PROP_DOUBLE_BUFFER:
2576       xvimagesink->double_buffer = g_value_get_boolean (value);
2577       break;
2578     case PROP_AUTOPAINT_COLORKEY:
2579       xvimagesink->autopaint_colorkey = g_value_get_boolean (value);
2580       break;
2581     case PROP_COLORKEY:
2582       xvimagesink->colorkey = g_value_get_int (value);
2583       break;
2584     case PROP_DRAW_BORDERS:
2585       xvimagesink->draw_borders = g_value_get_boolean (value);
2586       break;
2587     default:
2588       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2589       break;
2590   }
2591 }
2592
2593 static void
2594 gst_xvimagesink_get_property (GObject * object, guint prop_id,
2595     GValue * value, GParamSpec * pspec)
2596 {
2597   GstXvImageSink *xvimagesink;
2598
2599   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2600
2601   xvimagesink = GST_XVIMAGESINK (object);
2602
2603   switch (prop_id) {
2604     case PROP_HUE:
2605       g_value_set_int (value, xvimagesink->hue);
2606       break;
2607     case PROP_CONTRAST:
2608       g_value_set_int (value, xvimagesink->contrast);
2609       break;
2610     case PROP_BRIGHTNESS:
2611       g_value_set_int (value, xvimagesink->brightness);
2612       break;
2613     case PROP_SATURATION:
2614       g_value_set_int (value, xvimagesink->saturation);
2615       break;
2616     case PROP_DISPLAY:
2617       g_value_set_string (value, xvimagesink->display_name);
2618       break;
2619     case PROP_SYNCHRONOUS:
2620       g_value_set_boolean (value, xvimagesink->synchronous);
2621       break;
2622     case PROP_PIXEL_ASPECT_RATIO:
2623       if (xvimagesink->par)
2624         g_value_transform (xvimagesink->par, value);
2625       break;
2626     case PROP_FORCE_ASPECT_RATIO:
2627       g_value_set_boolean (value, xvimagesink->keep_aspect);
2628       break;
2629     case PROP_HANDLE_EVENTS:
2630       g_value_set_boolean (value, xvimagesink->handle_events);
2631       break;
2632     case PROP_DEVICE:
2633     {
2634       char *adaptor_no_s = g_strdup_printf ("%u", xvimagesink->adaptor_no);
2635
2636       g_value_set_string (value, adaptor_no_s);
2637       g_free (adaptor_no_s);
2638       break;
2639     }
2640     case PROP_DEVICE_NAME:
2641       if (xvimagesink->xcontext && xvimagesink->xcontext->adaptors) {
2642         g_value_set_string (value,
2643             xvimagesink->xcontext->adaptors[xvimagesink->adaptor_no]);
2644       } else {
2645         g_value_set_string (value, NULL);
2646       }
2647       break;
2648     case PROP_HANDLE_EXPOSE:
2649       g_value_set_boolean (value, xvimagesink->handle_expose);
2650       break;
2651     case PROP_DOUBLE_BUFFER:
2652       g_value_set_boolean (value, xvimagesink->double_buffer);
2653       break;
2654     case PROP_AUTOPAINT_COLORKEY:
2655       g_value_set_boolean (value, xvimagesink->autopaint_colorkey);
2656       break;
2657     case PROP_COLORKEY:
2658       g_value_set_int (value, xvimagesink->colorkey);
2659       break;
2660     case PROP_DRAW_BORDERS:
2661       g_value_set_boolean (value, xvimagesink->draw_borders);
2662       break;
2663     case PROP_WINDOW_WIDTH:
2664       if (xvimagesink->xwindow)
2665         g_value_set_uint64 (value, xvimagesink->xwindow->width);
2666       else
2667         g_value_set_uint64 (value, 0);
2668       break;
2669     case PROP_WINDOW_HEIGHT:
2670       if (xvimagesink->xwindow)
2671         g_value_set_uint64 (value, xvimagesink->xwindow->height);
2672       else
2673         g_value_set_uint64 (value, 0);
2674       break;
2675     default:
2676       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2677       break;
2678   }
2679 }
2680
2681 static void
2682 gst_xvimagesink_reset (GstXvImageSink * xvimagesink)
2683 {
2684   GThread *thread;
2685
2686   GST_OBJECT_LOCK (xvimagesink);
2687   xvimagesink->running = FALSE;
2688   /* grab thread and mark it as NULL */
2689   thread = xvimagesink->event_thread;
2690   xvimagesink->event_thread = NULL;
2691   GST_OBJECT_UNLOCK (xvimagesink);
2692
2693   /* Wait for our event thread to finish before we clean up our stuff. */
2694   if (thread)
2695     g_thread_join (thread);
2696
2697   if (xvimagesink->cur_image) {
2698     gst_buffer_unref (xvimagesink->cur_image);
2699     xvimagesink->cur_image = NULL;
2700   }
2701
2702   g_mutex_lock (&xvimagesink->flow_lock);
2703
2704   if (xvimagesink->pool) {
2705     gst_object_unref (xvimagesink->pool);
2706     xvimagesink->pool = NULL;
2707   }
2708
2709   if (xvimagesink->xwindow) {
2710     gst_xvimagesink_xwindow_clear (xvimagesink, xvimagesink->xwindow);
2711     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2712     xvimagesink->xwindow = NULL;
2713   }
2714   g_mutex_unlock (&xvimagesink->flow_lock);
2715
2716   xvimagesink->render_rect.x = xvimagesink->render_rect.y =
2717       xvimagesink->render_rect.w = xvimagesink->render_rect.h = 0;
2718   xvimagesink->have_render_rect = FALSE;
2719
2720   gst_xvimagesink_xcontext_clear (xvimagesink);
2721 }
2722
2723 /* Finalize is called only once, dispose can be called multiple times.
2724  * We use mutexes and don't reset stuff to NULL here so let's register
2725  * as a finalize. */
2726 static void
2727 gst_xvimagesink_finalize (GObject * object)
2728 {
2729   GstXvImageSink *xvimagesink;
2730
2731   xvimagesink = GST_XVIMAGESINK (object);
2732
2733   gst_xvimagesink_reset (xvimagesink);
2734
2735   if (xvimagesink->display_name) {
2736     g_free (xvimagesink->display_name);
2737     xvimagesink->display_name = NULL;
2738   }
2739
2740   if (xvimagesink->par) {
2741     g_free (xvimagesink->par);
2742     xvimagesink->par = NULL;
2743   }
2744   g_mutex_clear (&xvimagesink->x_lock);
2745   g_mutex_clear (&xvimagesink->flow_lock);
2746   g_free (xvimagesink->media_title);
2747
2748   G_OBJECT_CLASS (parent_class)->finalize (object);
2749 }
2750
2751 static void
2752 gst_xvimagesink_init (GstXvImageSink * xvimagesink)
2753 {
2754   xvimagesink->display_name = NULL;
2755   xvimagesink->adaptor_no = 0;
2756   xvimagesink->xcontext = NULL;
2757   xvimagesink->xwindow = NULL;
2758   xvimagesink->cur_image = NULL;
2759
2760   xvimagesink->hue = xvimagesink->saturation = 0;
2761   xvimagesink->contrast = xvimagesink->brightness = 0;
2762   xvimagesink->cb_changed = FALSE;
2763
2764   xvimagesink->fps_n = 0;
2765   xvimagesink->fps_d = 0;
2766   xvimagesink->video_width = 0;
2767   xvimagesink->video_height = 0;
2768
2769   g_mutex_init (&xvimagesink->x_lock);
2770   g_mutex_init (&xvimagesink->flow_lock);
2771
2772   xvimagesink->pool = NULL;
2773
2774   xvimagesink->synchronous = FALSE;
2775   xvimagesink->double_buffer = TRUE;
2776   xvimagesink->running = FALSE;
2777   xvimagesink->keep_aspect = TRUE;
2778   xvimagesink->handle_events = TRUE;
2779   xvimagesink->par = NULL;
2780   xvimagesink->handle_expose = TRUE;
2781   xvimagesink->autopaint_colorkey = TRUE;
2782
2783   /* on 16bit displays this becomes r,g,b = 1,2,3
2784    * on 24bit displays this becomes r,g,b = 8,8,16
2785    * as a port atom value
2786    */
2787   xvimagesink->colorkey = (8 << 16) | (8 << 8) | 16;
2788   xvimagesink->draw_borders = TRUE;
2789 }
2790
2791 static void
2792 gst_xvimagesink_class_init (GstXvImageSinkClass * klass)
2793 {
2794   GObjectClass *gobject_class;
2795   GstElementClass *gstelement_class;
2796   GstBaseSinkClass *gstbasesink_class;
2797   GstVideoSinkClass *videosink_class;
2798
2799   gobject_class = (GObjectClass *) klass;
2800   gstelement_class = (GstElementClass *) klass;
2801   gstbasesink_class = (GstBaseSinkClass *) klass;
2802   videosink_class = (GstVideoSinkClass *) klass;
2803
2804   parent_class = g_type_class_peek_parent (klass);
2805
2806   gobject_class->set_property = gst_xvimagesink_set_property;
2807   gobject_class->get_property = gst_xvimagesink_get_property;
2808
2809   g_object_class_install_property (gobject_class, PROP_CONTRAST,
2810       g_param_spec_int ("contrast", "Contrast", "The contrast of the video",
2811           -1000, 1000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2812   g_object_class_install_property (gobject_class, PROP_BRIGHTNESS,
2813       g_param_spec_int ("brightness", "Brightness",
2814           "The brightness of the video", -1000, 1000, 0,
2815           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2816   g_object_class_install_property (gobject_class, PROP_HUE,
2817       g_param_spec_int ("hue", "Hue", "The hue of the video", -1000, 1000, 0,
2818           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2819   g_object_class_install_property (gobject_class, PROP_SATURATION,
2820       g_param_spec_int ("saturation", "Saturation",
2821           "The saturation of the video", -1000, 1000, 0,
2822           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2823   g_object_class_install_property (gobject_class, PROP_DISPLAY,
2824       g_param_spec_string ("display", "Display", "X Display name", NULL,
2825           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2826   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
2827       g_param_spec_boolean ("synchronous", "Synchronous",
2828           "When enabled, runs the X display in synchronous mode. "
2829           "(unrelated to A/V sync, used only for debugging)", FALSE,
2830           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2831   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
2832       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
2833           "The pixel aspect ratio of the device", "1/1",
2834           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2835   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
2836       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
2837           "When enabled, scaling will respect original aspect ratio", TRUE,
2838           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2839   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
2840       g_param_spec_boolean ("handle-events", "Handle XEvents",
2841           "When enabled, XEvents will be selected and handled", TRUE,
2842           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2843   g_object_class_install_property (gobject_class, PROP_DEVICE,
2844       g_param_spec_string ("device", "Adaptor number",
2845           "The number of the video adaptor", "0",
2846           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2847   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
2848       g_param_spec_string ("device-name", "Adaptor name",
2849           "The name of the video adaptor", NULL,
2850           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2851   /**
2852    * GstXvImageSink:handle-expose
2853    *
2854    * When enabled, the current frame will always be drawn in response to X
2855    * Expose.
2856    *
2857    * Since: 0.10.14
2858    */
2859   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
2860       g_param_spec_boolean ("handle-expose", "Handle expose",
2861           "When enabled, "
2862           "the current frame will always be drawn in response to X Expose "
2863           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2864
2865   /**
2866    * GstXvImageSink:double-buffer
2867    *
2868    * Whether to double-buffer the output.
2869    *
2870    * Since: 0.10.14
2871    */
2872   g_object_class_install_property (gobject_class, PROP_DOUBLE_BUFFER,
2873       g_param_spec_boolean ("double-buffer", "Double-buffer",
2874           "Whether to double-buffer the output", TRUE,
2875           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2876   /**
2877    * GstXvImageSink:autopaint-colorkey
2878    *
2879    * Whether to autofill overlay with colorkey
2880    *
2881    * Since: 0.10.21
2882    */
2883   g_object_class_install_property (gobject_class, PROP_AUTOPAINT_COLORKEY,
2884       g_param_spec_boolean ("autopaint-colorkey", "Autofill with colorkey",
2885           "Whether to autofill overlay with colorkey", TRUE,
2886           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2887   /**
2888    * GstXvImageSink:colorkey
2889    *
2890    * Color to use for the overlay mask.
2891    *
2892    * Since: 0.10.21
2893    */
2894   g_object_class_install_property (gobject_class, PROP_COLORKEY,
2895       g_param_spec_int ("colorkey", "Colorkey",
2896           "Color to use for the overlay mask", G_MININT, G_MAXINT, 0,
2897           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2898
2899   /**
2900    * GstXvImageSink:draw-borders
2901    *
2902    * Draw black borders when using GstXvImageSink:force-aspect-ratio to fill
2903    * unused parts of the video area.
2904    *
2905    * Since: 0.10.21
2906    */
2907   g_object_class_install_property (gobject_class, PROP_DRAW_BORDERS,
2908       g_param_spec_boolean ("draw-borders", "Colorkey",
2909           "Draw black borders to fill unused area in force-aspect-ratio mode",
2910           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2911
2912   /**
2913    * GstXvImageSink:window-width
2914    *
2915    * Actual width of the video window.
2916    *
2917    * Since: 0.10.32
2918    */
2919   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
2920       g_param_spec_uint64 ("window-width", "window-width",
2921           "Width of the window", 0, G_MAXUINT64, 0,
2922           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2923
2924   /**
2925    * GstXvImageSink:window-height
2926    *
2927    * Actual height of the video window.
2928    *
2929    * Since: 0.10.32
2930    */
2931   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
2932       g_param_spec_uint64 ("window-height", "window-height",
2933           "Height of the window", 0, G_MAXUINT64, 0,
2934           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2935
2936   gobject_class->finalize = gst_xvimagesink_finalize;
2937
2938   gst_element_class_set_static_metadata (gstelement_class,
2939       "Video sink", "Sink/Video",
2940       "A Xv based videosink", "Julien Moutte <julien@moutte.net>");
2941
2942   gst_element_class_add_pad_template (gstelement_class,
2943       gst_static_pad_template_get (&gst_xvimagesink_sink_template_factory));
2944
2945   gstelement_class->change_state =
2946       GST_DEBUG_FUNCPTR (gst_xvimagesink_change_state);
2947
2948   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_getcaps);
2949   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_setcaps);
2950   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_xvimagesink_get_times);
2951   gstbasesink_class->propose_allocation =
2952       GST_DEBUG_FUNCPTR (gst_xvimagesink_propose_allocation);
2953   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_xvimagesink_event);
2954
2955   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_xvimagesink_show_frame);
2956 }