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