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