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