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