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