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