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