2 * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * SECTION:element-ximagesink
23 * XImageSink renders video frames to a drawable (XWindow) on a local or remote
24 * display. This element can receive a Window ID from the application through
25 * the #GstVideoOverlay interface and will then render video frames in this
26 * drawable. If no Window ID was provided by the application, the element will
27 * create its own internal window and render into it.
30 * <title>Scaling</title>
32 * As standard XImage rendering to a drawable is not scaled, XImageSink will use
33 * reverse caps negotiation to try to get scaled video frames for the drawable.
34 * This is accomplished by asking the peer pad if it accepts some different caps
35 * which in most cases implies that there is a scaling element in the pipeline,
36 * or that an element generating the video frames can generate them with a
37 * different geometry. This mechanism is handled during buffer allocations, for
38 * each allocation request the video sink will check the drawable geometry, look
39 * at the #GstXImageSink:force-aspect-ratio property, calculate the geometry of
40 * desired video frames and then check that the peer pad accept those new caps.
41 * If it does it will then allocate a buffer in video memory with this new
42 * geometry and return it with the new caps.
46 * <title>Events</title>
48 * XImageSink creates a thread to handle events coming from the drawable. There
49 * are several kind of events that can be grouped in 2 big categories: input
50 * events and window state related events. Input events will be translated to
51 * navigation events and pushed upstream for other elements to react on them.
52 * This includes events such as pointer moves, key press/release, clicks etc...
53 * Other events are used to handle the drawable appearance even when the data
54 * is not flowing (GST_STATE_PAUSED). That means that even when the element is
55 * paused, it will receive expose events from the drawable and draw the latest
56 * frame with correct borders/aspect-ratio.
60 * <title>Pixel aspect ratio</title>
62 * When changing state to GST_STATE_READY, XImageSink will open a connection to
63 * the display specified in the #GstXImageSink:display property or the default
64 * display if nothing specified. Once this connection is open it will inspect
65 * the display configuration including the physical display geometry and
66 * then calculate the pixel aspect ratio. When caps negotiation will occur, the
67 * video sink will set the calculated pixel aspect ratio on the caps to make
68 * sure that incoming video frames will have the correct pixel aspect ratio for
69 * this display. Sometimes the calculated pixel aspect ratio can be wrong, it is
70 * then possible to enforce a specific pixel aspect ratio using the
71 * #GstXImageSink:pixel-aspect-ratio property.
75 * <title>Examples</title>
77 * gst-launch -v videotestsrc ! queue ! ximagesink
78 * ]| A pipeline to test reverse negotiation. When the test video signal appears
79 * you can resize the window and see that scaled buffers of the desired size are
80 * going to arrive with a short delay. This illustrates how buffers of desired
81 * size are allocated along the way. If you take away the queue, scaling will
82 * happen almost immediately.
84 * gst-launch -v videotestsrc ! navigationtest ! videoconvert ! ximagesink
85 * ]| A pipeline to test navigation events.
86 * While moving the mouse pointer over the test signal you will see a black box
87 * following the mouse pointer. If you press the mouse button somewhere on the
88 * video and release it somewhere else a green box will appear where you pressed
89 * the button and a red one where you released it. (The navigationtest element
90 * is part of gst-plugins-good.)
92 * gst-launch -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
93 * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
94 * videotestsrc, in most cases the pixel aspect ratio of the display will be
95 * 1/1. This means that videoscale will have to do the scaling to convert
96 * incoming frames to a size that will match the display pixel aspect ratio
97 * (from 320x240 to 320x180 in this case). Note that you might have to escape
98 * some characters for your shell like '\(fraction\)'.
107 #include <gst/video/navigation.h>
108 #include <gst/video/videooverlay.h>
110 #include <gst/video/gstvideometa.h>
113 #include "ximagesink.h"
115 /* Debugging category */
116 #include <gst/gstinfo.h>
118 #include "gst/glib-compat-private.h"
120 /* for XkbKeycodeToKeysym */
121 #include <X11/XKBlib.h>
123 GST_DEBUG_CATEGORY_EXTERN (gst_debug_ximagesink);
124 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
125 #define GST_CAT_DEFAULT gst_debug_ximagesink
130 unsigned long functions;
131 unsigned long decorations;
133 unsigned long status;
135 MotifWmHints, MwmHints;
137 #define MWM_HINTS_DECORATIONS (1L << 1)
139 static void gst_ximagesink_reset (GstXImageSink * ximagesink);
140 static void gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink);
141 static void gst_ximagesink_expose (GstVideoOverlay * overlay);
143 static GstStaticPadTemplate gst_ximagesink_sink_template_factory =
144 GST_STATIC_PAD_TEMPLATE ("sink",
147 GST_STATIC_CAPS ("video/x-raw, "
148 "framerate = (fraction) [ 0, MAX ], "
149 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
157 PROP_PIXEL_ASPECT_RATIO,
158 PROP_FORCE_ASPECT_RATIO,
165 /* ============================================================= */
169 /* ============================================================= */
171 /* =========================================== */
173 /* Object typing & Creation */
175 /* =========================================== */
176 static void gst_ximagesink_navigation_init (GstNavigationInterface * iface);
177 static void gst_ximagesink_video_overlay_init (GstVideoOverlayInterface *
179 #define gst_ximagesink_parent_class parent_class
180 G_DEFINE_TYPE_WITH_CODE (GstXImageSink, gst_ximagesink, GST_TYPE_VIDEO_SINK,
181 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_ximagesink_navigation_init);
182 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
183 gst_ximagesink_video_overlay_init));
185 /* ============================================================= */
187 /* Private Methods */
189 /* ============================================================= */
193 /* We are called with the x_lock taken */
195 gst_ximagesink_xwindow_draw_borders (GstXImageSink * ximagesink,
196 GstXWindow * xwindow, GstVideoRectangle rect)
198 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
199 g_return_if_fail (xwindow != NULL);
201 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
202 ximagesink->xcontext->black);
206 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
207 0, 0, rect.x, xwindow->height);
211 if ((rect.x + rect.w) < xwindow->width) {
212 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
213 rect.x + rect.w, 0, xwindow->width, xwindow->height);
218 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
219 0, 0, xwindow->width, rect.y);
223 if ((rect.y + rect.h) < xwindow->height) {
224 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
225 0, rect.y + rect.h, xwindow->width, xwindow->height);
229 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
231 gst_ximagesink_ximage_put (GstXImageSink * ximagesink, GstBuffer * ximage)
234 GstVideoCropMeta *crop;
235 GstVideoRectangle src, dst, result;
236 gboolean draw_border = FALSE;
238 /* We take the flow_lock. If expose is in there we don't want to run
239 concurrently from the data flow thread */
240 g_mutex_lock (ximagesink->flow_lock);
242 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
243 g_mutex_unlock (ximagesink->flow_lock);
247 /* Draw borders when displaying the first frame. After this
248 draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
249 if (!ximagesink->cur_image || ximagesink->draw_border) {
253 /* Store a reference to the last image we put, lose the previous one */
254 if (ximage && ximagesink->cur_image != ximage) {
255 if (ximagesink->cur_image) {
256 GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
257 gst_buffer_unref (ximagesink->cur_image);
259 GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
260 ximagesink->cur_image = gst_buffer_ref (ximage);
263 /* Expose sends a NULL image, we take the latest frame */
266 if (ximagesink->cur_image) {
267 ximage = ximagesink->cur_image;
269 g_mutex_unlock (ximagesink->flow_lock);
274 meta = gst_buffer_get_ximage_meta (ximage);
275 crop = gst_buffer_get_video_crop_meta (ximage);
278 src.x = crop->x + meta->x;
279 src.y = crop->y + meta->y;
281 src.h = crop->height;
282 GST_LOG_OBJECT (ximagesink,
283 "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
288 src.h = meta->height;
290 dst.w = ximagesink->xwindow->width;
291 dst.h = ximagesink->xwindow->height;
293 gst_video_sink_center_rect (src, dst, &result, FALSE);
295 g_mutex_lock (ximagesink->x_lock);
298 gst_ximagesink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
300 ximagesink->draw_border = FALSE;
303 if (ximagesink->xcontext->use_xshm) {
304 GST_LOG_OBJECT (ximagesink,
305 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
306 ximage, 0, 0, result.x, result.y, result.w, result.h,
307 ximagesink->xwindow->width, ximagesink->xwindow->height);
308 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
309 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
310 result.w, result.h, FALSE);
312 #endif /* HAVE_XSHM */
314 GST_LOG_OBJECT (ximagesink,
315 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
316 ximage, 0, 0, result.x, result.y, result.w, result.h,
317 ximagesink->xwindow->width, ximagesink->xwindow->height);
318 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
319 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
323 XSync (ximagesink->xcontext->disp, FALSE);
325 g_mutex_unlock (ximagesink->x_lock);
327 g_mutex_unlock (ximagesink->flow_lock);
333 gst_ximagesink_xwindow_decorate (GstXImageSink * ximagesink,
336 Atom hints_atom = None;
339 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
340 g_return_val_if_fail (window != NULL, FALSE);
342 g_mutex_lock (ximagesink->x_lock);
344 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
346 if (hints_atom == None) {
347 g_mutex_unlock (ximagesink->x_lock);
351 hints = g_malloc0 (sizeof (MotifWmHints));
353 hints->flags |= MWM_HINTS_DECORATIONS;
354 hints->decorations = 1 << 0;
356 XChangeProperty (ximagesink->xcontext->disp, window->win,
357 hints_atom, hints_atom, 32, PropModeReplace,
358 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
360 XSync (ximagesink->xcontext->disp, FALSE);
362 g_mutex_unlock (ximagesink->x_lock);
370 gst_ximagesink_xwindow_set_title (GstXImageSink * ximagesink,
371 GstXWindow * xwindow, const gchar * media_title)
374 g_free (ximagesink->media_title);
375 ximagesink->media_title = g_strdup (media_title);
378 /* we have a window */
379 if (xwindow->internal) {
380 XTextProperty xproperty;
381 const gchar *app_name;
382 const gchar *title = NULL;
383 gchar *title_mem = NULL;
385 /* set application name as a title */
386 app_name = g_get_application_name ();
388 if (app_name && ximagesink->media_title) {
389 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
391 } else if (app_name) {
393 } else if (ximagesink->media_title) {
394 title = ximagesink->media_title;
398 if ((XStringListToTextProperty (((char **) &title), 1,
400 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
401 XFree (xproperty.value);
410 /* This function handles a GstXWindow creation */
412 gst_ximagesink_xwindow_new (GstXImageSink * ximagesink, gint width, gint height)
414 GstXWindow *xwindow = NULL;
417 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
419 xwindow = g_new0 (GstXWindow, 1);
421 xwindow->width = width;
422 xwindow->height = height;
423 xwindow->internal = TRUE;
425 g_mutex_lock (ximagesink->x_lock);
427 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
428 ximagesink->xcontext->root,
429 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
431 /* We have to do that to prevent X from redrawing the background on
432 ConfigureNotify. This takes away flickering of video when resizing. */
433 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
435 /* set application name as a title */
436 gst_ximagesink_xwindow_set_title (ximagesink, xwindow, NULL);
438 if (ximagesink->handle_events) {
441 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
442 StructureNotifyMask | PointerMotionMask | KeyPressMask |
443 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
445 /* Tell the window manager we'd like delete client messages instead of
447 wm_delete = XInternAtom (ximagesink->xcontext->disp,
448 "WM_DELETE_WINDOW", False);
449 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
453 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
456 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
458 XSync (ximagesink->xcontext->disp, FALSE);
460 g_mutex_unlock (ximagesink->x_lock);
462 gst_ximagesink_xwindow_decorate (ximagesink, xwindow);
464 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
470 /* This function destroys a GstXWindow */
472 gst_ximagesink_xwindow_destroy (GstXImageSink * ximagesink,
473 GstXWindow * xwindow)
475 g_return_if_fail (xwindow != NULL);
476 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
478 g_mutex_lock (ximagesink->x_lock);
480 /* If we did not create that window we just free the GC and let it live */
481 if (xwindow->internal)
482 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
484 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
486 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
488 XSync (ximagesink->xcontext->disp, FALSE);
490 g_mutex_unlock (ximagesink->x_lock);
496 gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink)
498 XWindowAttributes attr;
499 gboolean reconfigure;
501 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
503 /* Update the window geometry */
504 g_mutex_lock (ximagesink->x_lock);
505 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
506 g_mutex_unlock (ximagesink->x_lock);
510 XGetWindowAttributes (ximagesink->xcontext->disp,
511 ximagesink->xwindow->win, &attr);
513 /* Check if we would suggest a different width/height now */
514 reconfigure = (ximagesink->xwindow->width != attr.width)
515 || (ximagesink->xwindow->height != attr.height);
516 ximagesink->xwindow->width = attr.width;
517 ximagesink->xwindow->height = attr.height;
519 g_mutex_unlock (ximagesink->x_lock);
522 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
523 gst_event_new_reconfigure ());
527 gst_ximagesink_xwindow_clear (GstXImageSink * ximagesink, GstXWindow * xwindow)
529 g_return_if_fail (xwindow != NULL);
530 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
532 g_mutex_lock (ximagesink->x_lock);
534 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
535 ximagesink->xcontext->black);
537 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
538 0, 0, xwindow->width, xwindow->height);
540 XSync (ximagesink->xcontext->disp, FALSE);
542 g_mutex_unlock (ximagesink->x_lock);
545 /* This function handles XEvents that might be in the queue. It generates
546 GstEvent that will be sent upstream in the pipeline to handle interactivity
549 gst_ximagesink_handle_xevents (GstXImageSink * ximagesink)
552 guint pointer_x = 0, pointer_y = 0;
553 gboolean pointer_moved = FALSE;
554 gboolean exposed = FALSE, configured = FALSE;
556 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
558 /* Then we get all pointer motion events, only the last position is
560 g_mutex_lock (ximagesink->flow_lock);
561 g_mutex_lock (ximagesink->x_lock);
562 while (XCheckWindowEvent (ximagesink->xcontext->disp,
563 ximagesink->xwindow->win, PointerMotionMask, &e)) {
564 g_mutex_unlock (ximagesink->x_lock);
565 g_mutex_unlock (ximagesink->flow_lock);
569 pointer_x = e.xmotion.x;
570 pointer_y = e.xmotion.y;
571 pointer_moved = TRUE;
576 g_mutex_lock (ximagesink->flow_lock);
577 g_mutex_lock (ximagesink->x_lock);
581 g_mutex_unlock (ximagesink->x_lock);
582 g_mutex_unlock (ximagesink->flow_lock);
584 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
585 pointer_x, pointer_y);
586 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
587 "mouse-move", 0, pointer_x, pointer_y);
589 g_mutex_lock (ximagesink->flow_lock);
590 g_mutex_lock (ximagesink->x_lock);
593 /* We get all remaining events on our window to throw them upstream */
594 while (XCheckWindowEvent (ximagesink->xcontext->disp,
595 ximagesink->xwindow->win,
596 KeyPressMask | KeyReleaseMask |
597 ButtonPressMask | ButtonReleaseMask, &e)) {
599 const char *key_str = NULL;
601 /* We lock only for the X function call */
602 g_mutex_unlock (ximagesink->x_lock);
603 g_mutex_unlock (ximagesink->flow_lock);
607 /* Mouse button pressed/released over our window. We send upstream
608 events for interactivity/navigation */
609 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
610 e.xbutton.button, e.xbutton.x, e.xbutton.x);
611 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
612 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
615 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
616 e.xbutton.button, e.xbutton.x, e.xbutton.x);
617 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
618 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
622 /* Key pressed/released over our window. We send upstream
623 events for interactivity/navigation */
624 g_mutex_lock (ximagesink->x_lock);
625 keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
626 e.xkey.keycode, 0, 0);
627 if (keysym != NoSymbol) {
628 key_str = XKeysymToString (keysym);
632 g_mutex_unlock (ximagesink->x_lock);
633 GST_DEBUG_OBJECT (ximagesink,
634 "key %d pressed over window at %d,%d (%s)",
635 e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
636 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
637 e.type == KeyPress ? "key-press" : "key-release", key_str);
640 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
643 g_mutex_lock (ximagesink->flow_lock);
644 g_mutex_lock (ximagesink->x_lock);
648 while (XCheckWindowEvent (ximagesink->xcontext->disp,
649 ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
654 case ConfigureNotify:
655 g_mutex_unlock (ximagesink->x_lock);
656 gst_ximagesink_xwindow_update_geometry (ximagesink);
657 g_mutex_lock (ximagesink->x_lock);
665 if (ximagesink->handle_expose && (exposed || configured)) {
666 g_mutex_unlock (ximagesink->x_lock);
667 g_mutex_unlock (ximagesink->flow_lock);
669 gst_ximagesink_expose (GST_VIDEO_OVERLAY (ximagesink));
671 g_mutex_lock (ximagesink->flow_lock);
672 g_mutex_lock (ximagesink->x_lock);
675 /* Handle Display events */
676 while (XPending (ximagesink->xcontext->disp)) {
677 XNextEvent (ximagesink->xcontext->disp, &e);
683 wm_delete = XInternAtom (ximagesink->xcontext->disp,
684 "WM_DELETE_WINDOW", False);
685 if (wm_delete == (Atom) e.xclient.data.l[0]) {
686 /* Handle window deletion by posting an error on the bus */
687 GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
688 ("Output window was closed"), (NULL));
690 g_mutex_unlock (ximagesink->x_lock);
691 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
692 ximagesink->xwindow = NULL;
693 g_mutex_lock (ximagesink->x_lock);
702 g_mutex_unlock (ximagesink->x_lock);
703 g_mutex_unlock (ximagesink->flow_lock);
707 gst_ximagesink_event_thread (GstXImageSink * ximagesink)
709 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
711 GST_OBJECT_LOCK (ximagesink);
712 while (ximagesink->running) {
713 GST_OBJECT_UNLOCK (ximagesink);
715 if (ximagesink->xwindow) {
716 gst_ximagesink_handle_xevents (ximagesink);
718 /* FIXME: do we want to align this with the framerate or anything else? */
719 g_usleep (G_USEC_PER_SEC / 20);
721 GST_OBJECT_LOCK (ximagesink);
723 GST_OBJECT_UNLOCK (ximagesink);
729 gst_ximagesink_manage_event_thread (GstXImageSink * ximagesink)
731 GThread *thread = NULL;
733 /* don't start the thread too early */
734 if (ximagesink->xcontext == NULL) {
738 GST_OBJECT_LOCK (ximagesink);
739 if (ximagesink->handle_expose || ximagesink->handle_events) {
740 if (!ximagesink->event_thread) {
741 /* Setup our event listening thread */
742 GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
743 ximagesink->handle_expose, ximagesink->handle_events);
744 ximagesink->running = TRUE;
745 ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
746 (GThreadFunc) gst_ximagesink_event_thread, ximagesink, NULL);
749 if (ximagesink->event_thread) {
750 GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
751 ximagesink->handle_expose, ximagesink->handle_events);
752 ximagesink->running = FALSE;
753 /* grab thread and mark it as NULL */
754 thread = ximagesink->event_thread;
755 ximagesink->event_thread = NULL;
758 GST_OBJECT_UNLOCK (ximagesink);
760 /* Wait for our event thread to finish */
762 g_thread_join (thread);
767 /* This function calculates the pixel aspect ratio based on the properties
768 * in the xcontext structure and stores it there. */
770 gst_ximagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
772 static const gint par[][2] = {
773 {1, 1}, /* regular screen */
774 {16, 15}, /* PAL TV */
775 {11, 10}, /* 525 line Rec.601 video */
776 {54, 59}, /* 625 line Rec.601 video */
777 {64, 45}, /* 1280x1024 on 16:9 display */
778 {5, 3}, /* 1280x1024 on 4:3 display */
779 {4, 3} /* 800x600 on 16:9 display */
786 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
788 /* first calculate the "real" ratio based on the X values;
789 * which is the "physical" w/h divided by the w/h in pixels of the display */
790 ratio = (gdouble) (xcontext->widthmm * xcontext->height)
791 / (xcontext->heightmm * xcontext->width);
793 /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
795 if (xcontext->width == 720 && xcontext->height == 576) {
796 ratio = 4.0 * 576 / (3.0 * 720);
798 GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
800 /* now find the one from par[][2] with the lowest delta to the real one */
804 for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
805 gdouble this_delta = DELTA (i);
807 if (this_delta < delta) {
813 GST_DEBUG ("Decided on index %d (%d/%d)", index,
814 par[index][0], par[index][1]);
816 g_free (xcontext->par);
817 xcontext->par = g_new0 (GValue, 1);
818 g_value_init (xcontext->par, GST_TYPE_FRACTION);
819 gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
820 GST_DEBUG ("set xcontext PAR to %d/%d",
821 gst_value_get_fraction_numerator (xcontext->par),
822 gst_value_get_fraction_denominator (xcontext->par));
825 /* This function gets the X Display and global info about it. Everything is
826 stored in our object and will be cleaned when the object is disposed. Note
827 here that caps for supported format are generated without any window or
830 gst_ximagesink_xcontext_get (GstXImageSink * ximagesink)
832 GstXContext *xcontext = NULL;
833 XPixmapFormatValues *px_formats = NULL;
834 gint nb_formats = 0, i;
836 GstVideoFormat vformat;
838 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
840 xcontext = g_new0 (GstXContext, 1);
842 g_mutex_lock (ximagesink->x_lock);
844 xcontext->disp = XOpenDisplay (ximagesink->display_name);
846 if (!xcontext->disp) {
847 g_mutex_unlock (ximagesink->x_lock);
849 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
850 ("Could not initialise X output"), ("Could not open display"));
854 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
855 xcontext->screen_num = DefaultScreen (xcontext->disp);
856 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
857 xcontext->root = DefaultRootWindow (xcontext->disp);
858 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
859 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
860 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
862 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
863 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
864 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
865 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
867 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
868 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
870 gst_ximagesink_calculate_pixel_aspect_ratio (xcontext);
872 /* We get supported pixmap formats at supported depth */
873 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
876 XCloseDisplay (xcontext->disp);
877 g_mutex_unlock (ximagesink->x_lock);
878 g_free (xcontext->par);
880 GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
881 ("Could not get supported pixmap formats"), (NULL));
885 /* We get bpp value corresponding to our running depth */
886 for (i = 0; i < nb_formats; i++) {
887 if (px_formats[i].depth == xcontext->depth)
888 xcontext->bpp = px_formats[i].bits_per_pixel;
893 endianness = (ImageByteOrder (xcontext->disp) ==
894 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
896 /* Search for XShm extension support */
898 if (XShmQueryExtension (xcontext->disp) &&
899 gst_ximagesink_check_xshm_calls (ximagesink, xcontext)) {
900 xcontext->use_xshm = TRUE;
901 GST_DEBUG ("ximagesink is using XShm extension");
903 #endif /* HAVE_XSHM */
905 xcontext->use_xshm = FALSE;
906 GST_DEBUG ("ximagesink is not using XShm extension");
909 vformat = gst_video_format_from_masks (xcontext->depth, xcontext->bpp,
910 endianness, xcontext->visual->red_mask, xcontext->visual->green_mask,
911 xcontext->visual->blue_mask, 0);
913 if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
916 /* update object's par with calculated one if not set yet */
917 if (!ximagesink->par) {
918 ximagesink->par = g_new0 (GValue, 1);
919 gst_value_init_and_copy (ximagesink->par, xcontext->par);
920 GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
922 xcontext->caps = gst_caps_new_simple ("video/x-raw",
923 "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
924 "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
925 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
926 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
927 if (ximagesink->par) {
930 nom = gst_value_get_fraction_numerator (ximagesink->par);
931 den = gst_value_get_fraction_denominator (ximagesink->par);
932 gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
933 GST_TYPE_FRACTION, nom, den, NULL);
936 g_mutex_unlock (ximagesink->x_lock);
943 GST_ERROR_OBJECT (ximagesink, "unknown format");
948 /* This function cleans the X context. Closing the Display and unrefing the
949 caps for supported formats. */
951 gst_ximagesink_xcontext_clear (GstXImageSink * ximagesink)
953 GstXContext *xcontext;
955 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
957 GST_OBJECT_LOCK (ximagesink);
958 if (ximagesink->xcontext == NULL) {
959 GST_OBJECT_UNLOCK (ximagesink);
963 /* Take the xcontext reference and NULL it while we
964 * clean it up, so that any buffer-alloced buffers
965 * arriving after this will be freed correctly */
966 xcontext = ximagesink->xcontext;
967 ximagesink->xcontext = NULL;
969 GST_OBJECT_UNLOCK (ximagesink);
971 gst_caps_unref (xcontext->caps);
972 g_free (xcontext->par);
973 g_free (ximagesink->par);
974 ximagesink->par = NULL;
976 if (xcontext->last_caps)
977 gst_caps_replace (&xcontext->last_caps, NULL);
979 g_mutex_lock (ximagesink->x_lock);
981 XCloseDisplay (xcontext->disp);
983 g_mutex_unlock (ximagesink->x_lock);
991 gst_ximagesink_getcaps (GstBaseSink * bsink, GstCaps * filter)
993 GstXImageSink *ximagesink;
997 ximagesink = GST_XIMAGESINK (bsink);
999 g_mutex_lock (ximagesink->x_lock);
1000 if (ximagesink->xcontext) {
1003 caps = gst_caps_ref (ximagesink->xcontext->caps);
1006 GstCaps *intersection;
1009 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1010 gst_caps_unref (caps);
1011 caps = intersection;
1014 if (ximagesink->xwindow && ximagesink->xwindow->width) {
1015 GstStructure *s0, *s1;
1017 caps = gst_caps_make_writable (caps);
1019 /* There can only be a single structure because the xcontext
1020 * caps only have a single structure */
1021 s0 = gst_caps_get_structure (caps, 0);
1022 s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1024 gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1025 "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1026 gst_caps_append_structure (caps, s1);
1028 /* This will not change the order but will remove the
1029 * fixed width/height caps again if not possible
1032 GstCaps *intersection;
1035 gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1036 gst_caps_unref (caps);
1037 caps = intersection;
1041 g_mutex_unlock (ximagesink->x_lock);
1044 g_mutex_unlock (ximagesink->x_lock);
1046 /* get a template copy and add the pixel aspect ratio */
1047 caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1048 if (ximagesink->par) {
1049 caps = gst_caps_make_writable (caps);
1050 for (i = 0; i < gst_caps_get_size (caps); ++i) {
1051 GstStructure *structure = gst_caps_get_structure (caps, i);
1054 nom = gst_value_get_fraction_numerator (ximagesink->par);
1055 den = gst_value_get_fraction_denominator (ximagesink->par);
1056 gst_structure_set (structure, "pixel-aspect-ratio",
1057 GST_TYPE_FRACTION, nom, den, NULL);
1062 GstCaps *intersection;
1065 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1066 gst_caps_unref (caps);
1067 caps = intersection;
1074 gst_ximagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1076 GstXImageSink *ximagesink;
1077 GstStructure *structure;
1079 GstBufferPool *newpool, *oldpool;
1082 static GstAllocationParams params = { 0, 0, 0, 15, };
1084 ximagesink = GST_XIMAGESINK (bsink);
1086 if (!ximagesink->xcontext)
1089 GST_DEBUG_OBJECT (ximagesink,
1090 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1091 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1093 /* We intersect those caps with our template to make sure they are correct */
1094 if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1095 goto incompatible_caps;
1097 if (!gst_video_info_from_caps (&info, caps))
1098 goto invalid_format;
1102 structure = gst_caps_get_structure (caps, 0);
1103 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1104 * otherwise linking should fail */
1105 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1107 if (ximagesink->par) {
1108 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1111 } else if (ximagesink->xcontext->par) {
1112 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1118 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1119 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1120 ximagesink->fps_n = info.fps_n;
1121 ximagesink->fps_d = info.fps_d;
1123 /* Notify application to set xwindow id now */
1124 g_mutex_lock (ximagesink->flow_lock);
1125 if (!ximagesink->xwindow) {
1126 g_mutex_unlock (ximagesink->flow_lock);
1127 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1129 g_mutex_unlock (ximagesink->flow_lock);
1132 /* Creating our window and our image */
1133 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1134 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1137 g_mutex_lock (ximagesink->flow_lock);
1138 if (!ximagesink->xwindow) {
1139 ximagesink->xwindow = gst_ximagesink_xwindow_new (ximagesink,
1140 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1143 ximagesink->info = info;
1145 /* Remember to draw borders for next frame */
1146 ximagesink->draw_border = TRUE;
1148 /* create a new pool for the new configuration */
1149 newpool = gst_ximage_buffer_pool_new (ximagesink);
1151 structure = gst_buffer_pool_get_config (newpool);
1152 gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
1153 gst_buffer_pool_config_set_allocator (structure, NULL, ¶ms);
1154 if (!gst_buffer_pool_set_config (newpool, structure))
1157 oldpool = ximagesink->pool;
1158 /* we don't activate the pool yet, this will be done by downstream after it
1159 * has configured the pool. If downstream does not want our pool we will
1160 * activate it when we render into it */
1161 ximagesink->pool = newpool;
1162 g_mutex_unlock (ximagesink->flow_lock);
1164 /* unref the old sink */
1166 /* we don't deactivate, some elements might still be using it, it will be
1167 * deactivated when the last ref is gone */
1168 gst_object_unref (oldpool);
1176 GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1181 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1186 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1191 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1192 ("Invalid image size."));
1197 GST_ERROR_OBJECT (ximagesink, "failed to set config.");
1198 g_mutex_unlock (ximagesink->flow_lock);
1203 static GstStateChangeReturn
1204 gst_ximagesink_change_state (GstElement * element, GstStateChange transition)
1206 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1207 GstXImageSink *ximagesink;
1208 GstXContext *xcontext = NULL;
1210 ximagesink = GST_XIMAGESINK (element);
1212 switch (transition) {
1213 case GST_STATE_CHANGE_NULL_TO_READY:
1214 /* Initializing the XContext */
1215 if (ximagesink->xcontext == NULL) {
1216 xcontext = gst_ximagesink_xcontext_get (ximagesink);
1217 if (xcontext == NULL) {
1218 ret = GST_STATE_CHANGE_FAILURE;
1221 GST_OBJECT_LOCK (ximagesink);
1223 ximagesink->xcontext = xcontext;
1224 GST_OBJECT_UNLOCK (ximagesink);
1227 /* call XSynchronize with the current value of synchronous */
1228 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1229 ximagesink->synchronous ? "TRUE" : "FALSE");
1230 g_mutex_lock (ximagesink->x_lock);
1231 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1232 g_mutex_unlock (ximagesink->x_lock);
1233 gst_ximagesink_manage_event_thread (ximagesink);
1235 case GST_STATE_CHANGE_READY_TO_PAUSED:
1236 g_mutex_lock (ximagesink->flow_lock);
1237 if (ximagesink->xwindow)
1238 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1239 g_mutex_unlock (ximagesink->flow_lock);
1241 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1247 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1249 switch (transition) {
1250 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1252 case GST_STATE_CHANGE_PAUSED_TO_READY:
1253 ximagesink->fps_n = 0;
1254 ximagesink->fps_d = 1;
1255 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1256 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1257 g_mutex_lock (ximagesink->flow_lock);
1258 if (ximagesink->pool)
1259 gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1260 g_mutex_unlock (ximagesink->flow_lock);
1262 case GST_STATE_CHANGE_READY_TO_NULL:
1263 gst_ximagesink_reset (ximagesink);
1274 gst_ximagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1275 GstClockTime * start, GstClockTime * end)
1277 GstXImageSink *ximagesink;
1279 ximagesink = GST_XIMAGESINK (bsink);
1281 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1282 *start = GST_BUFFER_TIMESTAMP (buf);
1283 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1284 *end = *start + GST_BUFFER_DURATION (buf);
1286 if (ximagesink->fps_n > 0) {
1288 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1295 static GstFlowReturn
1296 gst_ximagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1299 GstXImageSink *ximagesink;
1300 GstXImageMeta *meta;
1301 GstBuffer *to_put = NULL;
1303 ximagesink = GST_XIMAGESINK (vsink);
1305 meta = gst_buffer_get_ximage_meta (buf);
1307 if (meta && meta->sink == ximagesink) {
1308 /* If this buffer has been allocated using our buffer management we simply
1309 put the ximage which is in the PRIVATE pointer */
1310 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1314 GstVideoFrame src, dest;
1316 /* Else we have to copy the data into our private image, */
1317 /* if we have one... */
1318 GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1320 /* we should have a pool, configured in setcaps */
1321 if (ximagesink->pool == NULL)
1324 if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1325 goto activate_failed;
1327 /* take a buffer from our pool */
1328 res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, NULL);
1329 if (res != GST_FLOW_OK)
1332 GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, ximagesink,
1333 "slow copy into bufferpool buffer %p", to_put);
1335 if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1336 goto invalid_buffer;
1338 if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1339 gst_video_frame_unmap (&src);
1340 goto invalid_buffer;
1343 gst_video_frame_copy (&dest, &src);
1345 gst_video_frame_unmap (&dest);
1346 gst_video_frame_unmap (&src);
1349 if (!gst_ximagesink_ximage_put (ximagesink, to_put))
1354 gst_buffer_unref (to_put);
1361 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1362 ("Internal error: can't allocate images"),
1363 ("We don't have a bufferpool negotiated"));
1364 return GST_FLOW_ERROR;
1368 /* No image available. That's very bad ! */
1369 GST_WARNING_OBJECT (ximagesink, "could not create image");
1374 /* No Window available to put our image into */
1375 GST_WARNING_OBJECT (ximagesink, "could map image");
1381 /* No Window available to put our image into */
1382 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1383 res = GST_FLOW_ERROR;
1388 GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1389 res = GST_FLOW_ERROR;
1395 gst_ximagesink_event (GstBaseSink * sink, GstEvent * event)
1397 GstXImageSink *ximagesink = GST_XIMAGESINK (sink);
1399 switch (GST_EVENT_TYPE (event)) {
1400 case GST_EVENT_TAG:{
1402 gchar *title = NULL;
1404 gst_event_parse_tag (event, &l);
1405 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1408 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1409 gst_ximagesink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1419 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1423 gst_ximagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1425 GstXImageSink *ximagesink = GST_XIMAGESINK (bsink);
1426 GstBufferPool *pool;
1427 GstStructure *config;
1432 gst_query_parse_allocation (query, &caps, &need_pool);
1437 g_mutex_lock (ximagesink->flow_lock);
1438 if ((pool = ximagesink->pool))
1439 gst_object_ref (pool);
1440 g_mutex_unlock (ximagesink->flow_lock);
1445 /* we had a pool, check caps */
1446 config = gst_buffer_pool_get_config (pool);
1447 gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
1449 GST_DEBUG_OBJECT (ximagesink,
1450 "we had a pool with caps %" GST_PTR_FORMAT, pcaps);
1451 if (!gst_caps_is_equal (caps, pcaps)) {
1452 /* different caps, we can't use this pool */
1453 GST_DEBUG_OBJECT (ximagesink, "pool has different caps");
1454 gst_object_unref (pool);
1457 gst_structure_free (config);
1459 if (pool == NULL && need_pool) {
1462 GST_DEBUG_OBJECT (ximagesink, "create new pool");
1463 pool = gst_ximage_buffer_pool_new (ximagesink);
1465 if (!gst_video_info_from_caps (&info, caps))
1468 /* the normal size of a frame */
1471 config = gst_buffer_pool_get_config (pool);
1472 gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
1473 if (!gst_buffer_pool_set_config (pool, config))
1477 /* we need at least 2 buffer because we hold on to the last one */
1478 gst_query_add_allocation_pool (query, pool, size, 2, 0);
1479 gst_object_unref (pool);
1482 /* we also support various metadata */
1483 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE);
1484 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE);
1491 GST_DEBUG_OBJECT (bsink, "no caps specified");
1496 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1501 GST_DEBUG_OBJECT (bsink, "failed setting config");
1506 /* Interfaces stuff */
1508 gst_ximagesink_navigation_send_event (GstNavigation * navigation,
1509 GstStructure * structure)
1511 GstXImageSink *ximagesink = GST_XIMAGESINK (navigation);
1513 gint x_offset, y_offset;
1517 event = gst_event_new_navigation (structure);
1519 /* We are not converting the pointer coordinates as there's no hardware
1520 scaling done here. The only possible scaling is done by videoscale and
1521 videoscale will have to catch those events and tranform the coordinates
1522 to match the applied scaling. So here we just add the offset if the image
1523 is centered in the window. */
1525 /* We take the flow_lock while we look at the window */
1526 g_mutex_lock (ximagesink->flow_lock);
1528 if (!ximagesink->xwindow) {
1529 g_mutex_unlock (ximagesink->flow_lock);
1533 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1534 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1536 g_mutex_unlock (ximagesink->flow_lock);
1538 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1540 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1542 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1544 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1547 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
1549 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
1550 gst_pad_send_event (pad, event);
1552 gst_object_unref (pad);
1557 gst_ximagesink_navigation_init (GstNavigationInterface * iface)
1559 iface->send_event = gst_ximagesink_navigation_send_event;
1563 gst_ximagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1565 XID xwindow_id = id;
1566 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1567 GstXWindow *xwindow = NULL;
1568 XWindowAttributes attr;
1570 /* We acquire the stream lock while setting this window in the element.
1571 We are basically cleaning tons of stuff replacing the old window, putting
1572 images while we do that would surely crash */
1573 g_mutex_lock (ximagesink->flow_lock);
1575 /* If we already use that window return */
1576 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1577 g_mutex_unlock (ximagesink->flow_lock);
1581 /* If the element has not initialized the X11 context try to do so */
1582 if (!ximagesink->xcontext &&
1583 !(ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink))) {
1584 g_mutex_unlock (ximagesink->flow_lock);
1585 /* we have thrown a GST_ELEMENT_ERROR now */
1589 /* If a window is there already we destroy it */
1590 if (ximagesink->xwindow) {
1591 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1592 ximagesink->xwindow = NULL;
1595 /* If the xid is 0 we go back to an internal window */
1596 if (xwindow_id == 0) {
1597 /* If no width/height caps nego did not happen window will be created
1598 during caps nego then */
1599 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1600 xwindow = gst_ximagesink_xwindow_new (ximagesink,
1601 GST_VIDEO_SINK_WIDTH (ximagesink),
1602 GST_VIDEO_SINK_HEIGHT (ximagesink));
1605 xwindow = g_new0 (GstXWindow, 1);
1607 xwindow->win = xwindow_id;
1609 /* We get window geometry, set the event we want to receive,
1611 g_mutex_lock (ximagesink->x_lock);
1612 XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
1613 xwindow->width = attr.width;
1614 xwindow->height = attr.height;
1615 xwindow->internal = FALSE;
1616 if (ximagesink->handle_events) {
1617 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1618 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1622 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1623 g_mutex_unlock (ximagesink->x_lock);
1627 ximagesink->xwindow = xwindow;
1629 g_mutex_unlock (ximagesink->flow_lock);
1633 gst_ximagesink_expose (GstVideoOverlay * overlay)
1635 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1637 gst_ximagesink_xwindow_update_geometry (ximagesink);
1638 gst_ximagesink_ximage_put (ximagesink, NULL);
1642 gst_ximagesink_set_event_handling (GstVideoOverlay * overlay,
1643 gboolean handle_events)
1645 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1647 ximagesink->handle_events = handle_events;
1649 g_mutex_lock (ximagesink->flow_lock);
1651 if (G_UNLIKELY (!ximagesink->xwindow)) {
1652 g_mutex_unlock (ximagesink->flow_lock);
1656 g_mutex_lock (ximagesink->x_lock);
1658 if (handle_events) {
1659 if (ximagesink->xwindow->internal) {
1660 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1661 ExposureMask | StructureNotifyMask | PointerMotionMask |
1662 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1664 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1665 ExposureMask | StructureNotifyMask | PointerMotionMask |
1666 KeyPressMask | KeyReleaseMask);
1669 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1672 g_mutex_unlock (ximagesink->x_lock);
1674 g_mutex_unlock (ximagesink->flow_lock);
1678 gst_ximagesink_video_overlay_init (GstVideoOverlayInterface * iface)
1680 iface->set_window_handle = gst_ximagesink_set_window_handle;
1681 iface->expose = gst_ximagesink_expose;
1682 iface->handle_events = gst_ximagesink_set_event_handling;
1685 /* =========================================== */
1687 /* Init & Class init */
1689 /* =========================================== */
1692 gst_ximagesink_set_property (GObject * object, guint prop_id,
1693 const GValue * value, GParamSpec * pspec)
1695 GstXImageSink *ximagesink;
1697 g_return_if_fail (GST_IS_XIMAGESINK (object));
1699 ximagesink = GST_XIMAGESINK (object);
1703 ximagesink->display_name = g_strdup (g_value_get_string (value));
1705 case PROP_SYNCHRONOUS:
1706 ximagesink->synchronous = g_value_get_boolean (value);
1707 if (ximagesink->xcontext) {
1708 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1709 ximagesink->synchronous ? "TRUE" : "FALSE");
1710 g_mutex_lock (ximagesink->x_lock);
1711 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1712 g_mutex_unlock (ximagesink->x_lock);
1715 case PROP_FORCE_ASPECT_RATIO:
1716 ximagesink->keep_aspect = g_value_get_boolean (value);
1718 case PROP_PIXEL_ASPECT_RATIO:
1722 tmp = g_new0 (GValue, 1);
1723 g_value_init (tmp, GST_TYPE_FRACTION);
1725 if (!g_value_transform (value, tmp)) {
1726 GST_WARNING_OBJECT (ximagesink,
1727 "Could not transform string to aspect ratio");
1730 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1731 gst_value_get_fraction_numerator (tmp),
1732 gst_value_get_fraction_denominator (tmp));
1733 g_free (ximagesink->par);
1734 ximagesink->par = tmp;
1738 case PROP_HANDLE_EVENTS:
1739 gst_ximagesink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1740 g_value_get_boolean (value));
1741 gst_ximagesink_manage_event_thread (ximagesink);
1743 case PROP_HANDLE_EXPOSE:
1744 ximagesink->handle_expose = g_value_get_boolean (value);
1745 gst_ximagesink_manage_event_thread (ximagesink);
1748 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1754 gst_ximagesink_get_property (GObject * object, guint prop_id,
1755 GValue * value, GParamSpec * pspec)
1757 GstXImageSink *ximagesink;
1759 g_return_if_fail (GST_IS_XIMAGESINK (object));
1761 ximagesink = GST_XIMAGESINK (object);
1765 g_value_set_string (value, ximagesink->display_name);
1767 case PROP_SYNCHRONOUS:
1768 g_value_set_boolean (value, ximagesink->synchronous);
1770 case PROP_FORCE_ASPECT_RATIO:
1771 g_value_set_boolean (value, ximagesink->keep_aspect);
1773 case PROP_PIXEL_ASPECT_RATIO:
1774 if (ximagesink->par)
1775 g_value_transform (ximagesink->par, value);
1777 case PROP_HANDLE_EVENTS:
1778 g_value_set_boolean (value, ximagesink->handle_events);
1780 case PROP_HANDLE_EXPOSE:
1781 g_value_set_boolean (value, ximagesink->handle_expose);
1783 case PROP_WINDOW_WIDTH:
1784 if (ximagesink->xwindow)
1785 g_value_set_uint64 (value, ximagesink->xwindow->width);
1787 g_value_set_uint64 (value, 0);
1789 case PROP_WINDOW_HEIGHT:
1790 if (ximagesink->xwindow)
1791 g_value_set_uint64 (value, ximagesink->xwindow->height);
1793 g_value_set_uint64 (value, 0);
1796 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1802 gst_ximagesink_reset (GstXImageSink * ximagesink)
1806 GST_OBJECT_LOCK (ximagesink);
1807 ximagesink->running = FALSE;
1808 /* grab thread and mark it as NULL */
1809 thread = ximagesink->event_thread;
1810 ximagesink->event_thread = NULL;
1811 GST_OBJECT_UNLOCK (ximagesink);
1813 /* Wait for our event thread to finish before we clean up our stuff. */
1815 g_thread_join (thread);
1817 if (ximagesink->cur_image) {
1818 gst_buffer_unref (ximagesink->cur_image);
1819 ximagesink->cur_image = NULL;
1822 g_mutex_lock (ximagesink->flow_lock);
1824 if (ximagesink->pool) {
1825 gst_object_unref (ximagesink->pool);
1826 ximagesink->pool = NULL;
1829 if (ximagesink->xwindow) {
1830 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1831 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1832 ximagesink->xwindow = NULL;
1834 g_mutex_unlock (ximagesink->flow_lock);
1836 gst_ximagesink_xcontext_clear (ximagesink);
1840 gst_ximagesink_finalize (GObject * object)
1842 GstXImageSink *ximagesink;
1844 ximagesink = GST_XIMAGESINK (object);
1846 gst_ximagesink_reset (ximagesink);
1848 if (ximagesink->display_name) {
1849 g_free (ximagesink->display_name);
1850 ximagesink->display_name = NULL;
1852 if (ximagesink->par) {
1853 g_free (ximagesink->par);
1854 ximagesink->par = NULL;
1856 if (ximagesink->x_lock) {
1857 g_mutex_free (ximagesink->x_lock);
1858 ximagesink->x_lock = NULL;
1860 if (ximagesink->flow_lock) {
1861 g_mutex_free (ximagesink->flow_lock);
1862 ximagesink->flow_lock = NULL;
1865 g_free (ximagesink->media_title);
1867 G_OBJECT_CLASS (parent_class)->finalize (object);
1871 gst_ximagesink_init (GstXImageSink * ximagesink)
1873 ximagesink->display_name = NULL;
1874 ximagesink->xcontext = NULL;
1875 ximagesink->xwindow = NULL;
1876 ximagesink->cur_image = NULL;
1878 ximagesink->event_thread = NULL;
1879 ximagesink->running = FALSE;
1881 ximagesink->fps_n = 0;
1882 ximagesink->fps_d = 1;
1884 ximagesink->x_lock = g_mutex_new ();
1885 ximagesink->flow_lock = g_mutex_new ();
1887 ximagesink->par = NULL;
1889 ximagesink->pool = NULL;
1891 ximagesink->synchronous = FALSE;
1892 ximagesink->keep_aspect = FALSE;
1893 ximagesink->handle_events = TRUE;
1894 ximagesink->handle_expose = TRUE;
1898 gst_ximagesink_class_init (GstXImageSinkClass * klass)
1900 GObjectClass *gobject_class;
1901 GstElementClass *gstelement_class;
1902 GstBaseSinkClass *gstbasesink_class;
1903 GstVideoSinkClass *videosink_class;
1905 gobject_class = (GObjectClass *) klass;
1906 gstelement_class = (GstElementClass *) klass;
1907 gstbasesink_class = (GstBaseSinkClass *) klass;
1908 videosink_class = (GstVideoSinkClass *) klass;
1910 gobject_class->finalize = gst_ximagesink_finalize;
1911 gobject_class->set_property = gst_ximagesink_set_property;
1912 gobject_class->get_property = gst_ximagesink_get_property;
1914 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1915 g_param_spec_string ("display", "Display", "X Display name",
1916 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1917 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1918 g_param_spec_boolean ("synchronous", "Synchronous",
1919 "When enabled, runs the X display in synchronous mode. "
1920 "(unrelated to A/V sync, used only for debugging)", FALSE,
1921 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1922 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1923 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1924 "When enabled, reverse caps negotiation (scaling) will respect "
1925 "original aspect ratio", FALSE,
1926 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1927 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1928 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1929 "The pixel aspect ratio of the device", "1/1",
1930 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1931 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1932 g_param_spec_boolean ("handle-events", "Handle XEvents",
1933 "When enabled, XEvents will be selected and handled", TRUE,
1934 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1935 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1936 g_param_spec_boolean ("handle-expose", "Handle expose",
1938 "the current frame will always be drawn in response to X Expose "
1939 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1942 * GstXImageSink:window-width
1944 * Actual width of the video window.
1948 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1949 g_param_spec_uint64 ("window-width", "window-width",
1950 "Width of the window", 0, G_MAXUINT64, 0,
1951 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1954 * GstXImageSink:window-height
1956 * Actual height of the video window.
1960 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1961 g_param_spec_uint64 ("window-height", "window-height",
1962 "Height of the window", 0, G_MAXUINT64, 0,
1963 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1965 gst_element_class_set_static_metadata (gstelement_class,
1966 "Video sink", "Sink/Video",
1967 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1969 gst_element_class_add_pad_template (gstelement_class,
1970 gst_static_pad_template_get (&gst_ximagesink_sink_template_factory));
1972 gstelement_class->change_state = gst_ximagesink_change_state;
1974 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_getcaps);
1975 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_setcaps);
1976 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_ximagesink_get_times);
1977 gstbasesink_class->propose_allocation =
1978 GST_DEBUG_FUNCPTR (gst_ximagesink_propose_allocation);
1979 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_ximagesink_event);
1981 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);