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