Merge remote-tracking branch 'origin/master' into 0.11
[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 #ifdef HAVE_CONFIG_H
113 #include "config.h"
114 #endif
115
116 /* Our interfaces */
117 #include <gst/interfaces/navigation.h>
118 #include <gst/interfaces/propertyprobe.h>
119 #include <gst/video/videooverlay.h>
120 #include <gst/video/colorbalance.h>
121 /* Helper functions */
122 #include <gst/video/gstvideometa.h>
123
124 /* Object header */
125 #include "xvimagesink.h"
126
127 /* Debugging category */
128 #include <gst/gstinfo.h>
129
130 #include "gst/glib-compat-private.h"
131
132 GST_DEBUG_CATEGORY_EXTERN (gst_debug_xvimagesink);
133 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
134 #define GST_CAT_DEFAULT gst_debug_xvimagesink
135
136 typedef struct
137 {
138   unsigned long flags;
139   unsigned long functions;
140   unsigned long decorations;
141   long input_mode;
142   unsigned long status;
143 }
144 MotifWmHints, MwmHints;
145
146 #define MWM_HINTS_DECORATIONS   (1L << 1)
147
148 static void gst_xvimagesink_reset (GstXvImageSink * xvimagesink);
149 static void gst_xvimagesink_xwindow_update_geometry (GstXvImageSink *
150     xvimagesink);
151 static void gst_xvimagesink_expose (GstVideoOverlay * overlay);
152
153 /* Default template - initiated with class struct to allow gst-register to work
154    without X running */
155 static GstStaticPadTemplate gst_xvimagesink_sink_template_factory =
156 GST_STATIC_PAD_TEMPLATE ("sink",
157     GST_PAD_SINK,
158     GST_PAD_ALWAYS,
159     GST_STATIC_CAPS ("video/x-raw, "
160         "framerate = (fraction) [ 0, MAX ], "
161         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
162     );
163
164 enum
165 {
166   PROP_0,
167   PROP_CONTRAST,
168   PROP_BRIGHTNESS,
169   PROP_HUE,
170   PROP_SATURATION,
171   PROP_DISPLAY,
172   PROP_SYNCHRONOUS,
173   PROP_PIXEL_ASPECT_RATIO,
174   PROP_FORCE_ASPECT_RATIO,
175   PROP_HANDLE_EVENTS,
176   PROP_DEVICE,
177   PROP_DEVICE_NAME,
178   PROP_HANDLE_EXPOSE,
179   PROP_DOUBLE_BUFFER,
180   PROP_AUTOPAINT_COLORKEY,
181   PROP_COLORKEY,
182   PROP_DRAW_BORDERS,
183   PROP_WINDOW_WIDTH,
184   PROP_WINDOW_HEIGHT
185 };
186
187 /* ============================================================= */
188 /*                                                               */
189 /*                       Public Methods                          */
190 /*                                                               */
191 /* ============================================================= */
192
193 /* =========================================== */
194 /*                                             */
195 /*          Object typing & Creation           */
196 /*                                             */
197 /* =========================================== */
198 static void gst_xvimagesink_navigation_init (GstNavigationInterface * iface);
199 static void gst_xvimagesink_video_overlay_init (GstVideoOverlayInterface *
200     iface);
201 static void gst_xvimagesink_colorbalance_init (GstColorBalanceInterface *
202     iface);
203 static void
204 gst_xvimagesink_property_probe_interface_init (GstPropertyProbeInterface *
205     iface);
206 #define gst_xvimagesink_parent_class parent_class
207 G_DEFINE_TYPE_WITH_CODE (GstXvImageSink, gst_xvimagesink, GST_TYPE_VIDEO_SINK,
208     G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
209         gst_xvimagesink_navigation_init);
210     G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
211         gst_xvimagesink_video_overlay_init);
212     G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
213         gst_xvimagesink_colorbalance_init);
214     G_IMPLEMENT_INTERFACE (GST_TYPE_PROPERTY_PROBE,
215         gst_xvimagesink_property_probe_interface_init));
216
217
218 /* ============================================================= */
219 /*                                                               */
220 /*                       Private Methods                         */
221 /*                                                               */
222 /* ============================================================= */
223
224
225 /* We are called with the x_lock taken */
226 static void
227 gst_xvimagesink_xwindow_draw_borders (GstXvImageSink * xvimagesink,
228     GstXWindow * xwindow, GstVideoRectangle rect)
229 {
230   gint t1, t2;
231
232   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
233   g_return_if_fail (xwindow != NULL);
234
235   XSetForeground (xvimagesink->xcontext->disp, xwindow->gc,
236       xvimagesink->xcontext->black);
237
238   /* Left border */
239   if (rect.x > xvimagesink->render_rect.x) {
240     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
241         xvimagesink->render_rect.x, xvimagesink->render_rect.y,
242         rect.x - xvimagesink->render_rect.x, xvimagesink->render_rect.h);
243   }
244
245   /* Right border */
246   t1 = rect.x + rect.w;
247   t2 = xvimagesink->render_rect.x + xvimagesink->render_rect.w;
248   if (t1 < t2) {
249     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
250         t1, xvimagesink->render_rect.y, t2 - t1, xvimagesink->render_rect.h);
251   }
252
253   /* Top border */
254   if (rect.y > xvimagesink->render_rect.y) {
255     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
256         xvimagesink->render_rect.x, xvimagesink->render_rect.y,
257         xvimagesink->render_rect.w, rect.y - xvimagesink->render_rect.y);
258   }
259
260   /* Bottom border */
261   t1 = rect.y + rect.h;
262   t2 = xvimagesink->render_rect.y + xvimagesink->render_rect.h;
263   if (t1 < t2) {
264     XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
265         xvimagesink->render_rect.x, t1, xvimagesink->render_rect.w, t2 - t1);
266   }
267 }
268
269 /* This function puts a GstXvImage on a GstXvImageSink's window. Returns FALSE
270  * if no window was available  */
271 static gboolean
272 gst_xvimagesink_xvimage_put (GstXvImageSink * xvimagesink, GstBuffer * xvimage)
273 {
274   GstXvImageMeta *meta;
275   GstVideoCropMeta *crop;
276   GstVideoRectangle result;
277   gboolean draw_border = FALSE;
278   GstVideoRectangle src, dst;
279
280   /* We take the flow_lock. If expose is in there we don't want to run
281      concurrently from the data flow thread */
282   g_mutex_lock (xvimagesink->flow_lock);
283
284   if (G_UNLIKELY (xvimagesink->xwindow == NULL)) {
285     g_mutex_unlock (xvimagesink->flow_lock);
286     return FALSE;
287   }
288
289   /* Draw borders when displaying the first frame. After this
290      draw borders only on expose event or after a size change. */
291   if (!xvimagesink->cur_image || xvimagesink->redraw_border) {
292     draw_border = TRUE;
293   }
294
295   /* Store a reference to the last image we put, lose the previous one */
296   if (xvimage && xvimagesink->cur_image != xvimage) {
297     if (xvimagesink->cur_image) {
298       GST_LOG_OBJECT (xvimagesink, "unreffing %p", xvimagesink->cur_image);
299       gst_buffer_unref (xvimagesink->cur_image);
300     }
301     GST_LOG_OBJECT (xvimagesink, "reffing %p as our current image", xvimage);
302     xvimagesink->cur_image = gst_buffer_ref (xvimage);
303   }
304
305   /* Expose sends a NULL image, we take the latest frame */
306   if (!xvimage) {
307     if (xvimagesink->cur_image) {
308       draw_border = TRUE;
309       xvimage = xvimagesink->cur_image;
310     } else {
311       g_mutex_unlock (xvimagesink->flow_lock);
312       return TRUE;
313     }
314   }
315
316   meta = gst_buffer_get_xvimage_meta (xvimage);
317
318   crop = gst_buffer_get_video_crop_meta (xvimage);
319
320   if (crop) {
321     src.x = crop->x + meta->x;
322     src.y = crop->y + meta->y;
323     src.w = crop->width;
324     src.h = crop->height;
325   } else {
326     src.x = meta->x;
327     src.y = meta->y;
328     src.w = meta->width;
329     src.h = meta->height;
330   }
331
332   if (xvimagesink->keep_aspect) {
333     dst.w = xvimagesink->render_rect.w;
334     dst.h = xvimagesink->render_rect.h;
335
336     gst_video_sink_center_rect (src, dst, &result, TRUE);
337     result.x += xvimagesink->render_rect.x;
338     result.y += xvimagesink->render_rect.y;
339   } else {
340     memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
341   }
342
343   g_mutex_lock (xvimagesink->x_lock);
344
345   if (draw_border && xvimagesink->draw_borders) {
346     gst_xvimagesink_xwindow_draw_borders (xvimagesink, xvimagesink->xwindow,
347         result);
348     xvimagesink->redraw_border = FALSE;
349   }
350 #ifdef HAVE_XSHM
351   if (xvimagesink->xcontext->use_xshm) {
352     GST_LOG_OBJECT (xvimagesink,
353         "XvShmPutImage with image %dx%d and window %dx%d, from xvimage %"
354         GST_PTR_FORMAT, meta->width, meta->height,
355         xvimagesink->render_rect.w, xvimagesink->render_rect.h, xvimage);
356
357     XvShmPutImage (xvimagesink->xcontext->disp,
358         xvimagesink->xcontext->xv_port_id,
359         xvimagesink->xwindow->win,
360         xvimagesink->xwindow->gc, meta->xvimage,
361         src.x, src.y, src.w, src.h,
362         result.x, result.y, result.w, result.h, FALSE);
363   } else
364 #endif /* HAVE_XSHM */
365   {
366     XvPutImage (xvimagesink->xcontext->disp,
367         xvimagesink->xcontext->xv_port_id,
368         xvimagesink->xwindow->win,
369         xvimagesink->xwindow->gc, meta->xvimage,
370         src.x, src.y, src.w, src.h, result.x, result.y, result.w, result.h);
371   }
372
373   XSync (xvimagesink->xcontext->disp, FALSE);
374
375   g_mutex_unlock (xvimagesink->x_lock);
376
377   g_mutex_unlock (xvimagesink->flow_lock);
378
379   return TRUE;
380 }
381
382 static gboolean
383 gst_xvimagesink_xwindow_decorate (GstXvImageSink * xvimagesink,
384     GstXWindow * window)
385 {
386   Atom hints_atom = None;
387   MotifWmHints *hints;
388
389   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), FALSE);
390   g_return_val_if_fail (window != NULL, FALSE);
391
392   g_mutex_lock (xvimagesink->x_lock);
393
394   hints_atom = XInternAtom (xvimagesink->xcontext->disp, "_MOTIF_WM_HINTS",
395       True);
396   if (hints_atom == None) {
397     g_mutex_unlock (xvimagesink->x_lock);
398     return FALSE;
399   }
400
401   hints = g_malloc0 (sizeof (MotifWmHints));
402
403   hints->flags |= MWM_HINTS_DECORATIONS;
404   hints->decorations = 1 << 0;
405
406   XChangeProperty (xvimagesink->xcontext->disp, window->win,
407       hints_atom, hints_atom, 32, PropModeReplace,
408       (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
409
410   XSync (xvimagesink->xcontext->disp, FALSE);
411
412   g_mutex_unlock (xvimagesink->x_lock);
413
414   g_free (hints);
415
416   return TRUE;
417 }
418
419 static void
420 gst_xvimagesink_xwindow_set_title (GstXvImageSink * xvimagesink,
421     GstXWindow * xwindow, const gchar * media_title)
422 {
423   if (media_title) {
424     g_free (xvimagesink->media_title);
425     xvimagesink->media_title = g_strdup (media_title);
426   }
427   if (xwindow) {
428     /* we have a window */
429     if (xwindow->internal) {
430       XTextProperty xproperty;
431       const gchar *app_name;
432       const gchar *title = NULL;
433       gchar *title_mem = NULL;
434
435       /* set application name as a title */
436       app_name = g_get_application_name ();
437
438       if (app_name && xvimagesink->media_title) {
439         title = title_mem = g_strconcat (xvimagesink->media_title, " : ",
440             app_name, NULL);
441       } else if (app_name) {
442         title = app_name;
443       } else if (xvimagesink->media_title) {
444         title = xvimagesink->media_title;
445       }
446
447       if (title) {
448         if ((XStringListToTextProperty (((char **) &title), 1,
449                     &xproperty)) != 0) {
450           XSetWMName (xvimagesink->xcontext->disp, xwindow->win, &xproperty);
451           XFree (xproperty.value);
452         }
453
454         g_free (title_mem);
455       }
456     }
457   }
458 }
459
460 /* This function handles a GstXWindow creation
461  * The width and height are the actual pixel size on the display */
462 static GstXWindow *
463 gst_xvimagesink_xwindow_new (GstXvImageSink * xvimagesink,
464     gint width, gint height)
465 {
466   GstXWindow *xwindow = NULL;
467   XGCValues values;
468
469   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
470
471   xwindow = g_new0 (GstXWindow, 1);
472
473   xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
474   xvimagesink->render_rect.w = width;
475   xvimagesink->render_rect.h = height;
476
477   xwindow->width = width;
478   xwindow->height = height;
479   xwindow->internal = TRUE;
480
481   g_mutex_lock (xvimagesink->x_lock);
482
483   xwindow->win = XCreateSimpleWindow (xvimagesink->xcontext->disp,
484       xvimagesink->xcontext->root,
485       0, 0, width, height, 0, 0, xvimagesink->xcontext->black);
486
487   /* We have to do that to prevent X from redrawing the background on
488    * ConfigureNotify. This takes away flickering of video when resizing. */
489   XSetWindowBackgroundPixmap (xvimagesink->xcontext->disp, xwindow->win, None);
490
491   /* set application name as a title */
492   gst_xvimagesink_xwindow_set_title (xvimagesink, xwindow, NULL);
493
494   if (xvimagesink->handle_events) {
495     Atom wm_delete;
496
497     XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask |
498         StructureNotifyMask | PointerMotionMask | KeyPressMask |
499         KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
500
501     /* Tell the window manager we'd like delete client messages instead of
502      * being killed */
503     wm_delete = XInternAtom (xvimagesink->xcontext->disp,
504         "WM_DELETE_WINDOW", True);
505     if (wm_delete != None) {
506       (void) XSetWMProtocols (xvimagesink->xcontext->disp, xwindow->win,
507           &wm_delete, 1);
508     }
509   }
510
511   xwindow->gc = XCreateGC (xvimagesink->xcontext->disp,
512       xwindow->win, 0, &values);
513
514   XMapRaised (xvimagesink->xcontext->disp, xwindow->win);
515
516   XSync (xvimagesink->xcontext->disp, FALSE);
517
518   g_mutex_unlock (xvimagesink->x_lock);
519
520   gst_xvimagesink_xwindow_decorate (xvimagesink, xwindow);
521
522   gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (xvimagesink),
523       xwindow->win);
524
525   return xwindow;
526 }
527
528 /* This function destroys a GstXWindow */
529 static void
530 gst_xvimagesink_xwindow_destroy (GstXvImageSink * xvimagesink,
531     GstXWindow * xwindow)
532 {
533   g_return_if_fail (xwindow != NULL);
534   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
535
536   g_mutex_lock (xvimagesink->x_lock);
537
538   /* If we did not create that window we just free the GC and let it live */
539   if (xwindow->internal)
540     XDestroyWindow (xvimagesink->xcontext->disp, xwindow->win);
541   else
542     XSelectInput (xvimagesink->xcontext->disp, xwindow->win, 0);
543
544   XFreeGC (xvimagesink->xcontext->disp, xwindow->gc);
545
546   XSync (xvimagesink->xcontext->disp, FALSE);
547
548   g_mutex_unlock (xvimagesink->x_lock);
549
550   g_free (xwindow);
551 }
552
553 static void
554 gst_xvimagesink_xwindow_update_geometry (GstXvImageSink * xvimagesink)
555 {
556   XWindowAttributes attr;
557
558   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
559
560   /* Update the window geometry */
561   g_mutex_lock (xvimagesink->x_lock);
562   if (G_UNLIKELY (xvimagesink->xwindow == NULL)) {
563     g_mutex_unlock (xvimagesink->x_lock);
564     return;
565   }
566
567   XGetWindowAttributes (xvimagesink->xcontext->disp,
568       xvimagesink->xwindow->win, &attr);
569
570   xvimagesink->xwindow->width = attr.width;
571   xvimagesink->xwindow->height = attr.height;
572
573   if (!xvimagesink->have_render_rect) {
574     xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
575     xvimagesink->render_rect.w = attr.width;
576     xvimagesink->render_rect.h = attr.height;
577   }
578
579   g_mutex_unlock (xvimagesink->x_lock);
580 }
581
582 static void
583 gst_xvimagesink_xwindow_clear (GstXvImageSink * xvimagesink,
584     GstXWindow * xwindow)
585 {
586   g_return_if_fail (xwindow != NULL);
587   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
588
589   g_mutex_lock (xvimagesink->x_lock);
590
591   XvStopVideo (xvimagesink->xcontext->disp, xvimagesink->xcontext->xv_port_id,
592       xwindow->win);
593
594   XSync (xvimagesink->xcontext->disp, FALSE);
595
596   g_mutex_unlock (xvimagesink->x_lock);
597 }
598
599 /* This function commits our internal colorbalance settings to our grabbed Xv
600    port. If the xcontext is not initialized yet it simply returns */
601 static void
602 gst_xvimagesink_update_colorbalance (GstXvImageSink * xvimagesink)
603 {
604   GList *channels = NULL;
605
606   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
607
608   /* If we haven't initialized the X context we can't update anything */
609   if (xvimagesink->xcontext == NULL)
610     return;
611
612   /* Don't set the attributes if they haven't been changed, to avoid
613    * rounding errors changing the values */
614   if (!xvimagesink->cb_changed)
615     return;
616
617   /* For each channel of the colorbalance we calculate the correct value
618      doing range conversion and then set the Xv port attribute to match our
619      values. */
620   channels = xvimagesink->xcontext->channels_list;
621
622   while (channels) {
623     if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) {
624       GstColorBalanceChannel *channel = NULL;
625       Atom prop_atom;
626       gint value = 0;
627       gdouble convert_coef;
628
629       channel = GST_COLOR_BALANCE_CHANNEL (channels->data);
630       g_object_ref (channel);
631
632       /* Our range conversion coef */
633       convert_coef = (channel->max_value - channel->min_value) / 2000.0;
634
635       if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
636         value = xvimagesink->hue;
637       } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
638         value = xvimagesink->saturation;
639       } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
640         value = xvimagesink->contrast;
641       } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
642         value = xvimagesink->brightness;
643       } else {
644         g_warning ("got an unknown channel %s", channel->label);
645         g_object_unref (channel);
646         return;
647       }
648
649       /* Committing to Xv port */
650       g_mutex_lock (xvimagesink->x_lock);
651       prop_atom =
652           XInternAtom (xvimagesink->xcontext->disp, channel->label, True);
653       if (prop_atom != None) {
654         int xv_value;
655         xv_value =
656             floor (0.5 + (value + 1000) * convert_coef + channel->min_value);
657         XvSetPortAttribute (xvimagesink->xcontext->disp,
658             xvimagesink->xcontext->xv_port_id, prop_atom, xv_value);
659       }
660       g_mutex_unlock (xvimagesink->x_lock);
661
662       g_object_unref (channel);
663     }
664     channels = g_list_next (channels);
665   }
666 }
667
668 /* This function handles XEvents that might be in the queue. It generates
669    GstEvent that will be sent upstream in the pipeline to handle interactivity
670    and navigation. It will also listen for configure events on the window to
671    trigger caps renegotiation so on the fly software scaling can work. */
672 static void
673 gst_xvimagesink_handle_xevents (GstXvImageSink * xvimagesink)
674 {
675   XEvent e;
676   guint pointer_x = 0, pointer_y = 0;
677   gboolean pointer_moved = FALSE;
678   gboolean exposed = FALSE, configured = FALSE;
679
680   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
681
682   /* Handle Interaction, produces navigation events */
683
684   /* We get all pointer motion events, only the last position is
685      interesting. */
686   g_mutex_lock (xvimagesink->flow_lock);
687   g_mutex_lock (xvimagesink->x_lock);
688   while (XCheckWindowEvent (xvimagesink->xcontext->disp,
689           xvimagesink->xwindow->win, PointerMotionMask, &e)) {
690     g_mutex_unlock (xvimagesink->x_lock);
691     g_mutex_unlock (xvimagesink->flow_lock);
692
693     switch (e.type) {
694       case MotionNotify:
695         pointer_x = e.xmotion.x;
696         pointer_y = e.xmotion.y;
697         pointer_moved = TRUE;
698         break;
699       default:
700         break;
701     }
702     g_mutex_lock (xvimagesink->flow_lock);
703     g_mutex_lock (xvimagesink->x_lock);
704   }
705
706   if (pointer_moved) {
707     g_mutex_unlock (xvimagesink->x_lock);
708     g_mutex_unlock (xvimagesink->flow_lock);
709
710     GST_DEBUG ("xvimagesink pointer moved over window at %d,%d",
711         pointer_x, pointer_y);
712     gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
713         "mouse-move", 0, e.xbutton.x, e.xbutton.y);
714
715     g_mutex_lock (xvimagesink->flow_lock);
716     g_mutex_lock (xvimagesink->x_lock);
717   }
718
719   /* We get all events on our window to throw them upstream */
720   while (XCheckWindowEvent (xvimagesink->xcontext->disp,
721           xvimagesink->xwindow->win,
722           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask,
723           &e)) {
724     KeySym keysym;
725
726     /* We lock only for the X function call */
727     g_mutex_unlock (xvimagesink->x_lock);
728     g_mutex_unlock (xvimagesink->flow_lock);
729
730     switch (e.type) {
731       case ButtonPress:
732         /* Mouse button pressed over our window. We send upstream
733            events for interactivity/navigation */
734         GST_DEBUG ("xvimagesink button %d pressed over window at %d,%d",
735             e.xbutton.button, e.xbutton.x, e.xbutton.y);
736         gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
737             "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
738         break;
739       case ButtonRelease:
740         /* Mouse button released over our window. We send upstream
741            events for interactivity/navigation */
742         GST_DEBUG ("xvimagesink button %d released over window at %d,%d",
743             e.xbutton.button, e.xbutton.x, e.xbutton.y);
744         gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink),
745             "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
746         break;
747       case KeyPress:
748       case KeyRelease:
749         /* Key pressed/released over our window. We send upstream
750            events for interactivity/navigation */
751         GST_DEBUG ("xvimagesink key %d pressed over window at %d,%d",
752             e.xkey.keycode, e.xkey.x, e.xkey.y);
753         g_mutex_lock (xvimagesink->x_lock);
754         keysym = XKeycodeToKeysym (xvimagesink->xcontext->disp,
755             e.xkey.keycode, 0);
756         g_mutex_unlock (xvimagesink->x_lock);
757         if (keysym != NoSymbol) {
758           char *key_str = NULL;
759
760           g_mutex_lock (xvimagesink->x_lock);
761           key_str = XKeysymToString (keysym);
762           g_mutex_unlock (xvimagesink->x_lock);
763           gst_navigation_send_key_event (GST_NAVIGATION (xvimagesink),
764               e.type == KeyPress ? "key-press" : "key-release", key_str);
765         } else {
766           gst_navigation_send_key_event (GST_NAVIGATION (xvimagesink),
767               e.type == KeyPress ? "key-press" : "key-release", "unknown");
768         }
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 >= 0 &&
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 #if !GLIB_CHECK_VERSION (2, 31, 0)
1181       xvimagesink->event_thread = g_thread_create (
1182           (GThreadFunc) gst_xvimagesink_event_thread, xvimagesink, TRUE, NULL);
1183 #else
1184       xvimagesink->event_thread = g_thread_try_new ("xvimagesink-events",
1185           (GThreadFunc) gst_xvimagesink_event_thread, xvimagesink, NULL);
1186 #endif
1187     }
1188   } else {
1189     if (xvimagesink->event_thread) {
1190       GST_DEBUG_OBJECT (xvimagesink, "stop xevent thread, expose %d, events %d",
1191           xvimagesink->handle_expose, xvimagesink->handle_events);
1192       xvimagesink->running = FALSE;
1193       /* grab thread and mark it as NULL */
1194       thread = xvimagesink->event_thread;
1195       xvimagesink->event_thread = NULL;
1196     }
1197   }
1198   GST_OBJECT_UNLOCK (xvimagesink);
1199
1200   /* Wait for our event thread to finish */
1201   if (thread)
1202     g_thread_join (thread);
1203
1204 }
1205
1206
1207 /* This function calculates the pixel aspect ratio based on the properties
1208  * in the xcontext structure and stores it there. */
1209 static void
1210 gst_xvimagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
1211 {
1212   static const gint par[][2] = {
1213     {1, 1},                     /* regular screen */
1214     {16, 15},                   /* PAL TV */
1215     {11, 10},                   /* 525 line Rec.601 video */
1216     {54, 59},                   /* 625 line Rec.601 video */
1217     {64, 45},                   /* 1280x1024 on 16:9 display */
1218     {5, 3},                     /* 1280x1024 on 4:3 display */
1219     {4, 3}                      /*  800x600 on 16:9 display */
1220   };
1221   gint i;
1222   gint index;
1223   gdouble ratio;
1224   gdouble delta;
1225
1226 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
1227
1228   /* first calculate the "real" ratio based on the X values;
1229    * which is the "physical" w/h divided by the w/h in pixels of the display */
1230   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
1231       / (xcontext->heightmm * xcontext->width);
1232
1233   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
1234    * override here */
1235   if (xcontext->width == 720 && xcontext->height == 576) {
1236     ratio = 4.0 * 576 / (3.0 * 720);
1237   }
1238   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
1239
1240   /* now find the one from par[][2] with the lowest delta to the real one */
1241   delta = DELTA (0);
1242   index = 0;
1243
1244   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
1245     gdouble this_delta = DELTA (i);
1246
1247     if (this_delta < delta) {
1248       index = i;
1249       delta = this_delta;
1250     }
1251   }
1252
1253   GST_DEBUG ("Decided on index %d (%d/%d)", index,
1254       par[index][0], par[index][1]);
1255
1256   g_free (xcontext->par);
1257   xcontext->par = g_new0 (GValue, 1);
1258   g_value_init (xcontext->par, GST_TYPE_FRACTION);
1259   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
1260   GST_DEBUG ("set xcontext PAR to %d/%d",
1261       gst_value_get_fraction_numerator (xcontext->par),
1262       gst_value_get_fraction_denominator (xcontext->par));
1263 }
1264
1265 /* This function gets the X Display and global info about it. Everything is
1266    stored in our object and will be cleaned when the object is disposed. Note
1267    here that caps for supported format are generated without any window or
1268    image creation */
1269 static GstXContext *
1270 gst_xvimagesink_xcontext_get (GstXvImageSink * xvimagesink)
1271 {
1272   GstXContext *xcontext = NULL;
1273   XPixmapFormatValues *px_formats = NULL;
1274   gint nb_formats = 0, i, j, N_attr;
1275   XvAttribute *xv_attr;
1276   Atom prop_atom;
1277   const char *channels[4] = { "XV_HUE", "XV_SATURATION",
1278     "XV_BRIGHTNESS", "XV_CONTRAST"
1279   };
1280
1281   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
1282
1283   xcontext = g_new0 (GstXContext, 1);
1284   xcontext->im_format = 0;
1285
1286   g_mutex_lock (xvimagesink->x_lock);
1287
1288   xcontext->disp = XOpenDisplay (xvimagesink->display_name);
1289
1290   if (!xcontext->disp) {
1291     g_mutex_unlock (xvimagesink->x_lock);
1292     g_free (xcontext);
1293     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1294         ("Could not initialise Xv output"), ("Could not open display"));
1295     return NULL;
1296   }
1297
1298   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1299   xcontext->screen_num = DefaultScreen (xcontext->disp);
1300   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1301   xcontext->root = DefaultRootWindow (xcontext->disp);
1302   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1303   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1304   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1305
1306   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1307   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1308   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1309   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1310
1311   GST_DEBUG_OBJECT (xvimagesink, "X reports %dx%d pixels and %d mm x %d mm",
1312       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1313
1314   gst_xvimagesink_calculate_pixel_aspect_ratio (xcontext);
1315   /* We get supported pixmap formats at supported depth */
1316   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1317
1318   if (!px_formats) {
1319     XCloseDisplay (xcontext->disp);
1320     g_mutex_unlock (xvimagesink->x_lock);
1321     g_free (xcontext->par);
1322     g_free (xcontext);
1323     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS,
1324         ("Could not initialise Xv output"), ("Could not get pixel formats"));
1325     return NULL;
1326   }
1327
1328   /* We get bpp value corresponding to our running depth */
1329   for (i = 0; i < nb_formats; i++) {
1330     if (px_formats[i].depth == xcontext->depth)
1331       xcontext->bpp = px_formats[i].bits_per_pixel;
1332   }
1333
1334   XFree (px_formats);
1335
1336   xcontext->endianness =
1337       (ImageByteOrder (xcontext->disp) ==
1338       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1339
1340   /* our caps system handles 24/32bpp RGB as big-endian. */
1341   if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
1342       xcontext->endianness == G_LITTLE_ENDIAN) {
1343     xcontext->endianness = G_BIG_ENDIAN;
1344     xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask);
1345     xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask);
1346     xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask);
1347     if (xcontext->bpp == 24) {
1348       xcontext->visual->red_mask >>= 8;
1349       xcontext->visual->green_mask >>= 8;
1350       xcontext->visual->blue_mask >>= 8;
1351     }
1352   }
1353
1354   xcontext->caps = gst_xvimagesink_get_xv_support (xvimagesink, xcontext);
1355
1356   /* Search for XShm extension support */
1357 #ifdef HAVE_XSHM
1358   if (XShmQueryExtension (xcontext->disp) &&
1359       gst_xvimagesink_check_xshm_calls (xvimagesink, xcontext)) {
1360     xcontext->use_xshm = TRUE;
1361     GST_DEBUG ("xvimagesink is using XShm extension");
1362   } else
1363 #endif /* HAVE_XSHM */
1364   {
1365     xcontext->use_xshm = FALSE;
1366     GST_DEBUG ("xvimagesink is not using XShm extension");
1367   }
1368
1369   if (!xcontext->caps) {
1370     XCloseDisplay (xcontext->disp);
1371     g_mutex_unlock (xvimagesink->x_lock);
1372     g_free (xcontext->par);
1373     g_free (xcontext);
1374     /* GST_ELEMENT_ERROR is thrown by gst_xvimagesink_get_xv_support */
1375     return NULL;
1376   }
1377
1378   xv_attr = XvQueryPortAttributes (xcontext->disp,
1379       xcontext->xv_port_id, &N_attr);
1380
1381
1382   /* Generate the channels list */
1383   for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
1384     XvAttribute *matching_attr = NULL;
1385
1386     /* Retrieve the property atom if it exists. If it doesn't exist,
1387      * the attribute itself must not either, so we can skip */
1388     prop_atom = XInternAtom (xcontext->disp, channels[i], True);
1389     if (prop_atom == None)
1390       continue;
1391
1392     if (xv_attr != NULL) {
1393       for (j = 0; j < N_attr && matching_attr == NULL; ++j)
1394         if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
1395           matching_attr = xv_attr + j;
1396     }
1397
1398     if (matching_attr) {
1399       GstColorBalanceChannel *channel;
1400
1401       channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
1402       channel->label = g_strdup (channels[i]);
1403       channel->min_value = matching_attr ? matching_attr->min_value : -1000;
1404       channel->max_value = matching_attr ? matching_attr->max_value : 1000;
1405
1406       xcontext->channels_list = g_list_append (xcontext->channels_list,
1407           channel);
1408
1409       /* If the colorbalance settings have not been touched we get Xv values
1410          as defaults and update our internal variables */
1411       if (!xvimagesink->cb_changed) {
1412         gint val;
1413
1414         XvGetPortAttribute (xcontext->disp, xcontext->xv_port_id,
1415             prop_atom, &val);
1416         /* Normalize val to [-1000, 1000] */
1417         val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
1418             (double) (channel->max_value - channel->min_value));
1419
1420         if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
1421           xvimagesink->hue = val;
1422         else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
1423           xvimagesink->saturation = val;
1424         else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
1425           xvimagesink->brightness = val;
1426         else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
1427           xvimagesink->contrast = val;
1428       }
1429     }
1430   }
1431
1432   if (xv_attr)
1433     XFree (xv_attr);
1434
1435   g_mutex_unlock (xvimagesink->x_lock);
1436
1437   return xcontext;
1438 }
1439
1440 /* This function cleans the X context. Closing the Display, releasing the XV
1441    port and unrefing the caps for supported formats. */
1442 static void
1443 gst_xvimagesink_xcontext_clear (GstXvImageSink * xvimagesink)
1444 {
1445   GList *formats_list, *channels_list;
1446   GstXContext *xcontext;
1447   gint i = 0;
1448
1449   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
1450
1451   GST_OBJECT_LOCK (xvimagesink);
1452   if (xvimagesink->xcontext == NULL) {
1453     GST_OBJECT_UNLOCK (xvimagesink);
1454     return;
1455   }
1456
1457   /* Take the XContext from the sink and clean it up */
1458   xcontext = xvimagesink->xcontext;
1459   xvimagesink->xcontext = NULL;
1460
1461   GST_OBJECT_UNLOCK (xvimagesink);
1462
1463
1464   formats_list = xcontext->formats_list;
1465
1466   while (formats_list) {
1467     GstXvImageFormat *format = formats_list->data;
1468
1469     gst_caps_unref (format->caps);
1470     g_free (format);
1471     formats_list = g_list_next (formats_list);
1472   }
1473
1474   if (xcontext->formats_list)
1475     g_list_free (xcontext->formats_list);
1476
1477   channels_list = xcontext->channels_list;
1478
1479   while (channels_list) {
1480     GstColorBalanceChannel *channel = channels_list->data;
1481
1482     g_object_unref (channel);
1483     channels_list = g_list_next (channels_list);
1484   }
1485
1486   if (xcontext->channels_list)
1487     g_list_free (xcontext->channels_list);
1488
1489   gst_caps_unref (xcontext->caps);
1490   if (xcontext->last_caps)
1491     gst_caps_replace (&xcontext->last_caps, NULL);
1492
1493   for (i = 0; i < xcontext->nb_adaptors; i++) {
1494     g_free (xcontext->adaptors[i]);
1495   }
1496
1497   g_free (xcontext->adaptors);
1498
1499   g_free (xcontext->par);
1500
1501   g_mutex_lock (xvimagesink->x_lock);
1502
1503   GST_DEBUG_OBJECT (xvimagesink, "Closing display and freeing X Context");
1504
1505   XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0);
1506
1507   XCloseDisplay (xcontext->disp);
1508
1509   g_mutex_unlock (xvimagesink->x_lock);
1510
1511   g_free (xcontext);
1512 }
1513
1514 /* Element stuff */
1515
1516 static GstCaps *
1517 gst_xvimagesink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1518 {
1519   GstXvImageSink *xvimagesink;
1520   GstCaps *caps;
1521
1522   xvimagesink = GST_XVIMAGESINK (bsink);
1523
1524   if (xvimagesink->xcontext) {
1525     if (filter)
1526       return gst_caps_intersect_full (filter, xvimagesink->xcontext->caps,
1527           GST_CAPS_INTERSECT_FIRST);
1528     else
1529       return gst_caps_ref (xvimagesink->xcontext->caps);
1530   }
1531
1532   caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (xvimagesink));
1533   if (filter) {
1534     GstCaps *intersection;
1535
1536     intersection =
1537         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1538     gst_caps_unref (caps);
1539     caps = intersection;
1540   }
1541   return caps;
1542 }
1543
1544 static gboolean
1545 gst_xvimagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1546 {
1547   GstXvImageSink *xvimagesink;
1548   GstStructure *structure;
1549   GstBufferPool *newpool, *oldpool;
1550   GstVideoInfo info;
1551   guint32 im_format = 0;
1552   gint video_par_n, video_par_d;        /* video's PAR */
1553   gint display_par_n, display_par_d;    /* display's PAR */
1554   guint num, den;
1555   gint size;
1556
1557   xvimagesink = GST_XVIMAGESINK (bsink);
1558
1559   GST_DEBUG_OBJECT (xvimagesink,
1560       "In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %"
1561       GST_PTR_FORMAT, xvimagesink->xcontext->caps, caps);
1562
1563   if (!gst_caps_can_intersect (xvimagesink->xcontext->caps, caps))
1564     goto incompatible_caps;
1565
1566   if (!gst_video_info_from_caps (&info, caps))
1567     goto invalid_format;
1568
1569   structure = gst_caps_get_structure (caps, 0);
1570
1571   xvimagesink->fps_n = info.fps_n;
1572   xvimagesink->fps_d = info.fps_d;
1573
1574   xvimagesink->video_width = info.width;
1575   xvimagesink->video_height = info.height;
1576
1577   im_format = gst_xvimagesink_get_format_from_info (xvimagesink, &info);
1578   if (im_format == -1)
1579     goto invalid_format;
1580
1581   size = info.size;
1582
1583   /* get aspect ratio from caps if it's present, and
1584    * convert video width and height to a display width and height
1585    * using wd / hd = wv / hv * PARv / PARd */
1586
1587   /* get video's PAR */
1588   video_par_n = info.par_n;
1589   video_par_d = info.par_d;
1590
1591   /* get display's PAR */
1592   if (xvimagesink->par) {
1593     display_par_n = gst_value_get_fraction_numerator (xvimagesink->par);
1594     display_par_d = gst_value_get_fraction_denominator (xvimagesink->par);
1595   } else {
1596     display_par_n = 1;
1597     display_par_d = 1;
1598   }
1599
1600   if (!gst_video_calculate_display_ratio (&num, &den, info.width,
1601           info.height, video_par_n, video_par_d, display_par_n, display_par_d))
1602     goto no_disp_ratio;
1603
1604   GST_DEBUG_OBJECT (xvimagesink,
1605       "video width/height: %dx%d, calculated display ratio: %d/%d",
1606       info.width, info.height, num, den);
1607
1608   /* now find a width x height that respects this display ratio.
1609    * prefer those that have one of w/h the same as the incoming video
1610    * using wd / hd = num / den */
1611
1612   /* start with same height, because of interlaced video */
1613   /* check hd / den is an integer scale factor, and scale wd with the PAR */
1614   if (info.height % den == 0) {
1615     GST_DEBUG_OBJECT (xvimagesink, "keeping video height");
1616     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1617         gst_util_uint64_scale_int (info.height, num, den);
1618     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1619   } else if (info.width % num == 0) {
1620     GST_DEBUG_OBJECT (xvimagesink, "keeping video width");
1621     GST_VIDEO_SINK_WIDTH (xvimagesink) = info.width;
1622     GST_VIDEO_SINK_HEIGHT (xvimagesink) = (guint)
1623         gst_util_uint64_scale_int (info.width, den, num);
1624   } else {
1625     GST_DEBUG_OBJECT (xvimagesink, "approximating while keeping video height");
1626     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1627         gst_util_uint64_scale_int (info.height, num, den);
1628     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1629   }
1630   GST_DEBUG_OBJECT (xvimagesink, "scaling to %dx%d",
1631       GST_VIDEO_SINK_WIDTH (xvimagesink), GST_VIDEO_SINK_HEIGHT (xvimagesink));
1632
1633   /* Notify application to set xwindow id now */
1634   g_mutex_lock (xvimagesink->flow_lock);
1635   if (!xvimagesink->xwindow) {
1636     g_mutex_unlock (xvimagesink->flow_lock);
1637     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (xvimagesink));
1638   } else {
1639     g_mutex_unlock (xvimagesink->flow_lock);
1640   }
1641
1642   /* Creating our window and our image with the display size in pixels */
1643   if (GST_VIDEO_SINK_WIDTH (xvimagesink) <= 0 ||
1644       GST_VIDEO_SINK_HEIGHT (xvimagesink) <= 0)
1645     goto no_display_size;
1646
1647   g_mutex_lock (xvimagesink->flow_lock);
1648   if (!xvimagesink->xwindow) {
1649     xvimagesink->xwindow = gst_xvimagesink_xwindow_new (xvimagesink,
1650         GST_VIDEO_SINK_WIDTH (xvimagesink),
1651         GST_VIDEO_SINK_HEIGHT (xvimagesink));
1652   }
1653
1654   xvimagesink->info = info;
1655
1656   /* After a resize, we want to redraw the borders in case the new frame size
1657    * doesn't cover the same area */
1658   xvimagesink->redraw_border = TRUE;
1659
1660   /* create a new pool for the new configuration */
1661   newpool = gst_xvimage_buffer_pool_new (xvimagesink);
1662
1663   structure = gst_buffer_pool_get_config (newpool);
1664   gst_buffer_pool_config_set (structure, caps, size, 2, 0, 0, 15);
1665   if (!gst_buffer_pool_set_config (newpool, structure))
1666     goto config_failed;
1667
1668   oldpool = xvimagesink->pool;
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 form 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     if (gst_buffer_get_size (to_put) < gst_buffer_get_size (buf))
1847       goto wrong_size;
1848
1849     GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, xvimagesink,
1850         "slow copy into bufferpool buffer %p", to_put);
1851
1852     if (!gst_video_frame_map (&src, &xvimagesink->info, buf, GST_MAP_READ))
1853       goto invalid_buffer;
1854
1855     if (!gst_video_frame_map (&dest, &xvimagesink->info, to_put, GST_MAP_WRITE)) {
1856       gst_video_frame_unmap (&src);
1857       goto invalid_buffer;
1858     }
1859
1860     gst_video_frame_copy (&dest, &src);
1861
1862     gst_video_frame_unmap (&dest);
1863     gst_video_frame_unmap (&src);
1864   }
1865
1866   if (!gst_xvimagesink_xvimage_put (xvimagesink, to_put))
1867     goto no_window;
1868
1869 done:
1870   if (to_put != buf)
1871     gst_buffer_unref (to_put);
1872
1873   return res;
1874
1875   /* ERRORS */
1876 no_pool:
1877   {
1878     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1879         ("Internal error: can't allocate images"),
1880         ("We don't have a bufferpool negotiated"));
1881     return GST_FLOW_ERROR;
1882   }
1883 no_buffer:
1884   {
1885     /* No image available. That's very bad ! */
1886     GST_WARNING_OBJECT (xvimagesink, "could not create image");
1887     return res;
1888   }
1889 wrong_size:
1890   {
1891     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1892         ("Failed to create output image buffer"),
1893         ("XServer allocated buffer size did not match input buffer %"
1894             G_GSIZE_FORMAT " - %" G_GSIZE_FORMAT, gst_buffer_get_size (to_put),
1895             gst_buffer_get_size (buf)));
1896     res = GST_FLOW_ERROR;
1897     goto done;
1898   }
1899 invalid_buffer:
1900   {
1901     /* No Window available to put our image into */
1902     GST_WARNING_OBJECT (xvimagesink, "could map image");
1903     res = GST_FLOW_OK;
1904     goto done;
1905   }
1906 no_window:
1907   {
1908     /* No Window available to put our image into */
1909     GST_WARNING_OBJECT (xvimagesink, "could not output image - no window");
1910     res = GST_FLOW_ERROR;
1911     goto done;
1912   }
1913 activate_failed:
1914   {
1915     GST_ERROR_OBJECT (xvimagesink, "failed to activate bufferpool.");
1916     res = GST_FLOW_ERROR;
1917     goto done;
1918   }
1919 }
1920
1921 static gboolean
1922 gst_xvimagesink_event (GstBaseSink * sink, GstEvent * event)
1923 {
1924   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (sink);
1925
1926   switch (GST_EVENT_TYPE (event)) {
1927     case GST_EVENT_TAG:{
1928       GstTagList *l;
1929       gchar *title = NULL;
1930
1931       gst_event_parse_tag (event, &l);
1932       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1933
1934       if (title) {
1935         GST_DEBUG_OBJECT (xvimagesink, "got tags, title='%s'", title);
1936         gst_xvimagesink_xwindow_set_title (xvimagesink, xvimagesink->xwindow,
1937             title);
1938
1939         g_free (title);
1940       }
1941       break;
1942     }
1943     default:
1944       break;
1945   }
1946   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1947 }
1948
1949 static gboolean
1950 gst_xvimagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1951 {
1952   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (bsink);
1953   GstBufferPool *pool;
1954   GstStructure *config;
1955   GstCaps *caps;
1956   guint size;
1957   gboolean need_pool;
1958
1959   gst_query_parse_allocation (query, &caps, &need_pool);
1960
1961   if (caps == NULL)
1962     goto no_caps;
1963
1964   g_mutex_lock (xvimagesink->flow_lock);
1965   if ((pool = xvimagesink->pool))
1966     gst_object_ref (pool);
1967   g_mutex_unlock (xvimagesink->flow_lock);
1968
1969   if (pool != NULL) {
1970     const GstCaps *pcaps;
1971
1972     /* we had a pool, check caps */
1973     GST_DEBUG_OBJECT (xvimagesink, "check existing pool caps");
1974     config = gst_buffer_pool_get_config (pool);
1975     gst_buffer_pool_config_get (config, &pcaps, &size, NULL, NULL, NULL, NULL);
1976
1977     if (!gst_caps_is_equal (caps, pcaps)) {
1978       GST_DEBUG_OBJECT (xvimagesink, "pool has different caps");
1979       /* different caps, we can't use this pool */
1980       gst_object_unref (pool);
1981       pool = NULL;
1982     }
1983   }
1984   if (pool == NULL && need_pool) {
1985     GstVideoInfo info;
1986
1987     GST_DEBUG_OBJECT (xvimagesink, "create new pool");
1988     pool = gst_xvimage_buffer_pool_new (xvimagesink);
1989
1990     if (!gst_video_info_from_caps (&info, caps))
1991       goto invalid_caps;
1992
1993     /* the normal size of a frame */
1994     size = info.size;
1995
1996     config = gst_buffer_pool_get_config (pool);
1997     gst_buffer_pool_config_set (config, caps, size, 0, 0, 0, 0);
1998     if (!gst_buffer_pool_set_config (pool, config))
1999       goto config_failed;
2000   }
2001   /* we need at least 2 buffer because we hold on to the last one */
2002   gst_query_set_allocation_params (query, size, 2, 0, 0, 0, pool);
2003
2004   /* we also support various metadata */
2005   gst_query_add_allocation_meta (query, GST_VIDEO_META_API);
2006   gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API);
2007
2008   gst_object_unref (pool);
2009
2010   return TRUE;
2011
2012   /* ERRORS */
2013 no_caps:
2014   {
2015     GST_DEBUG_OBJECT (bsink, "no caps specified");
2016     return FALSE;
2017   }
2018 invalid_caps:
2019   {
2020     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
2021     return FALSE;
2022   }
2023 config_failed:
2024   {
2025     GST_DEBUG_OBJECT (bsink, "failed setting config");
2026     return FALSE;
2027   }
2028 }
2029
2030 /* Interfaces stuff */
2031 static void
2032 gst_xvimagesink_navigation_send_event (GstNavigation * navigation,
2033     GstStructure * structure)
2034 {
2035   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (navigation);
2036   GstPad *peer;
2037
2038   if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (xvimagesink)))) {
2039     GstEvent *event;
2040     GstVideoRectangle src, dst, result;
2041     gdouble x, y, xscale = 1.0, yscale = 1.0;
2042
2043     event = gst_event_new_navigation (structure);
2044
2045     /* We take the flow_lock while we look at the window */
2046     g_mutex_lock (xvimagesink->flow_lock);
2047
2048     if (!xvimagesink->xwindow) {
2049       g_mutex_unlock (xvimagesink->flow_lock);
2050       return;
2051     }
2052
2053     if (xvimagesink->keep_aspect) {
2054       /* We get the frame position using the calculated geometry from _setcaps
2055          that respect pixel aspect ratios */
2056       src.w = GST_VIDEO_SINK_WIDTH (xvimagesink);
2057       src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink);
2058       dst.w = xvimagesink->render_rect.w;
2059       dst.h = xvimagesink->render_rect.h;
2060
2061       gst_video_sink_center_rect (src, dst, &result, TRUE);
2062       result.x += xvimagesink->render_rect.x;
2063       result.y += xvimagesink->render_rect.y;
2064     } else {
2065       memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
2066     }
2067
2068     g_mutex_unlock (xvimagesink->flow_lock);
2069
2070     /* We calculate scaling using the original video frames geometry to include
2071        pixel aspect ratio scaling. */
2072     xscale = (gdouble) xvimagesink->video_width / result.w;
2073     yscale = (gdouble) xvimagesink->video_height / result.h;
2074
2075     /* Converting pointer coordinates to the non scaled geometry */
2076     if (gst_structure_get_double (structure, "pointer_x", &x)) {
2077       x = MIN (x, result.x + result.w);
2078       x = MAX (x - result.x, 0);
2079       gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
2080           (gdouble) x * xscale, NULL);
2081     }
2082     if (gst_structure_get_double (structure, "pointer_y", &y)) {
2083       y = MIN (y, result.y + result.h);
2084       y = MAX (y - result.y, 0);
2085       gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
2086           (gdouble) y * yscale, NULL);
2087     }
2088
2089     gst_pad_send_event (peer, event);
2090     gst_object_unref (peer);
2091   }
2092 }
2093
2094 static void
2095 gst_xvimagesink_navigation_init (GstNavigationInterface * iface)
2096 {
2097   iface->send_event = gst_xvimagesink_navigation_send_event;
2098 }
2099
2100 static void
2101 gst_xvimagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
2102 {
2103   XID xwindow_id = id;
2104   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2105   GstXWindow *xwindow = NULL;
2106
2107   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2108
2109   g_mutex_lock (xvimagesink->flow_lock);
2110
2111   /* If we already use that window return */
2112   if (xvimagesink->xwindow && (xwindow_id == xvimagesink->xwindow->win)) {
2113     g_mutex_unlock (xvimagesink->flow_lock);
2114     return;
2115   }
2116
2117   /* If the element has not initialized the X11 context try to do so */
2118   if (!xvimagesink->xcontext &&
2119       !(xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink))) {
2120     g_mutex_unlock (xvimagesink->flow_lock);
2121     /* we have thrown a GST_ELEMENT_ERROR now */
2122     return;
2123   }
2124
2125   gst_xvimagesink_update_colorbalance (xvimagesink);
2126
2127   /* If a window is there already we destroy it */
2128   if (xvimagesink->xwindow) {
2129     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2130     xvimagesink->xwindow = NULL;
2131   }
2132
2133   /* If the xid is 0 we go back to an internal window */
2134   if (xwindow_id == 0) {
2135     /* If no width/height caps nego did not happen window will be created
2136        during caps nego then */
2137     if (GST_VIDEO_SINK_WIDTH (xvimagesink)
2138         && GST_VIDEO_SINK_HEIGHT (xvimagesink)) {
2139       xwindow =
2140           gst_xvimagesink_xwindow_new (xvimagesink,
2141           GST_VIDEO_SINK_WIDTH (xvimagesink),
2142           GST_VIDEO_SINK_HEIGHT (xvimagesink));
2143     }
2144   } else {
2145     XWindowAttributes attr;
2146
2147     xwindow = g_new0 (GstXWindow, 1);
2148     xwindow->win = xwindow_id;
2149
2150     /* Set the event we want to receive and create a GC */
2151     g_mutex_lock (xvimagesink->x_lock);
2152
2153     XGetWindowAttributes (xvimagesink->xcontext->disp, xwindow->win, &attr);
2154
2155     xwindow->width = attr.width;
2156     xwindow->height = attr.height;
2157     xwindow->internal = FALSE;
2158     if (!xvimagesink->have_render_rect) {
2159       xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
2160       xvimagesink->render_rect.w = attr.width;
2161       xvimagesink->render_rect.h = attr.height;
2162     }
2163     if (xvimagesink->handle_events) {
2164       XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask |
2165           StructureNotifyMask | PointerMotionMask | KeyPressMask |
2166           KeyReleaseMask);
2167     }
2168
2169     xwindow->gc = XCreateGC (xvimagesink->xcontext->disp,
2170         xwindow->win, 0, NULL);
2171     g_mutex_unlock (xvimagesink->x_lock);
2172   }
2173
2174   if (xwindow)
2175     xvimagesink->xwindow = xwindow;
2176
2177   g_mutex_unlock (xvimagesink->flow_lock);
2178 }
2179
2180 static void
2181 gst_xvimagesink_expose (GstVideoOverlay * overlay)
2182 {
2183   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2184
2185   GST_DEBUG ("doing expose");
2186   gst_xvimagesink_xwindow_update_geometry (xvimagesink);
2187   gst_xvimagesink_xvimage_put (xvimagesink, NULL);
2188 }
2189
2190 static void
2191 gst_xvimagesink_set_event_handling (GstVideoOverlay * overlay,
2192     gboolean handle_events)
2193 {
2194   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2195
2196   xvimagesink->handle_events = handle_events;
2197
2198   g_mutex_lock (xvimagesink->flow_lock);
2199
2200   if (G_UNLIKELY (!xvimagesink->xwindow)) {
2201     g_mutex_unlock (xvimagesink->flow_lock);
2202     return;
2203   }
2204
2205   g_mutex_lock (xvimagesink->x_lock);
2206
2207   if (handle_events) {
2208     if (xvimagesink->xwindow->internal) {
2209       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2210           ExposureMask | StructureNotifyMask | PointerMotionMask |
2211           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
2212     } else {
2213       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2214           ExposureMask | StructureNotifyMask | PointerMotionMask |
2215           KeyPressMask | KeyReleaseMask);
2216     }
2217   } else {
2218     XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, 0);
2219   }
2220
2221   g_mutex_unlock (xvimagesink->x_lock);
2222
2223   g_mutex_unlock (xvimagesink->flow_lock);
2224 }
2225
2226 static void
2227 gst_xvimagesink_set_render_rectangle (GstVideoOverlay * overlay, gint x, gint y,
2228     gint width, gint height)
2229 {
2230   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2231
2232   /* FIXME: how about some locking? */
2233   if (width >= 0 && height >= 0) {
2234     xvimagesink->render_rect.x = x;
2235     xvimagesink->render_rect.y = y;
2236     xvimagesink->render_rect.w = width;
2237     xvimagesink->render_rect.h = height;
2238     xvimagesink->have_render_rect = TRUE;
2239   } else {
2240     xvimagesink->render_rect.x = 0;
2241     xvimagesink->render_rect.y = 0;
2242     xvimagesink->render_rect.w = xvimagesink->xwindow->width;
2243     xvimagesink->render_rect.h = xvimagesink->xwindow->height;
2244     xvimagesink->have_render_rect = FALSE;
2245   }
2246 }
2247
2248 static void
2249 gst_xvimagesink_video_overlay_init (GstVideoOverlayInterface * iface)
2250 {
2251   iface->set_window_handle = gst_xvimagesink_set_window_handle;
2252   iface->expose = gst_xvimagesink_expose;
2253   iface->handle_events = gst_xvimagesink_set_event_handling;
2254   iface->set_render_rectangle = gst_xvimagesink_set_render_rectangle;
2255 }
2256
2257 static const GList *
2258 gst_xvimagesink_colorbalance_list_channels (GstColorBalance * balance)
2259 {
2260   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2261
2262   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
2263
2264   if (xvimagesink->xcontext)
2265     return xvimagesink->xcontext->channels_list;
2266   else
2267     return NULL;
2268 }
2269
2270 static void
2271 gst_xvimagesink_colorbalance_set_value (GstColorBalance * balance,
2272     GstColorBalanceChannel * channel, gint value)
2273 {
2274   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2275
2276   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2277   g_return_if_fail (channel->label != NULL);
2278
2279   xvimagesink->cb_changed = TRUE;
2280
2281   /* Normalize val to [-1000, 1000] */
2282   value = floor (0.5 + -1000 + 2000 * (value - channel->min_value) /
2283       (double) (channel->max_value - channel->min_value));
2284
2285   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2286     xvimagesink->hue = value;
2287   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2288     xvimagesink->saturation = value;
2289   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2290     xvimagesink->contrast = value;
2291   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2292     xvimagesink->brightness = value;
2293   } else {
2294     g_warning ("got an unknown channel %s", channel->label);
2295     return;
2296   }
2297
2298   gst_xvimagesink_update_colorbalance (xvimagesink);
2299 }
2300
2301 static gint
2302 gst_xvimagesink_colorbalance_get_value (GstColorBalance * balance,
2303     GstColorBalanceChannel * channel)
2304 {
2305   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2306   gint value = 0;
2307
2308   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), 0);
2309   g_return_val_if_fail (channel->label != NULL, 0);
2310
2311   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2312     value = xvimagesink->hue;
2313   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2314     value = xvimagesink->saturation;
2315   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2316     value = xvimagesink->contrast;
2317   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2318     value = xvimagesink->brightness;
2319   } else {
2320     g_warning ("got an unknown channel %s", channel->label);
2321   }
2322
2323   /* Normalize val to [channel->min_value, channel->max_value] */
2324   value = channel->min_value + (channel->max_value - channel->min_value) *
2325       (value + 1000) / 2000;
2326
2327   return value;
2328 }
2329
2330 static void
2331 gst_xvimagesink_colorbalance_init (GstColorBalanceInterface * iface)
2332 {
2333   GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE;
2334   iface->list_channels = gst_xvimagesink_colorbalance_list_channels;
2335   iface->set_value = gst_xvimagesink_colorbalance_set_value;
2336   iface->get_value = gst_xvimagesink_colorbalance_get_value;
2337 }
2338
2339 static const GList *
2340 gst_xvimagesink_probe_get_properties (GstPropertyProbe * probe)
2341 {
2342   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
2343   static GList *list = NULL;
2344
2345   if (!list) {
2346     list = g_list_append (NULL, g_object_class_find_property (klass, "device"));
2347     list =
2348         g_list_append (list, g_object_class_find_property (klass,
2349             "autopaint-colorkey"));
2350     list =
2351         g_list_append (list, g_object_class_find_property (klass,
2352             "double-buffer"));
2353     list =
2354         g_list_append (list, g_object_class_find_property (klass, "colorkey"));
2355   }
2356
2357   return list;
2358 }
2359
2360 static void
2361 gst_xvimagesink_probe_probe_property (GstPropertyProbe * probe,
2362     guint prop_id, const GParamSpec * pspec)
2363 {
2364   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2365
2366   switch (prop_id) {
2367     case PROP_DEVICE:
2368     case PROP_AUTOPAINT_COLORKEY:
2369     case PROP_DOUBLE_BUFFER:
2370     case PROP_COLORKEY:
2371       GST_DEBUG_OBJECT (xvimagesink,
2372           "probing device list and get capabilities");
2373       if (!xvimagesink->xcontext) {
2374         GST_DEBUG_OBJECT (xvimagesink, "generating xcontext");
2375         xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink);
2376       }
2377       break;
2378     default:
2379       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2380       break;
2381   }
2382 }
2383
2384 static gboolean
2385 gst_xvimagesink_probe_needs_probe (GstPropertyProbe * probe,
2386     guint prop_id, const GParamSpec * pspec)
2387 {
2388   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2389   gboolean ret = FALSE;
2390
2391   switch (prop_id) {
2392     case PROP_DEVICE:
2393     case PROP_AUTOPAINT_COLORKEY:
2394     case PROP_DOUBLE_BUFFER:
2395     case PROP_COLORKEY:
2396       if (xvimagesink->xcontext != NULL) {
2397         ret = FALSE;
2398       } else {
2399         ret = TRUE;
2400       }
2401       break;
2402     default:
2403       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2404       break;
2405   }
2406
2407   return ret;
2408 }
2409
2410 static GValueArray *
2411 gst_xvimagesink_probe_get_values (GstPropertyProbe * probe,
2412     guint prop_id, const GParamSpec * pspec)
2413 {
2414   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2415   GValueArray *array = NULL;
2416
2417   if (G_UNLIKELY (!xvimagesink->xcontext)) {
2418     GST_WARNING_OBJECT (xvimagesink, "we don't have any xcontext, can't "
2419         "get values");
2420     goto beach;
2421   }
2422
2423   switch (prop_id) {
2424     case PROP_DEVICE:
2425     {
2426       guint i;
2427       GValue value = { 0 };
2428
2429       array = g_value_array_new (xvimagesink->xcontext->nb_adaptors);
2430       g_value_init (&value, G_TYPE_STRING);
2431
2432       for (i = 0; i < xvimagesink->xcontext->nb_adaptors; i++) {
2433         gchar *adaptor_id_s = g_strdup_printf ("%u", i);
2434
2435         g_value_set_string (&value, adaptor_id_s);
2436         g_value_array_append (array, &value);
2437         g_free (adaptor_id_s);
2438       }
2439       g_value_unset (&value);
2440       break;
2441     }
2442     case PROP_AUTOPAINT_COLORKEY:
2443       if (xvimagesink->have_autopaint_colorkey) {
2444         GValue value = { 0 };
2445
2446         array = g_value_array_new (2);
2447         g_value_init (&value, G_TYPE_BOOLEAN);
2448         g_value_set_boolean (&value, FALSE);
2449         g_value_array_append (array, &value);
2450         g_value_set_boolean (&value, TRUE);
2451         g_value_array_append (array, &value);
2452         g_value_unset (&value);
2453       }
2454       break;
2455     case PROP_DOUBLE_BUFFER:
2456       if (xvimagesink->have_double_buffer) {
2457         GValue value = { 0 };
2458
2459         array = g_value_array_new (2);
2460         g_value_init (&value, G_TYPE_BOOLEAN);
2461         g_value_set_boolean (&value, FALSE);
2462         g_value_array_append (array, &value);
2463         g_value_set_boolean (&value, TRUE);
2464         g_value_array_append (array, &value);
2465         g_value_unset (&value);
2466       }
2467       break;
2468     case PROP_COLORKEY:
2469       if (xvimagesink->have_colorkey) {
2470         GValue value = { 0 };
2471
2472         array = g_value_array_new (1);
2473         g_value_init (&value, GST_TYPE_INT_RANGE);
2474         gst_value_set_int_range (&value, 0, 0xffffff);
2475         g_value_array_append (array, &value);
2476         g_value_unset (&value);
2477       }
2478       break;
2479     default:
2480       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2481       break;
2482   }
2483
2484 beach:
2485   return array;
2486 }
2487
2488 static void
2489 gst_xvimagesink_property_probe_interface_init (GstPropertyProbeInterface *
2490     iface)
2491 {
2492   iface->get_properties = gst_xvimagesink_probe_get_properties;
2493   iface->probe_property = gst_xvimagesink_probe_probe_property;
2494   iface->needs_probe = gst_xvimagesink_probe_needs_probe;
2495   iface->get_values = gst_xvimagesink_probe_get_values;
2496 }
2497
2498 /* =========================================== */
2499 /*                                             */
2500 /*              Init & Class init              */
2501 /*                                             */
2502 /* =========================================== */
2503
2504 static void
2505 gst_xvimagesink_set_property (GObject * object, guint prop_id,
2506     const GValue * value, GParamSpec * pspec)
2507 {
2508   GstXvImageSink *xvimagesink;
2509
2510   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2511
2512   xvimagesink = GST_XVIMAGESINK (object);
2513
2514   switch (prop_id) {
2515     case PROP_HUE:
2516       xvimagesink->hue = g_value_get_int (value);
2517       xvimagesink->cb_changed = TRUE;
2518       gst_xvimagesink_update_colorbalance (xvimagesink);
2519       break;
2520     case PROP_CONTRAST:
2521       xvimagesink->contrast = g_value_get_int (value);
2522       xvimagesink->cb_changed = TRUE;
2523       gst_xvimagesink_update_colorbalance (xvimagesink);
2524       break;
2525     case PROP_BRIGHTNESS:
2526       xvimagesink->brightness = g_value_get_int (value);
2527       xvimagesink->cb_changed = TRUE;
2528       gst_xvimagesink_update_colorbalance (xvimagesink);
2529       break;
2530     case PROP_SATURATION:
2531       xvimagesink->saturation = g_value_get_int (value);
2532       xvimagesink->cb_changed = TRUE;
2533       gst_xvimagesink_update_colorbalance (xvimagesink);
2534       break;
2535     case PROP_DISPLAY:
2536       xvimagesink->display_name = g_strdup (g_value_get_string (value));
2537       break;
2538     case PROP_SYNCHRONOUS:
2539       xvimagesink->synchronous = g_value_get_boolean (value);
2540       if (xvimagesink->xcontext) {
2541         XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous);
2542         GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s",
2543             xvimagesink->synchronous ? "TRUE" : "FALSE");
2544       }
2545       break;
2546     case PROP_PIXEL_ASPECT_RATIO:
2547       g_free (xvimagesink->par);
2548       xvimagesink->par = g_new0 (GValue, 1);
2549       g_value_init (xvimagesink->par, GST_TYPE_FRACTION);
2550       if (!g_value_transform (value, xvimagesink->par)) {
2551         g_warning ("Could not transform string to aspect ratio");
2552         gst_value_set_fraction (xvimagesink->par, 1, 1);
2553       }
2554       GST_DEBUG_OBJECT (xvimagesink, "set PAR to %d/%d",
2555           gst_value_get_fraction_numerator (xvimagesink->par),
2556           gst_value_get_fraction_denominator (xvimagesink->par));
2557       break;
2558     case PROP_FORCE_ASPECT_RATIO:
2559       xvimagesink->keep_aspect = g_value_get_boolean (value);
2560       break;
2561     case PROP_HANDLE_EVENTS:
2562       gst_xvimagesink_set_event_handling (GST_VIDEO_OVERLAY (xvimagesink),
2563           g_value_get_boolean (value));
2564       gst_xvimagesink_manage_event_thread (xvimagesink);
2565       break;
2566     case PROP_DEVICE:
2567       xvimagesink->adaptor_no = atoi (g_value_get_string (value));
2568       break;
2569     case PROP_HANDLE_EXPOSE:
2570       xvimagesink->handle_expose = g_value_get_boolean (value);
2571       gst_xvimagesink_manage_event_thread (xvimagesink);
2572       break;
2573     case PROP_DOUBLE_BUFFER:
2574       xvimagesink->double_buffer = g_value_get_boolean (value);
2575       break;
2576     case PROP_AUTOPAINT_COLORKEY:
2577       xvimagesink->autopaint_colorkey = g_value_get_boolean (value);
2578       break;
2579     case PROP_COLORKEY:
2580       xvimagesink->colorkey = g_value_get_int (value);
2581       break;
2582     case PROP_DRAW_BORDERS:
2583       xvimagesink->draw_borders = g_value_get_boolean (value);
2584       break;
2585     default:
2586       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2587       break;
2588   }
2589 }
2590
2591 static void
2592 gst_xvimagesink_get_property (GObject * object, guint prop_id,
2593     GValue * value, GParamSpec * pspec)
2594 {
2595   GstXvImageSink *xvimagesink;
2596
2597   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2598
2599   xvimagesink = GST_XVIMAGESINK (object);
2600
2601   switch (prop_id) {
2602     case PROP_HUE:
2603       g_value_set_int (value, xvimagesink->hue);
2604       break;
2605     case PROP_CONTRAST:
2606       g_value_set_int (value, xvimagesink->contrast);
2607       break;
2608     case PROP_BRIGHTNESS:
2609       g_value_set_int (value, xvimagesink->brightness);
2610       break;
2611     case PROP_SATURATION:
2612       g_value_set_int (value, xvimagesink->saturation);
2613       break;
2614     case PROP_DISPLAY:
2615       g_value_set_string (value, xvimagesink->display_name);
2616       break;
2617     case PROP_SYNCHRONOUS:
2618       g_value_set_boolean (value, xvimagesink->synchronous);
2619       break;
2620     case PROP_PIXEL_ASPECT_RATIO:
2621       if (xvimagesink->par)
2622         g_value_transform (xvimagesink->par, value);
2623       break;
2624     case PROP_FORCE_ASPECT_RATIO:
2625       g_value_set_boolean (value, xvimagesink->keep_aspect);
2626       break;
2627     case PROP_HANDLE_EVENTS:
2628       g_value_set_boolean (value, xvimagesink->handle_events);
2629       break;
2630     case PROP_DEVICE:
2631     {
2632       char *adaptor_no_s = g_strdup_printf ("%u", xvimagesink->adaptor_no);
2633
2634       g_value_set_string (value, adaptor_no_s);
2635       g_free (adaptor_no_s);
2636       break;
2637     }
2638     case PROP_DEVICE_NAME:
2639       if (xvimagesink->xcontext && xvimagesink->xcontext->adaptors) {
2640         g_value_set_string (value,
2641             xvimagesink->xcontext->adaptors[xvimagesink->adaptor_no]);
2642       } else {
2643         g_value_set_string (value, NULL);
2644       }
2645       break;
2646     case PROP_HANDLE_EXPOSE:
2647       g_value_set_boolean (value, xvimagesink->handle_expose);
2648       break;
2649     case PROP_DOUBLE_BUFFER:
2650       g_value_set_boolean (value, xvimagesink->double_buffer);
2651       break;
2652     case PROP_AUTOPAINT_COLORKEY:
2653       g_value_set_boolean (value, xvimagesink->autopaint_colorkey);
2654       break;
2655     case PROP_COLORKEY:
2656       g_value_set_int (value, xvimagesink->colorkey);
2657       break;
2658     case PROP_DRAW_BORDERS:
2659       g_value_set_boolean (value, xvimagesink->draw_borders);
2660       break;
2661     case PROP_WINDOW_WIDTH:
2662       if (xvimagesink->xwindow)
2663         g_value_set_uint64 (value, xvimagesink->xwindow->width);
2664       else
2665         g_value_set_uint64 (value, 0);
2666       break;
2667     case PROP_WINDOW_HEIGHT:
2668       if (xvimagesink->xwindow)
2669         g_value_set_uint64 (value, xvimagesink->xwindow->height);
2670       else
2671         g_value_set_uint64 (value, 0);
2672       break;
2673     default:
2674       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2675       break;
2676   }
2677 }
2678
2679 static void
2680 gst_xvimagesink_reset (GstXvImageSink * xvimagesink)
2681 {
2682   GThread *thread;
2683
2684   GST_OBJECT_LOCK (xvimagesink);
2685   xvimagesink->running = FALSE;
2686   /* grab thread and mark it as NULL */
2687   thread = xvimagesink->event_thread;
2688   xvimagesink->event_thread = NULL;
2689   GST_OBJECT_UNLOCK (xvimagesink);
2690
2691   /* Wait for our event thread to finish before we clean up our stuff. */
2692   if (thread)
2693     g_thread_join (thread);
2694
2695   if (xvimagesink->cur_image) {
2696     gst_buffer_unref (xvimagesink->cur_image);
2697     xvimagesink->cur_image = NULL;
2698   }
2699
2700   g_mutex_lock (xvimagesink->flow_lock);
2701
2702   if (xvimagesink->pool) {
2703     gst_object_unref (xvimagesink->pool);
2704     xvimagesink->pool = NULL;
2705   }
2706
2707   if (xvimagesink->xwindow) {
2708     gst_xvimagesink_xwindow_clear (xvimagesink, xvimagesink->xwindow);
2709     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2710     xvimagesink->xwindow = NULL;
2711   }
2712   g_mutex_unlock (xvimagesink->flow_lock);
2713
2714   xvimagesink->render_rect.x = xvimagesink->render_rect.y =
2715       xvimagesink->render_rect.w = xvimagesink->render_rect.h = 0;
2716   xvimagesink->have_render_rect = FALSE;
2717
2718   gst_xvimagesink_xcontext_clear (xvimagesink);
2719 }
2720
2721 /* Finalize is called only once, dispose can be called multiple times.
2722  * We use mutexes and don't reset stuff to NULL here so let's register
2723  * as a finalize. */
2724 static void
2725 gst_xvimagesink_finalize (GObject * object)
2726 {
2727   GstXvImageSink *xvimagesink;
2728
2729   xvimagesink = GST_XVIMAGESINK (object);
2730
2731   gst_xvimagesink_reset (xvimagesink);
2732
2733   if (xvimagesink->display_name) {
2734     g_free (xvimagesink->display_name);
2735     xvimagesink->display_name = NULL;
2736   }
2737
2738   if (xvimagesink->par) {
2739     g_free (xvimagesink->par);
2740     xvimagesink->par = NULL;
2741   }
2742   if (xvimagesink->x_lock) {
2743     g_mutex_free (xvimagesink->x_lock);
2744     xvimagesink->x_lock = NULL;
2745   }
2746   if (xvimagesink->flow_lock) {
2747     g_mutex_free (xvimagesink->flow_lock);
2748     xvimagesink->flow_lock = NULL;
2749   }
2750
2751   g_free (xvimagesink->media_title);
2752
2753   G_OBJECT_CLASS (parent_class)->finalize (object);
2754 }
2755
2756 static void
2757 gst_xvimagesink_init (GstXvImageSink * xvimagesink)
2758 {
2759   xvimagesink->display_name = NULL;
2760   xvimagesink->adaptor_no = 0;
2761   xvimagesink->xcontext = NULL;
2762   xvimagesink->xwindow = NULL;
2763   xvimagesink->cur_image = NULL;
2764
2765   xvimagesink->hue = xvimagesink->saturation = 0;
2766   xvimagesink->contrast = xvimagesink->brightness = 0;
2767   xvimagesink->cb_changed = FALSE;
2768
2769   xvimagesink->fps_n = 0;
2770   xvimagesink->fps_d = 0;
2771   xvimagesink->video_width = 0;
2772   xvimagesink->video_height = 0;
2773
2774   xvimagesink->x_lock = g_mutex_new ();
2775   xvimagesink->flow_lock = g_mutex_new ();
2776
2777   xvimagesink->pool = NULL;
2778
2779   xvimagesink->synchronous = FALSE;
2780   xvimagesink->double_buffer = TRUE;
2781   xvimagesink->running = FALSE;
2782   xvimagesink->keep_aspect = FALSE;
2783   xvimagesink->handle_events = TRUE;
2784   xvimagesink->par = NULL;
2785   xvimagesink->handle_expose = TRUE;
2786   xvimagesink->autopaint_colorkey = TRUE;
2787
2788   /* on 16bit displays this becomes r,g,b = 1,2,3
2789    * on 24bit displays this becomes r,g,b = 8,8,16
2790    * as a port atom value
2791    */
2792   xvimagesink->colorkey = (8 << 16) | (8 << 8) | 16;
2793   xvimagesink->draw_borders = TRUE;
2794 }
2795
2796 static void
2797 gst_xvimagesink_class_init (GstXvImageSinkClass * klass)
2798 {
2799   GObjectClass *gobject_class;
2800   GstElementClass *gstelement_class;
2801   GstBaseSinkClass *gstbasesink_class;
2802   GstVideoSinkClass *videosink_class;
2803
2804   gobject_class = (GObjectClass *) klass;
2805   gstelement_class = (GstElementClass *) klass;
2806   gstbasesink_class = (GstBaseSinkClass *) klass;
2807   videosink_class = (GstVideoSinkClass *) klass;
2808
2809   parent_class = g_type_class_peek_parent (klass);
2810
2811   gobject_class->set_property = gst_xvimagesink_set_property;
2812   gobject_class->get_property = gst_xvimagesink_get_property;
2813
2814   g_object_class_install_property (gobject_class, PROP_CONTRAST,
2815       g_param_spec_int ("contrast", "Contrast", "The contrast of the video",
2816           -1000, 1000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2817   g_object_class_install_property (gobject_class, PROP_BRIGHTNESS,
2818       g_param_spec_int ("brightness", "Brightness",
2819           "The brightness of the video", -1000, 1000, 0,
2820           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2821   g_object_class_install_property (gobject_class, PROP_HUE,
2822       g_param_spec_int ("hue", "Hue", "The hue of the video", -1000, 1000, 0,
2823           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2824   g_object_class_install_property (gobject_class, PROP_SATURATION,
2825       g_param_spec_int ("saturation", "Saturation",
2826           "The saturation of the video", -1000, 1000, 0,
2827           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2828   g_object_class_install_property (gobject_class, PROP_DISPLAY,
2829       g_param_spec_string ("display", "Display", "X Display name", NULL,
2830           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2831   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
2832       g_param_spec_boolean ("synchronous", "Synchronous",
2833           "When enabled, runs the X display in synchronous mode. "
2834           "(unrelated to A/V sync, used only for debugging)", FALSE,
2835           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2836   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
2837       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
2838           "The pixel aspect ratio of the device", "1/1",
2839           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2840   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
2841       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
2842           "When enabled, scaling will respect original aspect ratio", FALSE,
2843           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2844   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
2845       g_param_spec_boolean ("handle-events", "Handle XEvents",
2846           "When enabled, XEvents will be selected and handled", TRUE,
2847           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2848   g_object_class_install_property (gobject_class, PROP_DEVICE,
2849       g_param_spec_string ("device", "Adaptor number",
2850           "The number of the video adaptor", "0",
2851           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2852   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
2853       g_param_spec_string ("device-name", "Adaptor name",
2854           "The name of the video adaptor", NULL,
2855           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2856   /**
2857    * GstXvImageSink:handle-expose
2858    *
2859    * When enabled, the current frame will always be drawn in response to X
2860    * Expose.
2861    *
2862    * Since: 0.10.14
2863    */
2864   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
2865       g_param_spec_boolean ("handle-expose", "Handle expose",
2866           "When enabled, "
2867           "the current frame will always be drawn in response to X Expose "
2868           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2869
2870   /**
2871    * GstXvImageSink:double-buffer
2872    *
2873    * Whether to double-buffer the output.
2874    *
2875    * Since: 0.10.14
2876    */
2877   g_object_class_install_property (gobject_class, PROP_DOUBLE_BUFFER,
2878       g_param_spec_boolean ("double-buffer", "Double-buffer",
2879           "Whether to double-buffer the output", TRUE,
2880           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2881   /**
2882    * GstXvImageSink:autopaint-colorkey
2883    *
2884    * Whether to autofill overlay with colorkey
2885    *
2886    * Since: 0.10.21
2887    */
2888   g_object_class_install_property (gobject_class, PROP_AUTOPAINT_COLORKEY,
2889       g_param_spec_boolean ("autopaint-colorkey", "Autofill with colorkey",
2890           "Whether to autofill overlay with colorkey", TRUE,
2891           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2892   /**
2893    * GstXvImageSink:colorkey
2894    *
2895    * Color to use for the overlay mask.
2896    *
2897    * Since: 0.10.21
2898    */
2899   g_object_class_install_property (gobject_class, PROP_COLORKEY,
2900       g_param_spec_int ("colorkey", "Colorkey",
2901           "Color to use for the overlay mask", G_MININT, G_MAXINT, 0,
2902           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2903
2904   /**
2905    * GstXvImageSink:draw-borders
2906    *
2907    * Draw black borders when using GstXvImageSink:force-aspect-ratio to fill
2908    * unused parts of the video area.
2909    *
2910    * Since: 0.10.21
2911    */
2912   g_object_class_install_property (gobject_class, PROP_DRAW_BORDERS,
2913       g_param_spec_boolean ("draw-borders", "Colorkey",
2914           "Draw black borders to fill unused area in force-aspect-ratio mode",
2915           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2916
2917   /**
2918    * GstXvImageSink:window-width
2919    *
2920    * Actual width of the video window.
2921    *
2922    * Since: 0.10.32
2923    */
2924   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
2925       g_param_spec_uint64 ("window-width", "window-width",
2926           "Width of the window", 0, G_MAXUINT64, 0,
2927           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2928
2929   /**
2930    * GstXvImageSink:window-height
2931    *
2932    * Actual height of the video window.
2933    *
2934    * Since: 0.10.32
2935    */
2936   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
2937       g_param_spec_uint64 ("window-height", "window-height",
2938           "Height of the window", 0, G_MAXUINT64, 0,
2939           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2940
2941   gobject_class->finalize = gst_xvimagesink_finalize;
2942
2943   gst_element_class_set_details_simple (gstelement_class,
2944       "Video sink", "Sink/Video",
2945       "A Xv based videosink", "Julien Moutte <julien@moutte.net>");
2946
2947   gst_element_class_add_pad_template (gstelement_class,
2948       gst_static_pad_template_get (&gst_xvimagesink_sink_template_factory));
2949
2950   gstelement_class->change_state =
2951       GST_DEBUG_FUNCPTR (gst_xvimagesink_change_state);
2952
2953   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_getcaps);
2954   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_setcaps);
2955   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_xvimagesink_get_times);
2956   gstbasesink_class->propose_allocation =
2957       GST_DEBUG_FUNCPTR (gst_xvimagesink_propose_allocation);
2958   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_xvimagesink_event);
2959
2960   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_xvimagesink_show_frame);
2961 }