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