Merge remote-tracking branch 'origin/master' into 0.11
[platform/upstream/gst-plugins-base.git] / sys / xvimage / xvimagesink.c
1 /* GStreamer
2  * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3  *               <2009>,<2010> Stefan Kost <stefan.kost@nokia.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:element-xvimagesink
23  *
24  * XvImageSink renders video frames to a drawable (XWindow) on a local display
25  * using the XVideo extension. Rendering to a remote display is theoretically
26  * possible but i doubt that the XVideo extension is actually available when
27  * connecting to a remote display. This element can receive a Window ID from the
28  * application through the XOverlay interface and will then render video frames
29  * in this drawable. If no Window ID was provided by the application, the
30  * element will create its own internal window and render into it.
31  *
32  * <refsect2>
33  * <title>Scaling</title>
34  * <para>
35  * The XVideo extension, when it's available, handles hardware accelerated
36  * scaling of video frames. This means that the element will just accept
37  * incoming video frames no matter their geometry and will then put them to the
38  * drawable scaling them on the fly. Using the #GstXvImageSink:force-aspect-ratio
39  * property it is possible to enforce scaling with a constant aspect ratio,
40  * which means drawing black borders around the video frame.
41  * </para>
42  * </refsect2>
43  * <refsect2>
44  * <title>Events</title>
45  * <para>
46  * XvImageSink creates a thread to handle events coming from the drawable. There
47  * are several kind of events that can be grouped in 2 big categories: input
48  * events and window state related events. Input events will be translated to
49  * navigation events and pushed upstream for other elements to react on them.
50  * This includes events such as pointer moves, key press/release, clicks etc...
51  * Other events are used to handle the drawable appearance even when the data
52  * is not flowing (GST_STATE_PAUSED). That means that even when the element is
53  * paused, it will receive expose events from the drawable and draw the latest
54  * frame with correct borders/aspect-ratio.
55  * </para>
56  * </refsect2>
57  * <refsect2>
58  * <title>Pixel aspect ratio</title>
59  * <para>
60  * When changing state to GST_STATE_READY, XvImageSink will open a connection to
61  * the display specified in the #GstXvImageSink:display property or the
62  * default display if nothing specified. Once this connection is open it will
63  * inspect the display configuration including the physical display geometry and
64  * then calculate the pixel aspect ratio. When receiving video frames with a
65  * different pixel aspect ratio, XvImageSink will use hardware scaling to
66  * display the video frames correctly on display's pixel aspect ratio.
67  * Sometimes the calculated pixel aspect ratio can be wrong, it is
68  * then possible to enforce a specific pixel aspect ratio using the
69  * #GstXvImageSink:pixel-aspect-ratio property.
70  * </para>
71  * </refsect2>
72  * <refsect2>
73  * <title>Examples</title>
74  * |[
75  * gst-launch -v videotestsrc ! xvimagesink
76  * ]| A pipeline to test hardware scaling.
77  * When the test video signal appears you can resize the window and see that
78  * video frames are scaled through hardware (no extra CPU cost).
79  * |[
80  * gst-launch -v videotestsrc ! xvimagesink force-aspect-ratio=true
81  * ]| Same pipeline with #GstXvImageSink:force-aspect-ratio property set to true
82  * You can observe the borders drawn around the scaled image respecting aspect
83  * ratio.
84  * |[
85  * gst-launch -v videotestsrc ! navigationtest ! xvimagesink
86  * ]| A pipeline to test navigation events.
87  * While moving the mouse pointer over the test signal you will see a black box
88  * following the mouse pointer. If you press the mouse button somewhere on the
89  * video and release it somewhere else a green box will appear where you pressed
90  * the button and a red one where you released it. (The navigationtest element
91  * is part of gst-plugins-good.) You can observe here that even if the images
92  * are scaled through hardware the pointer coordinates are converted back to the
93  * original video frame geometry so that the box can be drawn to the correct
94  * position. This also handles borders correctly, limiting coordinates to the
95  * image area
96  * |[
97  * gst-launch -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! xvimagesink
98  * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
99  * videotestsrc, in most cases the pixel aspect ratio of the display will be
100  * 1/1. This means that XvImageSink will have to do the scaling to convert
101  * incoming frames to a size that will match the display pixel aspect ratio
102  * (from 320x240 to 320x180 in this case). Note that you might have to escape
103  * some characters for your shell like '\(fraction\)'.
104  * |[
105  * gst-launch -v videotestsrc ! xvimagesink hue=100 saturation=-100 brightness=100
106  * ]| Demonstrates how to use the colorbalance interface.
107  * </refsect2>
108  */
109
110 /* for developers: there are two useful tools : xvinfo and xvattr */
111
112 #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 #if !GLIB_CHECK_VERSION (2, 31, 0)
1175       xvimagesink->event_thread = g_thread_create (
1176           (GThreadFunc) gst_xvimagesink_event_thread, xvimagesink, TRUE, NULL);
1177 #else
1178       xvimagesink->event_thread = g_thread_try_new ("xvimagesink-events",
1179           (GThreadFunc) gst_xvimagesink_event_thread, xvimagesink, NULL);
1180 #endif
1181     }
1182   } else {
1183     if (xvimagesink->event_thread) {
1184       GST_DEBUG_OBJECT (xvimagesink, "stop xevent thread, expose %d, events %d",
1185           xvimagesink->handle_expose, xvimagesink->handle_events);
1186       xvimagesink->running = FALSE;
1187       /* grab thread and mark it as NULL */
1188       thread = xvimagesink->event_thread;
1189       xvimagesink->event_thread = NULL;
1190     }
1191   }
1192   GST_OBJECT_UNLOCK (xvimagesink);
1193
1194   /* Wait for our event thread to finish */
1195   if (thread)
1196     g_thread_join (thread);
1197
1198 }
1199
1200
1201 /* This function calculates the pixel aspect ratio based on the properties
1202  * in the xcontext structure and stores it there. */
1203 static void
1204 gst_xvimagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
1205 {
1206   static const gint par[][2] = {
1207     {1, 1},                     /* regular screen */
1208     {16, 15},                   /* PAL TV */
1209     {11, 10},                   /* 525 line Rec.601 video */
1210     {54, 59},                   /* 625 line Rec.601 video */
1211     {64, 45},                   /* 1280x1024 on 16:9 display */
1212     {5, 3},                     /* 1280x1024 on 4:3 display */
1213     {4, 3}                      /*  800x600 on 16:9 display */
1214   };
1215   gint i;
1216   gint index;
1217   gdouble ratio;
1218   gdouble delta;
1219
1220 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
1221
1222   /* first calculate the "real" ratio based on the X values;
1223    * which is the "physical" w/h divided by the w/h in pixels of the display */
1224   ratio = (gdouble) (xcontext->widthmm * xcontext->height)
1225       / (xcontext->heightmm * xcontext->width);
1226
1227   /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
1228    * override here */
1229   if (xcontext->width == 720 && xcontext->height == 576) {
1230     ratio = 4.0 * 576 / (3.0 * 720);
1231   }
1232   GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
1233
1234   /* now find the one from par[][2] with the lowest delta to the real one */
1235   delta = DELTA (0);
1236   index = 0;
1237
1238   for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
1239     gdouble this_delta = DELTA (i);
1240
1241     if (this_delta < delta) {
1242       index = i;
1243       delta = this_delta;
1244     }
1245   }
1246
1247   GST_DEBUG ("Decided on index %d (%d/%d)", index,
1248       par[index][0], par[index][1]);
1249
1250   g_free (xcontext->par);
1251   xcontext->par = g_new0 (GValue, 1);
1252   g_value_init (xcontext->par, GST_TYPE_FRACTION);
1253   gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
1254   GST_DEBUG ("set xcontext PAR to %d/%d",
1255       gst_value_get_fraction_numerator (xcontext->par),
1256       gst_value_get_fraction_denominator (xcontext->par));
1257 }
1258
1259 /* This function gets the X Display and global info about it. Everything is
1260    stored in our object and will be cleaned when the object is disposed. Note
1261    here that caps for supported format are generated without any window or
1262    image creation */
1263 static GstXContext *
1264 gst_xvimagesink_xcontext_get (GstXvImageSink * xvimagesink)
1265 {
1266   GstXContext *xcontext = NULL;
1267   XPixmapFormatValues *px_formats = NULL;
1268   gint nb_formats = 0, i, j, N_attr;
1269   XvAttribute *xv_attr;
1270   Atom prop_atom;
1271   const char *channels[4] = { "XV_HUE", "XV_SATURATION",
1272     "XV_BRIGHTNESS", "XV_CONTRAST"
1273   };
1274
1275   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
1276
1277   xcontext = g_new0 (GstXContext, 1);
1278   xcontext->im_format = 0;
1279
1280   g_mutex_lock (xvimagesink->x_lock);
1281
1282   xcontext->disp = XOpenDisplay (xvimagesink->display_name);
1283
1284   if (!xcontext->disp) {
1285     g_mutex_unlock (xvimagesink->x_lock);
1286     g_free (xcontext);
1287     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1288         ("Could not initialise Xv output"), ("Could not open display"));
1289     return NULL;
1290   }
1291
1292   xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1293   xcontext->screen_num = DefaultScreen (xcontext->disp);
1294   xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1295   xcontext->root = DefaultRootWindow (xcontext->disp);
1296   xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1297   xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1298   xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1299
1300   xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1301   xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1302   xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1303   xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1304
1305   GST_DEBUG_OBJECT (xvimagesink, "X reports %dx%d pixels and %d mm x %d mm",
1306       xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1307
1308   gst_xvimagesink_calculate_pixel_aspect_ratio (xcontext);
1309   /* We get supported pixmap formats at supported depth */
1310   px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1311
1312   if (!px_formats) {
1313     XCloseDisplay (xcontext->disp);
1314     g_mutex_unlock (xvimagesink->x_lock);
1315     g_free (xcontext->par);
1316     g_free (xcontext);
1317     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS,
1318         ("Could not initialise Xv output"), ("Could not get pixel formats"));
1319     return NULL;
1320   }
1321
1322   /* We get bpp value corresponding to our running depth */
1323   for (i = 0; i < nb_formats; i++) {
1324     if (px_formats[i].depth == xcontext->depth)
1325       xcontext->bpp = px_formats[i].bits_per_pixel;
1326   }
1327
1328   XFree (px_formats);
1329
1330   xcontext->endianness =
1331       (ImageByteOrder (xcontext->disp) ==
1332       LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1333
1334   /* our caps system handles 24/32bpp RGB as big-endian. */
1335   if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
1336       xcontext->endianness == G_LITTLE_ENDIAN) {
1337     xcontext->endianness = G_BIG_ENDIAN;
1338     xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask);
1339     xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask);
1340     xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask);
1341     if (xcontext->bpp == 24) {
1342       xcontext->visual->red_mask >>= 8;
1343       xcontext->visual->green_mask >>= 8;
1344       xcontext->visual->blue_mask >>= 8;
1345     }
1346   }
1347
1348   xcontext->caps = gst_xvimagesink_get_xv_support (xvimagesink, xcontext);
1349
1350   /* Search for XShm extension support */
1351 #ifdef HAVE_XSHM
1352   if (XShmQueryExtension (xcontext->disp) &&
1353       gst_xvimagesink_check_xshm_calls (xvimagesink, xcontext)) {
1354     xcontext->use_xshm = TRUE;
1355     GST_DEBUG ("xvimagesink is using XShm extension");
1356   } else
1357 #endif /* HAVE_XSHM */
1358   {
1359     xcontext->use_xshm = FALSE;
1360     GST_DEBUG ("xvimagesink is not using XShm extension");
1361   }
1362
1363   if (!xcontext->caps) {
1364     XCloseDisplay (xcontext->disp);
1365     g_mutex_unlock (xvimagesink->x_lock);
1366     g_free (xcontext->par);
1367     g_free (xcontext);
1368     /* GST_ELEMENT_ERROR is thrown by gst_xvimagesink_get_xv_support */
1369     return NULL;
1370   }
1371
1372   xv_attr = XvQueryPortAttributes (xcontext->disp,
1373       xcontext->xv_port_id, &N_attr);
1374
1375
1376   /* Generate the channels list */
1377   for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
1378     XvAttribute *matching_attr = NULL;
1379
1380     /* Retrieve the property atom if it exists. If it doesn't exist,
1381      * the attribute itself must not either, so we can skip */
1382     prop_atom = XInternAtom (xcontext->disp, channels[i], True);
1383     if (prop_atom == None)
1384       continue;
1385
1386     if (xv_attr != NULL) {
1387       for (j = 0; j < N_attr && matching_attr == NULL; ++j)
1388         if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
1389           matching_attr = xv_attr + j;
1390     }
1391
1392     if (matching_attr) {
1393       GstColorBalanceChannel *channel;
1394
1395       channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
1396       channel->label = g_strdup (channels[i]);
1397       channel->min_value = matching_attr ? matching_attr->min_value : -1000;
1398       channel->max_value = matching_attr ? matching_attr->max_value : 1000;
1399
1400       xcontext->channels_list = g_list_append (xcontext->channels_list,
1401           channel);
1402
1403       /* If the colorbalance settings have not been touched we get Xv values
1404          as defaults and update our internal variables */
1405       if (!xvimagesink->cb_changed) {
1406         gint val;
1407
1408         XvGetPortAttribute (xcontext->disp, xcontext->xv_port_id,
1409             prop_atom, &val);
1410         /* Normalize val to [-1000, 1000] */
1411         val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
1412             (double) (channel->max_value - channel->min_value));
1413
1414         if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
1415           xvimagesink->hue = val;
1416         else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
1417           xvimagesink->saturation = val;
1418         else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
1419           xvimagesink->brightness = val;
1420         else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
1421           xvimagesink->contrast = val;
1422       }
1423     }
1424   }
1425
1426   if (xv_attr)
1427     XFree (xv_attr);
1428
1429   g_mutex_unlock (xvimagesink->x_lock);
1430
1431   return xcontext;
1432 }
1433
1434 /* This function cleans the X context. Closing the Display, releasing the XV
1435    port and unrefing the caps for supported formats. */
1436 static void
1437 gst_xvimagesink_xcontext_clear (GstXvImageSink * xvimagesink)
1438 {
1439   GList *formats_list, *channels_list;
1440   GstXContext *xcontext;
1441   gint i = 0;
1442
1443   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
1444
1445   GST_OBJECT_LOCK (xvimagesink);
1446   if (xvimagesink->xcontext == NULL) {
1447     GST_OBJECT_UNLOCK (xvimagesink);
1448     return;
1449   }
1450
1451   /* Take the XContext from the sink and clean it up */
1452   xcontext = xvimagesink->xcontext;
1453   xvimagesink->xcontext = NULL;
1454
1455   GST_OBJECT_UNLOCK (xvimagesink);
1456
1457
1458   formats_list = xcontext->formats_list;
1459
1460   while (formats_list) {
1461     GstXvImageFormat *format = formats_list->data;
1462
1463     gst_caps_unref (format->caps);
1464     g_free (format);
1465     formats_list = g_list_next (formats_list);
1466   }
1467
1468   if (xcontext->formats_list)
1469     g_list_free (xcontext->formats_list);
1470
1471   channels_list = xcontext->channels_list;
1472
1473   while (channels_list) {
1474     GstColorBalanceChannel *channel = channels_list->data;
1475
1476     g_object_unref (channel);
1477     channels_list = g_list_next (channels_list);
1478   }
1479
1480   if (xcontext->channels_list)
1481     g_list_free (xcontext->channels_list);
1482
1483   gst_caps_unref (xcontext->caps);
1484   if (xcontext->last_caps)
1485     gst_caps_replace (&xcontext->last_caps, NULL);
1486
1487   for (i = 0; i < xcontext->nb_adaptors; i++) {
1488     g_free (xcontext->adaptors[i]);
1489   }
1490
1491   g_free (xcontext->adaptors);
1492
1493   g_free (xcontext->par);
1494
1495   g_mutex_lock (xvimagesink->x_lock);
1496
1497   GST_DEBUG_OBJECT (xvimagesink, "Closing display and freeing X Context");
1498
1499   XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0);
1500
1501   XCloseDisplay (xcontext->disp);
1502
1503   g_mutex_unlock (xvimagesink->x_lock);
1504
1505   g_free (xcontext);
1506 }
1507
1508 /* Element stuff */
1509
1510 static GstCaps *
1511 gst_xvimagesink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1512 {
1513   GstXvImageSink *xvimagesink;
1514   GstCaps *caps;
1515
1516   xvimagesink = GST_XVIMAGESINK (bsink);
1517
1518   if (xvimagesink->xcontext) {
1519     if (filter)
1520       return gst_caps_intersect_full (filter, xvimagesink->xcontext->caps,
1521           GST_CAPS_INTERSECT_FIRST);
1522     else
1523       return gst_caps_ref (xvimagesink->xcontext->caps);
1524   }
1525
1526   caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (xvimagesink));
1527   if (filter) {
1528     GstCaps *intersection;
1529
1530     intersection =
1531         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1532     gst_caps_unref (caps);
1533     caps = intersection;
1534   }
1535   return caps;
1536 }
1537
1538 static gboolean
1539 gst_xvimagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1540 {
1541   GstXvImageSink *xvimagesink;
1542   GstStructure *structure;
1543   GstBufferPool *newpool, *oldpool;
1544   GstVideoInfo info;
1545   guint32 im_format = 0;
1546   gint video_par_n, video_par_d;        /* video's PAR */
1547   gint display_par_n, display_par_d;    /* display's PAR */
1548   guint num, den;
1549   gint size;
1550
1551   xvimagesink = GST_XVIMAGESINK (bsink);
1552
1553   GST_DEBUG_OBJECT (xvimagesink,
1554       "In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %"
1555       GST_PTR_FORMAT, xvimagesink->xcontext->caps, caps);
1556
1557   if (!gst_caps_can_intersect (xvimagesink->xcontext->caps, caps))
1558     goto incompatible_caps;
1559
1560   if (!gst_video_info_from_caps (&info, caps))
1561     goto invalid_format;
1562
1563   structure = gst_caps_get_structure (caps, 0);
1564
1565   xvimagesink->fps_n = info.fps_n;
1566   xvimagesink->fps_d = info.fps_d;
1567
1568   xvimagesink->video_width = info.width;
1569   xvimagesink->video_height = info.height;
1570
1571   im_format = gst_xvimagesink_get_format_from_info (xvimagesink, &info);
1572   if (im_format == -1)
1573     goto invalid_format;
1574
1575   size = info.size;
1576
1577   /* get aspect ratio from caps if it's present, and
1578    * convert video width and height to a display width and height
1579    * using wd / hd = wv / hv * PARv / PARd */
1580
1581   /* get video's PAR */
1582   video_par_n = info.par_n;
1583   video_par_d = info.par_d;
1584
1585   /* get display's PAR */
1586   if (xvimagesink->par) {
1587     display_par_n = gst_value_get_fraction_numerator (xvimagesink->par);
1588     display_par_d = gst_value_get_fraction_denominator (xvimagesink->par);
1589   } else {
1590     display_par_n = 1;
1591     display_par_d = 1;
1592   }
1593
1594   if (!gst_video_calculate_display_ratio (&num, &den, info.width,
1595           info.height, video_par_n, video_par_d, display_par_n, display_par_d))
1596     goto no_disp_ratio;
1597
1598   GST_DEBUG_OBJECT (xvimagesink,
1599       "video width/height: %dx%d, calculated display ratio: %d/%d",
1600       info.width, info.height, num, den);
1601
1602   /* now find a width x height that respects this display ratio.
1603    * prefer those that have one of w/h the same as the incoming video
1604    * using wd / hd = num / den */
1605
1606   /* start with same height, because of interlaced video */
1607   /* check hd / den is an integer scale factor, and scale wd with the PAR */
1608   if (info.height % den == 0) {
1609     GST_DEBUG_OBJECT (xvimagesink, "keeping video height");
1610     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1611         gst_util_uint64_scale_int (info.height, num, den);
1612     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1613   } else if (info.width % num == 0) {
1614     GST_DEBUG_OBJECT (xvimagesink, "keeping video width");
1615     GST_VIDEO_SINK_WIDTH (xvimagesink) = info.width;
1616     GST_VIDEO_SINK_HEIGHT (xvimagesink) = (guint)
1617         gst_util_uint64_scale_int (info.width, den, num);
1618   } else {
1619     GST_DEBUG_OBJECT (xvimagesink, "approximating while keeping video height");
1620     GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint)
1621         gst_util_uint64_scale_int (info.height, num, den);
1622     GST_VIDEO_SINK_HEIGHT (xvimagesink) = info.height;
1623   }
1624   GST_DEBUG_OBJECT (xvimagesink, "scaling to %dx%d",
1625       GST_VIDEO_SINK_WIDTH (xvimagesink), GST_VIDEO_SINK_HEIGHT (xvimagesink));
1626
1627   /* Notify application to set xwindow id now */
1628   g_mutex_lock (xvimagesink->flow_lock);
1629   if (!xvimagesink->xwindow) {
1630     g_mutex_unlock (xvimagesink->flow_lock);
1631     gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (xvimagesink));
1632   } else {
1633     g_mutex_unlock (xvimagesink->flow_lock);
1634   }
1635
1636   /* Creating our window and our image with the display size in pixels */
1637   if (GST_VIDEO_SINK_WIDTH (xvimagesink) <= 0 ||
1638       GST_VIDEO_SINK_HEIGHT (xvimagesink) <= 0)
1639     goto no_display_size;
1640
1641   g_mutex_lock (xvimagesink->flow_lock);
1642   if (!xvimagesink->xwindow) {
1643     xvimagesink->xwindow = gst_xvimagesink_xwindow_new (xvimagesink,
1644         GST_VIDEO_SINK_WIDTH (xvimagesink),
1645         GST_VIDEO_SINK_HEIGHT (xvimagesink));
1646   }
1647
1648   xvimagesink->info = info;
1649
1650   /* After a resize, we want to redraw the borders in case the new frame size
1651    * doesn't cover the same area */
1652   xvimagesink->redraw_border = TRUE;
1653
1654   /* create a new pool for the new configuration */
1655   newpool = gst_xvimage_buffer_pool_new (xvimagesink);
1656
1657   structure = gst_buffer_pool_get_config (newpool);
1658   gst_buffer_pool_config_set (structure, caps, size, 2, 0, 0, 15);
1659   if (!gst_buffer_pool_set_config (newpool, structure))
1660     goto config_failed;
1661
1662   oldpool = xvimagesink->pool;
1663   xvimagesink->pool = newpool;
1664   g_mutex_unlock (xvimagesink->flow_lock);
1665
1666   /* unref the old sink */
1667   if (oldpool) {
1668     /* we don't deactivate, some elements might still be using it, it will
1669      * be deactivated when the last ref is gone */
1670     gst_object_unref (oldpool);
1671   }
1672
1673   return TRUE;
1674
1675   /* ERRORS */
1676 incompatible_caps:
1677   {
1678     GST_ERROR_OBJECT (xvimagesink, "caps incompatible");
1679     return FALSE;
1680   }
1681 invalid_format:
1682   {
1683     GST_DEBUG_OBJECT (xvimagesink,
1684         "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
1685     return FALSE;
1686   }
1687 no_disp_ratio:
1688   {
1689     GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL),
1690         ("Error calculating the output display ratio of the video."));
1691     return FALSE;
1692   }
1693 no_display_size:
1694   {
1695     GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL),
1696         ("Error calculating the output display ratio of the video."));
1697     return FALSE;
1698   }
1699 config_failed:
1700   {
1701     GST_ERROR_OBJECT (xvimagesink, "failed to set config.");
1702     g_mutex_unlock (xvimagesink->flow_lock);
1703     return FALSE;
1704   }
1705 }
1706
1707 static GstStateChangeReturn
1708 gst_xvimagesink_change_state (GstElement * element, GstStateChange transition)
1709 {
1710   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1711   GstXvImageSink *xvimagesink;
1712   GstXContext *xcontext = NULL;
1713
1714   xvimagesink = GST_XVIMAGESINK (element);
1715
1716   switch (transition) {
1717     case GST_STATE_CHANGE_NULL_TO_READY:
1718       /* Initializing the XContext */
1719       if (xvimagesink->xcontext == NULL) {
1720         xcontext = gst_xvimagesink_xcontext_get (xvimagesink);
1721         if (xcontext == NULL) {
1722           ret = GST_STATE_CHANGE_FAILURE;
1723           goto beach;
1724         }
1725         GST_OBJECT_LOCK (xvimagesink);
1726         if (xcontext)
1727           xvimagesink->xcontext = xcontext;
1728         GST_OBJECT_UNLOCK (xvimagesink);
1729       }
1730
1731       /* update object's par with calculated one if not set yet */
1732       if (!xvimagesink->par) {
1733         xvimagesink->par = g_new0 (GValue, 1);
1734         gst_value_init_and_copy (xvimagesink->par, xvimagesink->xcontext->par);
1735         GST_DEBUG_OBJECT (xvimagesink, "set calculated PAR on object's PAR");
1736       }
1737       /* call XSynchronize with the current value of synchronous */
1738       GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s",
1739           xvimagesink->synchronous ? "TRUE" : "FALSE");
1740       XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous);
1741       gst_xvimagesink_update_colorbalance (xvimagesink);
1742       gst_xvimagesink_manage_event_thread (xvimagesink);
1743       break;
1744     case GST_STATE_CHANGE_READY_TO_PAUSED:
1745       break;
1746     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1747       break;
1748     case GST_STATE_CHANGE_PAUSED_TO_READY:
1749       break;
1750     default:
1751       break;
1752   }
1753
1754   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1755
1756   switch (transition) {
1757     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1758       break;
1759     case GST_STATE_CHANGE_PAUSED_TO_READY:
1760       xvimagesink->fps_n = 0;
1761       xvimagesink->fps_d = 1;
1762       GST_VIDEO_SINK_WIDTH (xvimagesink) = 0;
1763       GST_VIDEO_SINK_HEIGHT (xvimagesink) = 0;
1764       g_mutex_lock (xvimagesink->flow_lock);
1765       if (xvimagesink->pool)
1766         gst_buffer_pool_set_active (xvimagesink->pool, FALSE);
1767       g_mutex_unlock (xvimagesink->flow_lock);
1768       break;
1769     case GST_STATE_CHANGE_READY_TO_NULL:
1770       gst_xvimagesink_reset (xvimagesink);
1771       break;
1772     default:
1773       break;
1774   }
1775
1776 beach:
1777   return ret;
1778 }
1779
1780 static void
1781 gst_xvimagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1782     GstClockTime * start, GstClockTime * end)
1783 {
1784   GstXvImageSink *xvimagesink;
1785
1786   xvimagesink = GST_XVIMAGESINK (bsink);
1787
1788   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1789     *start = GST_BUFFER_TIMESTAMP (buf);
1790     if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1791       *end = *start + GST_BUFFER_DURATION (buf);
1792     } else {
1793       if (xvimagesink->fps_n > 0) {
1794         *end = *start +
1795             gst_util_uint64_scale_int (GST_SECOND, xvimagesink->fps_d,
1796             xvimagesink->fps_n);
1797       }
1798     }
1799   }
1800 }
1801
1802 static GstFlowReturn
1803 gst_xvimagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1804 {
1805   GstFlowReturn res;
1806   GstXvImageSink *xvimagesink;
1807   GstXvImageMeta *meta;
1808   GstBuffer *to_put;
1809
1810   xvimagesink = GST_XVIMAGESINK (vsink);
1811
1812   meta = gst_buffer_get_xvimage_meta (buf);
1813
1814   if (meta && meta->sink == xvimagesink) {
1815     /* If this buffer has been allocated using our buffer management we simply
1816        put the ximage which is in the PRIVATE pointer */
1817     GST_LOG_OBJECT (xvimagesink, "buffer %p from our pool, writing directly",
1818         buf);
1819     to_put = buf;
1820     res = GST_FLOW_OK;
1821   } else {
1822     GstVideoFrame src, dest;
1823
1824     /* Else we have to copy the data into our private image, */
1825     /* if we have one... */
1826     GST_LOG_OBJECT (xvimagesink, "buffer %p not from our pool, copying", buf);
1827
1828     /* we should have a pool, configured in setcaps */
1829     if (xvimagesink->pool == NULL)
1830       goto no_pool;
1831
1832     if (!gst_buffer_pool_set_active (xvimagesink->pool, TRUE))
1833       goto activate_failed;
1834
1835     /* take a buffer form our pool */
1836     res = gst_buffer_pool_acquire_buffer (xvimagesink->pool, &to_put, NULL);
1837     if (res != GST_FLOW_OK)
1838       goto no_buffer;
1839
1840     if (gst_buffer_get_size (to_put) < gst_buffer_get_size (buf))
1841       goto wrong_size;
1842
1843     GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, xvimagesink,
1844         "slow copy into bufferpool buffer %p", to_put);
1845
1846     if (!gst_video_frame_map (&src, &xvimagesink->info, buf, GST_MAP_READ))
1847       goto invalid_buffer;
1848
1849     if (!gst_video_frame_map (&dest, &xvimagesink->info, to_put, GST_MAP_WRITE)) {
1850       gst_video_frame_unmap (&src);
1851       goto invalid_buffer;
1852     }
1853
1854     gst_video_frame_copy (&dest, &src);
1855
1856     gst_video_frame_unmap (&dest);
1857     gst_video_frame_unmap (&src);
1858   }
1859
1860   if (!gst_xvimagesink_xvimage_put (xvimagesink, to_put))
1861     goto no_window;
1862
1863 done:
1864   if (to_put != buf)
1865     gst_buffer_unref (to_put);
1866
1867   return res;
1868
1869   /* ERRORS */
1870 no_pool:
1871   {
1872     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1873         ("Internal error: can't allocate images"),
1874         ("We don't have a bufferpool negotiated"));
1875     return GST_FLOW_ERROR;
1876   }
1877 no_buffer:
1878   {
1879     /* No image available. That's very bad ! */
1880     GST_WARNING_OBJECT (xvimagesink, "could not create image");
1881     return res;
1882   }
1883 wrong_size:
1884   {
1885     GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
1886         ("Failed to create output image buffer"),
1887         ("XServer allocated buffer size did not match input buffer %"
1888             G_GSIZE_FORMAT " - %" G_GSIZE_FORMAT, gst_buffer_get_size (to_put),
1889             gst_buffer_get_size (buf)));
1890     res = GST_FLOW_ERROR;
1891     goto done;
1892   }
1893 invalid_buffer:
1894   {
1895     /* No Window available to put our image into */
1896     GST_WARNING_OBJECT (xvimagesink, "could map image");
1897     res = GST_FLOW_OK;
1898     goto done;
1899   }
1900 no_window:
1901   {
1902     /* No Window available to put our image into */
1903     GST_WARNING_OBJECT (xvimagesink, "could not output image - no window");
1904     res = GST_FLOW_ERROR;
1905     goto done;
1906   }
1907 activate_failed:
1908   {
1909     GST_ERROR_OBJECT (xvimagesink, "failed to activate bufferpool.");
1910     res = GST_FLOW_ERROR;
1911     goto done;
1912   }
1913 }
1914
1915 static gboolean
1916 gst_xvimagesink_event (GstBaseSink * sink, GstEvent * event)
1917 {
1918   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (sink);
1919
1920   switch (GST_EVENT_TYPE (event)) {
1921     case GST_EVENT_TAG:{
1922       GstTagList *l;
1923       gchar *title = NULL;
1924
1925       gst_event_parse_tag (event, &l);
1926       gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1927
1928       if (title) {
1929         GST_DEBUG_OBJECT (xvimagesink, "got tags, title='%s'", title);
1930         gst_xvimagesink_xwindow_set_title (xvimagesink, xvimagesink->xwindow,
1931             title);
1932
1933         g_free (title);
1934       }
1935       break;
1936     }
1937     default:
1938       break;
1939   }
1940   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1941 }
1942
1943 static gboolean
1944 gst_xvimagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1945 {
1946   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (bsink);
1947   GstBufferPool *pool;
1948   GstStructure *config;
1949   GstCaps *caps;
1950   guint size;
1951   gboolean need_pool;
1952
1953   gst_query_parse_allocation (query, &caps, &need_pool);
1954
1955   if (caps == NULL)
1956     goto no_caps;
1957
1958   g_mutex_lock (xvimagesink->flow_lock);
1959   if ((pool = xvimagesink->pool))
1960     gst_object_ref (pool);
1961   g_mutex_unlock (xvimagesink->flow_lock);
1962
1963   if (pool != NULL) {
1964     const GstCaps *pcaps;
1965
1966     /* we had a pool, check caps */
1967     GST_DEBUG_OBJECT (xvimagesink, "check existing pool caps");
1968     config = gst_buffer_pool_get_config (pool);
1969     gst_buffer_pool_config_get (config, &pcaps, &size, NULL, NULL, NULL, NULL);
1970
1971     if (!gst_caps_is_equal (caps, pcaps)) {
1972       GST_DEBUG_OBJECT (xvimagesink, "pool has different caps");
1973       /* different caps, we can't use this pool */
1974       gst_object_unref (pool);
1975       pool = NULL;
1976     }
1977   }
1978   if (pool == NULL && need_pool) {
1979     GstVideoInfo info;
1980
1981     GST_DEBUG_OBJECT (xvimagesink, "create new pool");
1982     pool = gst_xvimage_buffer_pool_new (xvimagesink);
1983
1984     if (!gst_video_info_from_caps (&info, caps))
1985       goto invalid_caps;
1986
1987     /* the normal size of a frame */
1988     size = info.size;
1989
1990     config = gst_buffer_pool_get_config (pool);
1991     gst_buffer_pool_config_set (config, caps, size, 0, 0, 0, 0);
1992     if (!gst_buffer_pool_set_config (pool, config))
1993       goto config_failed;
1994   }
1995   /* we need at least 2 buffer because we hold on to the last one */
1996   gst_query_set_allocation_params (query, size, 2, 0, 0, 0, pool);
1997
1998   /* we also support various metadata */
1999   gst_query_add_allocation_meta (query, GST_VIDEO_META_API);
2000   gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API);
2001
2002   gst_object_unref (pool);
2003
2004   return TRUE;
2005
2006   /* ERRORS */
2007 no_caps:
2008   {
2009     GST_DEBUG_OBJECT (bsink, "no caps specified");
2010     return FALSE;
2011   }
2012 invalid_caps:
2013   {
2014     GST_DEBUG_OBJECT (bsink, "invalid caps specified");
2015     return FALSE;
2016   }
2017 config_failed:
2018   {
2019     GST_DEBUG_OBJECT (bsink, "failed setting config");
2020     return FALSE;
2021   }
2022 }
2023
2024 /* Interfaces stuff */
2025 static void
2026 gst_xvimagesink_navigation_send_event (GstNavigation * navigation,
2027     GstStructure * structure)
2028 {
2029   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (navigation);
2030   GstPad *peer;
2031
2032   if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (xvimagesink)))) {
2033     GstEvent *event;
2034     GstVideoRectangle src, dst, result;
2035     gdouble x, y, xscale = 1.0, yscale = 1.0;
2036
2037     event = gst_event_new_navigation (structure);
2038
2039     /* We take the flow_lock while we look at the window */
2040     g_mutex_lock (xvimagesink->flow_lock);
2041
2042     if (!xvimagesink->xwindow) {
2043       g_mutex_unlock (xvimagesink->flow_lock);
2044       return;
2045     }
2046
2047     if (xvimagesink->keep_aspect) {
2048       /* We get the frame position using the calculated geometry from _setcaps
2049          that respect pixel aspect ratios */
2050       src.w = GST_VIDEO_SINK_WIDTH (xvimagesink);
2051       src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink);
2052       dst.w = xvimagesink->render_rect.w;
2053       dst.h = xvimagesink->render_rect.h;
2054
2055       gst_video_sink_center_rect (src, dst, &result, TRUE);
2056       result.x += xvimagesink->render_rect.x;
2057       result.y += xvimagesink->render_rect.y;
2058     } else {
2059       memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
2060     }
2061
2062     g_mutex_unlock (xvimagesink->flow_lock);
2063
2064     /* We calculate scaling using the original video frames geometry to include
2065        pixel aspect ratio scaling. */
2066     xscale = (gdouble) xvimagesink->video_width / result.w;
2067     yscale = (gdouble) xvimagesink->video_height / result.h;
2068
2069     /* Converting pointer coordinates to the non scaled geometry */
2070     if (gst_structure_get_double (structure, "pointer_x", &x)) {
2071       x = MIN (x, result.x + result.w);
2072       x = MAX (x - result.x, 0);
2073       gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
2074           (gdouble) x * xscale, NULL);
2075     }
2076     if (gst_structure_get_double (structure, "pointer_y", &y)) {
2077       y = MIN (y, result.y + result.h);
2078       y = MAX (y - result.y, 0);
2079       gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
2080           (gdouble) y * yscale, NULL);
2081     }
2082
2083     gst_pad_send_event (peer, event);
2084     gst_object_unref (peer);
2085   }
2086 }
2087
2088 static void
2089 gst_xvimagesink_navigation_init (GstNavigationInterface * iface)
2090 {
2091   iface->send_event = gst_xvimagesink_navigation_send_event;
2092 }
2093
2094 static void
2095 gst_xvimagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
2096 {
2097   XID xwindow_id = id;
2098   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2099   GstXWindow *xwindow = NULL;
2100
2101   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2102
2103   g_mutex_lock (xvimagesink->flow_lock);
2104
2105   /* If we already use that window return */
2106   if (xvimagesink->xwindow && (xwindow_id == xvimagesink->xwindow->win)) {
2107     g_mutex_unlock (xvimagesink->flow_lock);
2108     return;
2109   }
2110
2111   /* If the element has not initialized the X11 context try to do so */
2112   if (!xvimagesink->xcontext &&
2113       !(xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink))) {
2114     g_mutex_unlock (xvimagesink->flow_lock);
2115     /* we have thrown a GST_ELEMENT_ERROR now */
2116     return;
2117   }
2118
2119   gst_xvimagesink_update_colorbalance (xvimagesink);
2120
2121   /* If a window is there already we destroy it */
2122   if (xvimagesink->xwindow) {
2123     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2124     xvimagesink->xwindow = NULL;
2125   }
2126
2127   /* If the xid is 0 we go back to an internal window */
2128   if (xwindow_id == 0) {
2129     /* If no width/height caps nego did not happen window will be created
2130        during caps nego then */
2131     if (GST_VIDEO_SINK_WIDTH (xvimagesink)
2132         && GST_VIDEO_SINK_HEIGHT (xvimagesink)) {
2133       xwindow =
2134           gst_xvimagesink_xwindow_new (xvimagesink,
2135           GST_VIDEO_SINK_WIDTH (xvimagesink),
2136           GST_VIDEO_SINK_HEIGHT (xvimagesink));
2137     }
2138   } else {
2139     XWindowAttributes attr;
2140
2141     xwindow = g_new0 (GstXWindow, 1);
2142     xwindow->win = xwindow_id;
2143
2144     /* Set the event we want to receive and create a GC */
2145     g_mutex_lock (xvimagesink->x_lock);
2146
2147     XGetWindowAttributes (xvimagesink->xcontext->disp, xwindow->win, &attr);
2148
2149     xwindow->width = attr.width;
2150     xwindow->height = attr.height;
2151     xwindow->internal = FALSE;
2152     if (!xvimagesink->have_render_rect) {
2153       xvimagesink->render_rect.x = xvimagesink->render_rect.y = 0;
2154       xvimagesink->render_rect.w = attr.width;
2155       xvimagesink->render_rect.h = attr.height;
2156     }
2157     if (xvimagesink->handle_events) {
2158       XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask |
2159           StructureNotifyMask | PointerMotionMask | KeyPressMask |
2160           KeyReleaseMask);
2161     }
2162
2163     xwindow->gc = XCreateGC (xvimagesink->xcontext->disp,
2164         xwindow->win, 0, NULL);
2165     g_mutex_unlock (xvimagesink->x_lock);
2166   }
2167
2168   if (xwindow)
2169     xvimagesink->xwindow = xwindow;
2170
2171   g_mutex_unlock (xvimagesink->flow_lock);
2172 }
2173
2174 static void
2175 gst_xvimagesink_expose (GstVideoOverlay * overlay)
2176 {
2177   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2178
2179   GST_DEBUG ("doing expose");
2180   gst_xvimagesink_xwindow_update_geometry (xvimagesink);
2181   gst_xvimagesink_xvimage_put (xvimagesink, NULL);
2182 }
2183
2184 static void
2185 gst_xvimagesink_set_event_handling (GstVideoOverlay * overlay,
2186     gboolean handle_events)
2187 {
2188   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2189
2190   xvimagesink->handle_events = handle_events;
2191
2192   g_mutex_lock (xvimagesink->flow_lock);
2193
2194   if (G_UNLIKELY (!xvimagesink->xwindow)) {
2195     g_mutex_unlock (xvimagesink->flow_lock);
2196     return;
2197   }
2198
2199   g_mutex_lock (xvimagesink->x_lock);
2200
2201   if (handle_events) {
2202     if (xvimagesink->xwindow->internal) {
2203       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2204           ExposureMask | StructureNotifyMask | PointerMotionMask |
2205           KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
2206     } else {
2207       XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win,
2208           ExposureMask | StructureNotifyMask | PointerMotionMask |
2209           KeyPressMask | KeyReleaseMask);
2210     }
2211   } else {
2212     XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, 0);
2213   }
2214
2215   g_mutex_unlock (xvimagesink->x_lock);
2216
2217   g_mutex_unlock (xvimagesink->flow_lock);
2218 }
2219
2220 static void
2221 gst_xvimagesink_set_render_rectangle (GstVideoOverlay * overlay, gint x, gint y,
2222     gint width, gint height)
2223 {
2224   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (overlay);
2225
2226   /* FIXME: how about some locking? */
2227   if (width >= 0 && height >= 0) {
2228     xvimagesink->render_rect.x = x;
2229     xvimagesink->render_rect.y = y;
2230     xvimagesink->render_rect.w = width;
2231     xvimagesink->render_rect.h = height;
2232     xvimagesink->have_render_rect = TRUE;
2233   } else {
2234     xvimagesink->render_rect.x = 0;
2235     xvimagesink->render_rect.y = 0;
2236     xvimagesink->render_rect.w = xvimagesink->xwindow->width;
2237     xvimagesink->render_rect.h = xvimagesink->xwindow->height;
2238     xvimagesink->have_render_rect = FALSE;
2239   }
2240 }
2241
2242 static void
2243 gst_xvimagesink_video_overlay_init (GstVideoOverlayInterface * iface)
2244 {
2245   iface->set_window_handle = gst_xvimagesink_set_window_handle;
2246   iface->expose = gst_xvimagesink_expose;
2247   iface->handle_events = gst_xvimagesink_set_event_handling;
2248   iface->set_render_rectangle = gst_xvimagesink_set_render_rectangle;
2249 }
2250
2251 static const GList *
2252 gst_xvimagesink_colorbalance_list_channels (GstColorBalance * balance)
2253 {
2254   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2255
2256   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
2257
2258   if (xvimagesink->xcontext)
2259     return xvimagesink->xcontext->channels_list;
2260   else
2261     return NULL;
2262 }
2263
2264 static void
2265 gst_xvimagesink_colorbalance_set_value (GstColorBalance * balance,
2266     GstColorBalanceChannel * channel, gint value)
2267 {
2268   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2269
2270   g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
2271   g_return_if_fail (channel->label != NULL);
2272
2273   xvimagesink->cb_changed = TRUE;
2274
2275   /* Normalize val to [-1000, 1000] */
2276   value = floor (0.5 + -1000 + 2000 * (value - channel->min_value) /
2277       (double) (channel->max_value - channel->min_value));
2278
2279   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2280     xvimagesink->hue = value;
2281   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2282     xvimagesink->saturation = value;
2283   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2284     xvimagesink->contrast = value;
2285   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2286     xvimagesink->brightness = value;
2287   } else {
2288     g_warning ("got an unknown channel %s", channel->label);
2289     return;
2290   }
2291
2292   gst_xvimagesink_update_colorbalance (xvimagesink);
2293 }
2294
2295 static gint
2296 gst_xvimagesink_colorbalance_get_value (GstColorBalance * balance,
2297     GstColorBalanceChannel * channel)
2298 {
2299   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (balance);
2300   gint value = 0;
2301
2302   g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), 0);
2303   g_return_val_if_fail (channel->label != NULL, 0);
2304
2305   if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
2306     value = xvimagesink->hue;
2307   } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
2308     value = xvimagesink->saturation;
2309   } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
2310     value = xvimagesink->contrast;
2311   } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
2312     value = xvimagesink->brightness;
2313   } else {
2314     g_warning ("got an unknown channel %s", channel->label);
2315   }
2316
2317   /* Normalize val to [channel->min_value, channel->max_value] */
2318   value = channel->min_value + (channel->max_value - channel->min_value) *
2319       (value + 1000) / 2000;
2320
2321   return value;
2322 }
2323
2324 static void
2325 gst_xvimagesink_colorbalance_init (GstColorBalanceInterface * iface)
2326 {
2327   GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE;
2328   iface->list_channels = gst_xvimagesink_colorbalance_list_channels;
2329   iface->set_value = gst_xvimagesink_colorbalance_set_value;
2330   iface->get_value = gst_xvimagesink_colorbalance_get_value;
2331 }
2332
2333 #if 0
2334 static const GList *
2335 gst_xvimagesink_probe_get_properties (GstPropertyProbe * probe)
2336 {
2337   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
2338   static GList *list = NULL;
2339
2340   if (!list) {
2341     list = g_list_append (NULL, g_object_class_find_property (klass, "device"));
2342     list =
2343         g_list_append (list, g_object_class_find_property (klass,
2344             "autopaint-colorkey"));
2345     list =
2346         g_list_append (list, g_object_class_find_property (klass,
2347             "double-buffer"));
2348     list =
2349         g_list_append (list, g_object_class_find_property (klass, "colorkey"));
2350   }
2351
2352   return list;
2353 }
2354
2355 static void
2356 gst_xvimagesink_probe_probe_property (GstPropertyProbe * probe,
2357     guint prop_id, const GParamSpec * pspec)
2358 {
2359   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2360
2361   switch (prop_id) {
2362     case PROP_DEVICE:
2363     case PROP_AUTOPAINT_COLORKEY:
2364     case PROP_DOUBLE_BUFFER:
2365     case PROP_COLORKEY:
2366       GST_DEBUG_OBJECT (xvimagesink,
2367           "probing device list and get capabilities");
2368       if (!xvimagesink->xcontext) {
2369         GST_DEBUG_OBJECT (xvimagesink, "generating xcontext");
2370         xvimagesink->xcontext = gst_xvimagesink_xcontext_get (xvimagesink);
2371       }
2372       break;
2373     default:
2374       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2375       break;
2376   }
2377 }
2378
2379 static gboolean
2380 gst_xvimagesink_probe_needs_probe (GstPropertyProbe * probe,
2381     guint prop_id, const GParamSpec * pspec)
2382 {
2383   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2384   gboolean ret = FALSE;
2385
2386   switch (prop_id) {
2387     case PROP_DEVICE:
2388     case PROP_AUTOPAINT_COLORKEY:
2389     case PROP_DOUBLE_BUFFER:
2390     case PROP_COLORKEY:
2391       if (xvimagesink->xcontext != NULL) {
2392         ret = FALSE;
2393       } else {
2394         ret = TRUE;
2395       }
2396       break;
2397     default:
2398       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2399       break;
2400   }
2401
2402   return ret;
2403 }
2404
2405 static GValueArray *
2406 gst_xvimagesink_probe_get_values (GstPropertyProbe * probe,
2407     guint prop_id, const GParamSpec * pspec)
2408 {
2409   GstXvImageSink *xvimagesink = GST_XVIMAGESINK (probe);
2410   GValueArray *array = NULL;
2411
2412   if (G_UNLIKELY (!xvimagesink->xcontext)) {
2413     GST_WARNING_OBJECT (xvimagesink, "we don't have any xcontext, can't "
2414         "get values");
2415     goto beach;
2416   }
2417
2418   switch (prop_id) {
2419     case PROP_DEVICE:
2420     {
2421       guint i;
2422       GValue value = { 0 };
2423
2424       array = g_value_array_new (xvimagesink->xcontext->nb_adaptors);
2425       g_value_init (&value, G_TYPE_STRING);
2426
2427       for (i = 0; i < xvimagesink->xcontext->nb_adaptors; i++) {
2428         gchar *adaptor_id_s = g_strdup_printf ("%u", i);
2429
2430         g_value_set_string (&value, adaptor_id_s);
2431         g_value_array_append (array, &value);
2432         g_free (adaptor_id_s);
2433       }
2434       g_value_unset (&value);
2435       break;
2436     }
2437     case PROP_AUTOPAINT_COLORKEY:
2438       if (xvimagesink->have_autopaint_colorkey) {
2439         GValue value = { 0 };
2440
2441         array = g_value_array_new (2);
2442         g_value_init (&value, G_TYPE_BOOLEAN);
2443         g_value_set_boolean (&value, FALSE);
2444         g_value_array_append (array, &value);
2445         g_value_set_boolean (&value, TRUE);
2446         g_value_array_append (array, &value);
2447         g_value_unset (&value);
2448       }
2449       break;
2450     case PROP_DOUBLE_BUFFER:
2451       if (xvimagesink->have_double_buffer) {
2452         GValue value = { 0 };
2453
2454         array = g_value_array_new (2);
2455         g_value_init (&value, G_TYPE_BOOLEAN);
2456         g_value_set_boolean (&value, FALSE);
2457         g_value_array_append (array, &value);
2458         g_value_set_boolean (&value, TRUE);
2459         g_value_array_append (array, &value);
2460         g_value_unset (&value);
2461       }
2462       break;
2463     case PROP_COLORKEY:
2464       if (xvimagesink->have_colorkey) {
2465         GValue value = { 0 };
2466
2467         array = g_value_array_new (1);
2468         g_value_init (&value, GST_TYPE_INT_RANGE);
2469         gst_value_set_int_range (&value, 0, 0xffffff);
2470         g_value_array_append (array, &value);
2471         g_value_unset (&value);
2472       }
2473       break;
2474     default:
2475       G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
2476       break;
2477   }
2478
2479 beach:
2480   return array;
2481 }
2482
2483 static void
2484 gst_xvimagesink_property_probe_interface_init (GstPropertyProbeInterface *
2485     iface)
2486 {
2487   iface->get_properties = gst_xvimagesink_probe_get_properties;
2488   iface->probe_property = gst_xvimagesink_probe_probe_property;
2489   iface->needs_probe = gst_xvimagesink_probe_needs_probe;
2490   iface->get_values = gst_xvimagesink_probe_get_values;
2491 }
2492 #endif
2493
2494 /* =========================================== */
2495 /*                                             */
2496 /*              Init & Class init              */
2497 /*                                             */
2498 /* =========================================== */
2499
2500 static void
2501 gst_xvimagesink_set_property (GObject * object, guint prop_id,
2502     const GValue * value, GParamSpec * pspec)
2503 {
2504   GstXvImageSink *xvimagesink;
2505
2506   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2507
2508   xvimagesink = GST_XVIMAGESINK (object);
2509
2510   switch (prop_id) {
2511     case PROP_HUE:
2512       xvimagesink->hue = g_value_get_int (value);
2513       xvimagesink->cb_changed = TRUE;
2514       gst_xvimagesink_update_colorbalance (xvimagesink);
2515       break;
2516     case PROP_CONTRAST:
2517       xvimagesink->contrast = g_value_get_int (value);
2518       xvimagesink->cb_changed = TRUE;
2519       gst_xvimagesink_update_colorbalance (xvimagesink);
2520       break;
2521     case PROP_BRIGHTNESS:
2522       xvimagesink->brightness = g_value_get_int (value);
2523       xvimagesink->cb_changed = TRUE;
2524       gst_xvimagesink_update_colorbalance (xvimagesink);
2525       break;
2526     case PROP_SATURATION:
2527       xvimagesink->saturation = g_value_get_int (value);
2528       xvimagesink->cb_changed = TRUE;
2529       gst_xvimagesink_update_colorbalance (xvimagesink);
2530       break;
2531     case PROP_DISPLAY:
2532       xvimagesink->display_name = g_strdup (g_value_get_string (value));
2533       break;
2534     case PROP_SYNCHRONOUS:
2535       xvimagesink->synchronous = g_value_get_boolean (value);
2536       if (xvimagesink->xcontext) {
2537         XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous);
2538         GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s",
2539             xvimagesink->synchronous ? "TRUE" : "FALSE");
2540       }
2541       break;
2542     case PROP_PIXEL_ASPECT_RATIO:
2543       g_free (xvimagesink->par);
2544       xvimagesink->par = g_new0 (GValue, 1);
2545       g_value_init (xvimagesink->par, GST_TYPE_FRACTION);
2546       if (!g_value_transform (value, xvimagesink->par)) {
2547         g_warning ("Could not transform string to aspect ratio");
2548         gst_value_set_fraction (xvimagesink->par, 1, 1);
2549       }
2550       GST_DEBUG_OBJECT (xvimagesink, "set PAR to %d/%d",
2551           gst_value_get_fraction_numerator (xvimagesink->par),
2552           gst_value_get_fraction_denominator (xvimagesink->par));
2553       break;
2554     case PROP_FORCE_ASPECT_RATIO:
2555       xvimagesink->keep_aspect = g_value_get_boolean (value);
2556       break;
2557     case PROP_HANDLE_EVENTS:
2558       gst_xvimagesink_set_event_handling (GST_VIDEO_OVERLAY (xvimagesink),
2559           g_value_get_boolean (value));
2560       gst_xvimagesink_manage_event_thread (xvimagesink);
2561       break;
2562     case PROP_DEVICE:
2563       xvimagesink->adaptor_no = atoi (g_value_get_string (value));
2564       break;
2565     case PROP_HANDLE_EXPOSE:
2566       xvimagesink->handle_expose = g_value_get_boolean (value);
2567       gst_xvimagesink_manage_event_thread (xvimagesink);
2568       break;
2569     case PROP_DOUBLE_BUFFER:
2570       xvimagesink->double_buffer = g_value_get_boolean (value);
2571       break;
2572     case PROP_AUTOPAINT_COLORKEY:
2573       xvimagesink->autopaint_colorkey = g_value_get_boolean (value);
2574       break;
2575     case PROP_COLORKEY:
2576       xvimagesink->colorkey = g_value_get_int (value);
2577       break;
2578     case PROP_DRAW_BORDERS:
2579       xvimagesink->draw_borders = g_value_get_boolean (value);
2580       break;
2581     default:
2582       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2583       break;
2584   }
2585 }
2586
2587 static void
2588 gst_xvimagesink_get_property (GObject * object, guint prop_id,
2589     GValue * value, GParamSpec * pspec)
2590 {
2591   GstXvImageSink *xvimagesink;
2592
2593   g_return_if_fail (GST_IS_XVIMAGESINK (object));
2594
2595   xvimagesink = GST_XVIMAGESINK (object);
2596
2597   switch (prop_id) {
2598     case PROP_HUE:
2599       g_value_set_int (value, xvimagesink->hue);
2600       break;
2601     case PROP_CONTRAST:
2602       g_value_set_int (value, xvimagesink->contrast);
2603       break;
2604     case PROP_BRIGHTNESS:
2605       g_value_set_int (value, xvimagesink->brightness);
2606       break;
2607     case PROP_SATURATION:
2608       g_value_set_int (value, xvimagesink->saturation);
2609       break;
2610     case PROP_DISPLAY:
2611       g_value_set_string (value, xvimagesink->display_name);
2612       break;
2613     case PROP_SYNCHRONOUS:
2614       g_value_set_boolean (value, xvimagesink->synchronous);
2615       break;
2616     case PROP_PIXEL_ASPECT_RATIO:
2617       if (xvimagesink->par)
2618         g_value_transform (xvimagesink->par, value);
2619       break;
2620     case PROP_FORCE_ASPECT_RATIO:
2621       g_value_set_boolean (value, xvimagesink->keep_aspect);
2622       break;
2623     case PROP_HANDLE_EVENTS:
2624       g_value_set_boolean (value, xvimagesink->handle_events);
2625       break;
2626     case PROP_DEVICE:
2627     {
2628       char *adaptor_no_s = g_strdup_printf ("%u", xvimagesink->adaptor_no);
2629
2630       g_value_set_string (value, adaptor_no_s);
2631       g_free (adaptor_no_s);
2632       break;
2633     }
2634     case PROP_DEVICE_NAME:
2635       if (xvimagesink->xcontext && xvimagesink->xcontext->adaptors) {
2636         g_value_set_string (value,
2637             xvimagesink->xcontext->adaptors[xvimagesink->adaptor_no]);
2638       } else {
2639         g_value_set_string (value, NULL);
2640       }
2641       break;
2642     case PROP_HANDLE_EXPOSE:
2643       g_value_set_boolean (value, xvimagesink->handle_expose);
2644       break;
2645     case PROP_DOUBLE_BUFFER:
2646       g_value_set_boolean (value, xvimagesink->double_buffer);
2647       break;
2648     case PROP_AUTOPAINT_COLORKEY:
2649       g_value_set_boolean (value, xvimagesink->autopaint_colorkey);
2650       break;
2651     case PROP_COLORKEY:
2652       g_value_set_int (value, xvimagesink->colorkey);
2653       break;
2654     case PROP_DRAW_BORDERS:
2655       g_value_set_boolean (value, xvimagesink->draw_borders);
2656       break;
2657     case PROP_WINDOW_WIDTH:
2658       if (xvimagesink->xwindow)
2659         g_value_set_uint64 (value, xvimagesink->xwindow->width);
2660       else
2661         g_value_set_uint64 (value, 0);
2662       break;
2663     case PROP_WINDOW_HEIGHT:
2664       if (xvimagesink->xwindow)
2665         g_value_set_uint64 (value, xvimagesink->xwindow->height);
2666       else
2667         g_value_set_uint64 (value, 0);
2668       break;
2669     default:
2670       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2671       break;
2672   }
2673 }
2674
2675 static void
2676 gst_xvimagesink_reset (GstXvImageSink * xvimagesink)
2677 {
2678   GThread *thread;
2679
2680   GST_OBJECT_LOCK (xvimagesink);
2681   xvimagesink->running = FALSE;
2682   /* grab thread and mark it as NULL */
2683   thread = xvimagesink->event_thread;
2684   xvimagesink->event_thread = NULL;
2685   GST_OBJECT_UNLOCK (xvimagesink);
2686
2687   /* Wait for our event thread to finish before we clean up our stuff. */
2688   if (thread)
2689     g_thread_join (thread);
2690
2691   if (xvimagesink->cur_image) {
2692     gst_buffer_unref (xvimagesink->cur_image);
2693     xvimagesink->cur_image = NULL;
2694   }
2695
2696   g_mutex_lock (xvimagesink->flow_lock);
2697
2698   if (xvimagesink->pool) {
2699     gst_object_unref (xvimagesink->pool);
2700     xvimagesink->pool = NULL;
2701   }
2702
2703   if (xvimagesink->xwindow) {
2704     gst_xvimagesink_xwindow_clear (xvimagesink, xvimagesink->xwindow);
2705     gst_xvimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow);
2706     xvimagesink->xwindow = NULL;
2707   }
2708   g_mutex_unlock (xvimagesink->flow_lock);
2709
2710   xvimagesink->render_rect.x = xvimagesink->render_rect.y =
2711       xvimagesink->render_rect.w = xvimagesink->render_rect.h = 0;
2712   xvimagesink->have_render_rect = FALSE;
2713
2714   gst_xvimagesink_xcontext_clear (xvimagesink);
2715 }
2716
2717 /* Finalize is called only once, dispose can be called multiple times.
2718  * We use mutexes and don't reset stuff to NULL here so let's register
2719  * as a finalize. */
2720 static void
2721 gst_xvimagesink_finalize (GObject * object)
2722 {
2723   GstXvImageSink *xvimagesink;
2724
2725   xvimagesink = GST_XVIMAGESINK (object);
2726
2727   gst_xvimagesink_reset (xvimagesink);
2728
2729   if (xvimagesink->display_name) {
2730     g_free (xvimagesink->display_name);
2731     xvimagesink->display_name = NULL;
2732   }
2733
2734   if (xvimagesink->par) {
2735     g_free (xvimagesink->par);
2736     xvimagesink->par = NULL;
2737   }
2738   if (xvimagesink->x_lock) {
2739     g_mutex_free (xvimagesink->x_lock);
2740     xvimagesink->x_lock = NULL;
2741   }
2742   if (xvimagesink->flow_lock) {
2743     g_mutex_free (xvimagesink->flow_lock);
2744     xvimagesink->flow_lock = NULL;
2745   }
2746
2747   g_free (xvimagesink->media_title);
2748
2749   G_OBJECT_CLASS (parent_class)->finalize (object);
2750 }
2751
2752 static void
2753 gst_xvimagesink_init (GstXvImageSink * xvimagesink)
2754 {
2755   xvimagesink->display_name = NULL;
2756   xvimagesink->adaptor_no = 0;
2757   xvimagesink->xcontext = NULL;
2758   xvimagesink->xwindow = NULL;
2759   xvimagesink->cur_image = NULL;
2760
2761   xvimagesink->hue = xvimagesink->saturation = 0;
2762   xvimagesink->contrast = xvimagesink->brightness = 0;
2763   xvimagesink->cb_changed = FALSE;
2764
2765   xvimagesink->fps_n = 0;
2766   xvimagesink->fps_d = 0;
2767   xvimagesink->video_width = 0;
2768   xvimagesink->video_height = 0;
2769
2770   xvimagesink->x_lock = g_mutex_new ();
2771   xvimagesink->flow_lock = g_mutex_new ();
2772
2773   xvimagesink->pool = NULL;
2774
2775   xvimagesink->synchronous = FALSE;
2776   xvimagesink->double_buffer = TRUE;
2777   xvimagesink->running = FALSE;
2778   xvimagesink->keep_aspect = FALSE;
2779   xvimagesink->handle_events = TRUE;
2780   xvimagesink->par = NULL;
2781   xvimagesink->handle_expose = TRUE;
2782   xvimagesink->autopaint_colorkey = TRUE;
2783
2784   /* on 16bit displays this becomes r,g,b = 1,2,3
2785    * on 24bit displays this becomes r,g,b = 8,8,16
2786    * as a port atom value
2787    */
2788   xvimagesink->colorkey = (8 << 16) | (8 << 8) | 16;
2789   xvimagesink->draw_borders = TRUE;
2790 }
2791
2792 static void
2793 gst_xvimagesink_class_init (GstXvImageSinkClass * klass)
2794 {
2795   GObjectClass *gobject_class;
2796   GstElementClass *gstelement_class;
2797   GstBaseSinkClass *gstbasesink_class;
2798   GstVideoSinkClass *videosink_class;
2799
2800   gobject_class = (GObjectClass *) klass;
2801   gstelement_class = (GstElementClass *) klass;
2802   gstbasesink_class = (GstBaseSinkClass *) klass;
2803   videosink_class = (GstVideoSinkClass *) klass;
2804
2805   parent_class = g_type_class_peek_parent (klass);
2806
2807   gobject_class->set_property = gst_xvimagesink_set_property;
2808   gobject_class->get_property = gst_xvimagesink_get_property;
2809
2810   g_object_class_install_property (gobject_class, PROP_CONTRAST,
2811       g_param_spec_int ("contrast", "Contrast", "The contrast of the video",
2812           -1000, 1000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2813   g_object_class_install_property (gobject_class, PROP_BRIGHTNESS,
2814       g_param_spec_int ("brightness", "Brightness",
2815           "The brightness of the video", -1000, 1000, 0,
2816           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2817   g_object_class_install_property (gobject_class, PROP_HUE,
2818       g_param_spec_int ("hue", "Hue", "The hue of the video", -1000, 1000, 0,
2819           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2820   g_object_class_install_property (gobject_class, PROP_SATURATION,
2821       g_param_spec_int ("saturation", "Saturation",
2822           "The saturation of the video", -1000, 1000, 0,
2823           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2824   g_object_class_install_property (gobject_class, PROP_DISPLAY,
2825       g_param_spec_string ("display", "Display", "X Display name", NULL,
2826           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2827   g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
2828       g_param_spec_boolean ("synchronous", "Synchronous",
2829           "When enabled, runs the X display in synchronous mode. "
2830           "(unrelated to A/V sync, used only for debugging)", FALSE,
2831           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2832   g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
2833       g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
2834           "The pixel aspect ratio of the device", "1/1",
2835           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2836   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
2837       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
2838           "When enabled, scaling will respect original aspect ratio", FALSE,
2839           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2840   g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
2841       g_param_spec_boolean ("handle-events", "Handle XEvents",
2842           "When enabled, XEvents will be selected and handled", TRUE,
2843           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2844   g_object_class_install_property (gobject_class, PROP_DEVICE,
2845       g_param_spec_string ("device", "Adaptor number",
2846           "The number of the video adaptor", "0",
2847           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2848   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
2849       g_param_spec_string ("device-name", "Adaptor name",
2850           "The name of the video adaptor", NULL,
2851           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2852   /**
2853    * GstXvImageSink:handle-expose
2854    *
2855    * When enabled, the current frame will always be drawn in response to X
2856    * Expose.
2857    *
2858    * Since: 0.10.14
2859    */
2860   g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
2861       g_param_spec_boolean ("handle-expose", "Handle expose",
2862           "When enabled, "
2863           "the current frame will always be drawn in response to X Expose "
2864           "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2865
2866   /**
2867    * GstXvImageSink:double-buffer
2868    *
2869    * Whether to double-buffer the output.
2870    *
2871    * Since: 0.10.14
2872    */
2873   g_object_class_install_property (gobject_class, PROP_DOUBLE_BUFFER,
2874       g_param_spec_boolean ("double-buffer", "Double-buffer",
2875           "Whether to double-buffer the output", TRUE,
2876           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2877   /**
2878    * GstXvImageSink:autopaint-colorkey
2879    *
2880    * Whether to autofill overlay with colorkey
2881    *
2882    * Since: 0.10.21
2883    */
2884   g_object_class_install_property (gobject_class, PROP_AUTOPAINT_COLORKEY,
2885       g_param_spec_boolean ("autopaint-colorkey", "Autofill with colorkey",
2886           "Whether to autofill overlay with colorkey", TRUE,
2887           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2888   /**
2889    * GstXvImageSink:colorkey
2890    *
2891    * Color to use for the overlay mask.
2892    *
2893    * Since: 0.10.21
2894    */
2895   g_object_class_install_property (gobject_class, PROP_COLORKEY,
2896       g_param_spec_int ("colorkey", "Colorkey",
2897           "Color to use for the overlay mask", G_MININT, G_MAXINT, 0,
2898           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2899
2900   /**
2901    * GstXvImageSink:draw-borders
2902    *
2903    * Draw black borders when using GstXvImageSink:force-aspect-ratio to fill
2904    * unused parts of the video area.
2905    *
2906    * Since: 0.10.21
2907    */
2908   g_object_class_install_property (gobject_class, PROP_DRAW_BORDERS,
2909       g_param_spec_boolean ("draw-borders", "Colorkey",
2910           "Draw black borders to fill unused area in force-aspect-ratio mode",
2911           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2912
2913   /**
2914    * GstXvImageSink:window-width
2915    *
2916    * Actual width of the video window.
2917    *
2918    * Since: 0.10.32
2919    */
2920   g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
2921       g_param_spec_uint64 ("window-width", "window-width",
2922           "Width of the window", 0, G_MAXUINT64, 0,
2923           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2924
2925   /**
2926    * GstXvImageSink:window-height
2927    *
2928    * Actual height of the video window.
2929    *
2930    * Since: 0.10.32
2931    */
2932   g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
2933       g_param_spec_uint64 ("window-height", "window-height",
2934           "Height of the window", 0, G_MAXUINT64, 0,
2935           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2936
2937   gobject_class->finalize = gst_xvimagesink_finalize;
2938
2939   gst_element_class_set_details_simple (gstelement_class,
2940       "Video sink", "Sink/Video",
2941       "A Xv based videosink", "Julien Moutte <julien@moutte.net>");
2942
2943   gst_element_class_add_pad_template (gstelement_class,
2944       gst_static_pad_template_get (&gst_xvimagesink_sink_template_factory));
2945
2946   gstelement_class->change_state =
2947       GST_DEBUG_FUNCPTR (gst_xvimagesink_change_state);
2948
2949   gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_getcaps);
2950   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_xvimagesink_setcaps);
2951   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_xvimagesink_get_times);
2952   gstbasesink_class->propose_allocation =
2953       GST_DEBUG_FUNCPTR (gst_xvimagesink_propose_allocation);
2954   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_xvimagesink_event);
2955
2956   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_xvimagesink_show_frame);
2957 }