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/interfaces/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 GST_DEBUG_CATEGORY_EXTERN (gst_debug_ximagesink);
121 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
122 #define GST_CAT_DEFAULT gst_debug_ximagesink
127 unsigned long functions;
128 unsigned long decorations;
130 unsigned long status;
132 MotifWmHints, MwmHints;
134 #define MWM_HINTS_DECORATIONS (1L << 1)
136 static void gst_ximagesink_reset (GstXImageSink * ximagesink);
137 static void gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink);
138 static void gst_ximagesink_expose (GstVideoOverlay * overlay);
140 static GstStaticPadTemplate gst_ximagesink_sink_template_factory =
141 GST_STATIC_PAD_TEMPLATE ("sink",
144 GST_STATIC_CAPS ("video/x-raw, "
145 "framerate = (fraction) [ 0, MAX ], "
146 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
154 PROP_PIXEL_ASPECT_RATIO,
155 PROP_FORCE_ASPECT_RATIO,
162 /* ============================================================= */
166 /* ============================================================= */
168 /* =========================================== */
170 /* Object typing & Creation */
172 /* =========================================== */
173 static void gst_ximagesink_navigation_init (GstNavigationInterface * iface);
174 static void gst_ximagesink_video_overlay_init (GstVideoOverlayInterface *
176 #define gst_ximagesink_parent_class parent_class
177 G_DEFINE_TYPE_WITH_CODE (GstXImageSink, gst_ximagesink, GST_TYPE_VIDEO_SINK,
178 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_ximagesink_navigation_init);
179 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
180 gst_ximagesink_video_overlay_init));
182 /* ============================================================= */
184 /* Private Methods */
186 /* ============================================================= */
190 /* We are called with the x_lock taken */
192 gst_ximagesink_xwindow_draw_borders (GstXImageSink * ximagesink,
193 GstXWindow * xwindow, GstVideoRectangle rect)
195 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
196 g_return_if_fail (xwindow != NULL);
198 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
199 ximagesink->xcontext->black);
203 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
204 0, 0, rect.x, xwindow->height);
208 if ((rect.x + rect.w) < xwindow->width) {
209 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
210 rect.x + rect.w, 0, xwindow->width, xwindow->height);
215 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
216 0, 0, xwindow->width, rect.y);
220 if ((rect.y + rect.h) < xwindow->height) {
221 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
222 0, rect.y + rect.h, xwindow->width, xwindow->height);
226 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
228 gst_ximagesink_ximage_put (GstXImageSink * ximagesink, GstBuffer * ximage)
231 GstVideoCropMeta *crop;
232 GstVideoRectangle src, dst, result;
233 gboolean draw_border = FALSE;
235 /* We take the flow_lock. If expose is in there we don't want to run
236 concurrently from the data flow thread */
237 g_mutex_lock (ximagesink->flow_lock);
239 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
240 g_mutex_unlock (ximagesink->flow_lock);
244 /* Draw borders when displaying the first frame. After this
245 draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
246 if (!ximagesink->cur_image || ximagesink->draw_border) {
250 /* Store a reference to the last image we put, lose the previous one */
251 if (ximage && ximagesink->cur_image != ximage) {
252 if (ximagesink->cur_image) {
253 GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
254 gst_buffer_unref (ximagesink->cur_image);
256 GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
257 ximagesink->cur_image = gst_buffer_ref (ximage);
260 /* Expose sends a NULL image, we take the latest frame */
263 if (ximagesink->cur_image) {
264 ximage = ximagesink->cur_image;
266 g_mutex_unlock (ximagesink->flow_lock);
271 meta = gst_buffer_get_ximage_meta (ximage);
272 crop = gst_buffer_get_video_crop_meta (ximage);
275 src.x = crop->x + meta->x;
276 src.y = crop->y + meta->y;
278 src.h = crop->height;
279 GST_LOG_OBJECT (ximagesink,
280 "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
285 src.h = meta->height;
287 dst.w = ximagesink->xwindow->width;
288 dst.h = ximagesink->xwindow->height;
290 gst_video_sink_center_rect (src, dst, &result, FALSE);
292 g_mutex_lock (ximagesink->x_lock);
295 gst_ximagesink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
297 ximagesink->draw_border = FALSE;
300 if (ximagesink->xcontext->use_xshm) {
301 GST_LOG_OBJECT (ximagesink,
302 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
303 ximage, 0, 0, result.x, result.y, result.w, result.h,
304 ximagesink->xwindow->width, ximagesink->xwindow->height);
305 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
306 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
307 result.w, result.h, FALSE);
309 #endif /* HAVE_XSHM */
311 GST_LOG_OBJECT (ximagesink,
312 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
313 ximage, 0, 0, result.x, result.y, result.w, result.h,
314 ximagesink->xwindow->width, ximagesink->xwindow->height);
315 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
316 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
320 XSync (ximagesink->xcontext->disp, FALSE);
322 g_mutex_unlock (ximagesink->x_lock);
324 g_mutex_unlock (ximagesink->flow_lock);
330 gst_ximagesink_xwindow_decorate (GstXImageSink * ximagesink,
333 Atom hints_atom = None;
336 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
337 g_return_val_if_fail (window != NULL, FALSE);
339 g_mutex_lock (ximagesink->x_lock);
341 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
343 if (hints_atom == None) {
344 g_mutex_unlock (ximagesink->x_lock);
348 hints = g_malloc0 (sizeof (MotifWmHints));
350 hints->flags |= MWM_HINTS_DECORATIONS;
351 hints->decorations = 1 << 0;
353 XChangeProperty (ximagesink->xcontext->disp, window->win,
354 hints_atom, hints_atom, 32, PropModeReplace,
355 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
357 XSync (ximagesink->xcontext->disp, FALSE);
359 g_mutex_unlock (ximagesink->x_lock);
367 gst_ximagesink_xwindow_set_title (GstXImageSink * ximagesink,
368 GstXWindow * xwindow, const gchar * media_title)
371 g_free (ximagesink->media_title);
372 ximagesink->media_title = g_strdup (media_title);
375 /* we have a window */
376 if (xwindow->internal) {
377 XTextProperty xproperty;
378 const gchar *app_name;
379 const gchar *title = NULL;
380 gchar *title_mem = NULL;
382 /* set application name as a title */
383 app_name = g_get_application_name ();
385 if (app_name && ximagesink->media_title) {
386 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
388 } else if (app_name) {
390 } else if (ximagesink->media_title) {
391 title = ximagesink->media_title;
395 if ((XStringListToTextProperty (((char **) &title), 1,
397 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
398 XFree (xproperty.value);
407 /* This function handles a GstXWindow creation */
409 gst_ximagesink_xwindow_new (GstXImageSink * ximagesink, gint width, gint height)
411 GstXWindow *xwindow = NULL;
414 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
416 xwindow = g_new0 (GstXWindow, 1);
418 xwindow->width = width;
419 xwindow->height = height;
420 xwindow->internal = TRUE;
422 g_mutex_lock (ximagesink->x_lock);
424 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
425 ximagesink->xcontext->root,
426 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
428 /* We have to do that to prevent X from redrawing the background on
429 ConfigureNotify. This takes away flickering of video when resizing. */
430 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
432 /* set application name as a title */
433 gst_ximagesink_xwindow_set_title (ximagesink, xwindow, NULL);
435 if (ximagesink->handle_events) {
438 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
439 StructureNotifyMask | PointerMotionMask | KeyPressMask |
440 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
442 /* Tell the window manager we'd like delete client messages instead of
444 wm_delete = XInternAtom (ximagesink->xcontext->disp,
445 "WM_DELETE_WINDOW", False);
446 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
450 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
453 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
455 XSync (ximagesink->xcontext->disp, FALSE);
457 g_mutex_unlock (ximagesink->x_lock);
459 gst_ximagesink_xwindow_decorate (ximagesink, xwindow);
461 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
467 /* This function destroys a GstXWindow */
469 gst_ximagesink_xwindow_destroy (GstXImageSink * ximagesink,
470 GstXWindow * xwindow)
472 g_return_if_fail (xwindow != NULL);
473 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
475 g_mutex_lock (ximagesink->x_lock);
477 /* If we did not create that window we just free the GC and let it live */
478 if (xwindow->internal)
479 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
481 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
483 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
485 XSync (ximagesink->xcontext->disp, FALSE);
487 g_mutex_unlock (ximagesink->x_lock);
493 gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink)
495 XWindowAttributes attr;
496 gboolean reconfigure;
498 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
500 /* Update the window geometry */
501 g_mutex_lock (ximagesink->x_lock);
502 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
503 g_mutex_unlock (ximagesink->x_lock);
507 XGetWindowAttributes (ximagesink->xcontext->disp,
508 ximagesink->xwindow->win, &attr);
510 /* Check if we would suggest a different width/height now */
511 reconfigure = (ximagesink->xwindow->width != attr.width)
512 || (ximagesink->xwindow->height != attr.height);
513 ximagesink->xwindow->width = attr.width;
514 ximagesink->xwindow->height = attr.height;
516 g_mutex_unlock (ximagesink->x_lock);
519 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
520 gst_event_new_reconfigure ());
524 gst_ximagesink_xwindow_clear (GstXImageSink * ximagesink, GstXWindow * xwindow)
526 g_return_if_fail (xwindow != NULL);
527 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
529 g_mutex_lock (ximagesink->x_lock);
531 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
532 ximagesink->xcontext->black);
534 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
535 0, 0, xwindow->width, xwindow->height);
537 XSync (ximagesink->xcontext->disp, FALSE);
539 g_mutex_unlock (ximagesink->x_lock);
542 /* This function handles XEvents that might be in the queue. It generates
543 GstEvent that will be sent upstream in the pipeline to handle interactivity
546 gst_ximagesink_handle_xevents (GstXImageSink * ximagesink)
549 guint pointer_x = 0, pointer_y = 0;
550 gboolean pointer_moved = FALSE;
551 gboolean exposed = FALSE, configured = FALSE;
553 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
555 /* Then we get all pointer motion events, only the last position is
557 g_mutex_lock (ximagesink->flow_lock);
558 g_mutex_lock (ximagesink->x_lock);
559 while (XCheckWindowEvent (ximagesink->xcontext->disp,
560 ximagesink->xwindow->win, PointerMotionMask, &e)) {
561 g_mutex_unlock (ximagesink->x_lock);
562 g_mutex_unlock (ximagesink->flow_lock);
566 pointer_x = e.xmotion.x;
567 pointer_y = e.xmotion.y;
568 pointer_moved = TRUE;
573 g_mutex_lock (ximagesink->flow_lock);
574 g_mutex_lock (ximagesink->x_lock);
578 g_mutex_unlock (ximagesink->x_lock);
579 g_mutex_unlock (ximagesink->flow_lock);
581 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
582 pointer_x, pointer_y);
583 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
584 "mouse-move", 0, pointer_x, pointer_y);
586 g_mutex_lock (ximagesink->flow_lock);
587 g_mutex_lock (ximagesink->x_lock);
590 /* We get all remaining events on our window to throw them upstream */
591 while (XCheckWindowEvent (ximagesink->xcontext->disp,
592 ximagesink->xwindow->win,
593 KeyPressMask | KeyReleaseMask |
594 ButtonPressMask | ButtonReleaseMask, &e)) {
597 /* We lock only for the X function call */
598 g_mutex_unlock (ximagesink->x_lock);
599 g_mutex_unlock (ximagesink->flow_lock);
603 /* Mouse button pressed/released over our window. We send upstream
604 events for interactivity/navigation */
605 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
606 e.xbutton.button, e.xbutton.x, e.xbutton.x);
607 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
608 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
611 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
612 e.xbutton.button, e.xbutton.x, e.xbutton.x);
613 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
614 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
618 /* Key pressed/released over our window. We send upstream
619 events for interactivity/navigation */
620 GST_DEBUG ("ximagesink key %d pressed over window at %d,%d",
621 e.xkey.keycode, e.xkey.x, e.xkey.x);
622 g_mutex_lock (ximagesink->x_lock);
623 keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
625 g_mutex_unlock (ximagesink->x_lock);
626 if (keysym != NoSymbol) {
627 char *key_str = NULL;
629 g_mutex_lock (ximagesink->x_lock);
630 key_str = XKeysymToString (keysym);
631 g_mutex_unlock (ximagesink->x_lock);
632 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
633 e.type == KeyPress ? "key-press" : "key-release", key_str);
635 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
636 e.type == KeyPress ? "key-press" : "key-release", "unknown");
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;
1083 ximagesink = GST_XIMAGESINK (bsink);
1085 if (!ximagesink->xcontext)
1088 GST_DEBUG_OBJECT (ximagesink,
1089 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1090 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1092 /* We intersect those caps with our template to make sure they are correct */
1093 if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1094 goto incompatible_caps;
1096 if (!gst_video_info_from_caps (&info, caps))
1097 goto invalid_format;
1101 structure = gst_caps_get_structure (caps, 0);
1102 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1103 * otherwise linking should fail */
1104 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1106 if (ximagesink->par) {
1107 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1110 } else if (ximagesink->xcontext->par) {
1111 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1117 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1118 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1119 ximagesink->fps_n = info.fps_n;
1120 ximagesink->fps_d = info.fps_d;
1122 /* Notify application to set xwindow id now */
1123 g_mutex_lock (ximagesink->flow_lock);
1124 if (!ximagesink->xwindow) {
1125 g_mutex_unlock (ximagesink->flow_lock);
1126 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1128 g_mutex_unlock (ximagesink->flow_lock);
1131 /* Creating our window and our image */
1132 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1133 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1136 g_mutex_lock (ximagesink->flow_lock);
1137 if (!ximagesink->xwindow) {
1138 ximagesink->xwindow = gst_ximagesink_xwindow_new (ximagesink,
1139 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1142 ximagesink->info = info;
1144 /* Remember to draw borders for next frame */
1145 ximagesink->draw_border = TRUE;
1147 /* create a new pool for the new configuration */
1148 newpool = gst_ximage_buffer_pool_new (ximagesink);
1150 structure = gst_buffer_pool_get_config (newpool);
1151 gst_buffer_pool_config_set (structure, caps, size, 2, 0, 0, 15);
1152 if (!gst_buffer_pool_set_config (newpool, structure))
1155 oldpool = ximagesink->pool;
1156 ximagesink->pool = newpool;
1158 /* unref the old sink */
1160 /* we don't deactivate, some elements might still be using it, it will be
1161 * deactivated when the last ref is gone */
1162 gst_object_unref (oldpool);
1164 g_mutex_unlock (ximagesink->flow_lock);
1171 GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1176 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1181 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1186 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1187 ("Invalid image size."));
1192 GST_ERROR_OBJECT (ximagesink, "failed to set config.");
1193 g_mutex_unlock (ximagesink->flow_lock);
1198 static GstStateChangeReturn
1199 gst_ximagesink_change_state (GstElement * element, GstStateChange transition)
1201 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1202 GstXImageSink *ximagesink;
1203 GstXContext *xcontext = NULL;
1205 ximagesink = GST_XIMAGESINK (element);
1207 switch (transition) {
1208 case GST_STATE_CHANGE_NULL_TO_READY:
1209 /* Initializing the XContext */
1210 if (ximagesink->xcontext == NULL) {
1211 xcontext = gst_ximagesink_xcontext_get (ximagesink);
1212 if (xcontext == NULL) {
1213 ret = GST_STATE_CHANGE_FAILURE;
1216 GST_OBJECT_LOCK (ximagesink);
1218 ximagesink->xcontext = xcontext;
1219 GST_OBJECT_UNLOCK (ximagesink);
1222 /* call XSynchronize with the current value of synchronous */
1223 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1224 ximagesink->synchronous ? "TRUE" : "FALSE");
1225 g_mutex_lock (ximagesink->x_lock);
1226 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1227 g_mutex_unlock (ximagesink->x_lock);
1228 gst_ximagesink_manage_event_thread (ximagesink);
1230 case GST_STATE_CHANGE_READY_TO_PAUSED:
1231 g_mutex_lock (ximagesink->flow_lock);
1232 if (ximagesink->xwindow)
1233 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1234 g_mutex_unlock (ximagesink->flow_lock);
1236 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1242 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1244 switch (transition) {
1245 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1247 case GST_STATE_CHANGE_PAUSED_TO_READY:
1248 ximagesink->fps_n = 0;
1249 ximagesink->fps_d = 1;
1250 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1251 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1252 g_mutex_lock (ximagesink->flow_lock);
1253 if (ximagesink->pool)
1254 gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1255 g_mutex_unlock (ximagesink->flow_lock);
1257 case GST_STATE_CHANGE_READY_TO_NULL:
1258 gst_ximagesink_reset (ximagesink);
1269 gst_ximagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1270 GstClockTime * start, GstClockTime * end)
1272 GstXImageSink *ximagesink;
1274 ximagesink = GST_XIMAGESINK (bsink);
1276 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1277 *start = GST_BUFFER_TIMESTAMP (buf);
1278 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1279 *end = *start + GST_BUFFER_DURATION (buf);
1281 if (ximagesink->fps_n > 0) {
1283 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1290 static GstFlowReturn
1291 gst_ximagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1294 GstXImageSink *ximagesink;
1295 GstXImageMeta *meta;
1296 GstBuffer *to_put = NULL;
1298 ximagesink = GST_XIMAGESINK (vsink);
1300 meta = gst_buffer_get_ximage_meta (buf);
1302 if (meta && meta->sink == ximagesink) {
1303 /* If this buffer has been allocated using our buffer management we simply
1304 put the ximage which is in the PRIVATE pointer */
1305 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1309 GstVideoFrame src, dest;
1311 /* Else we have to copy the data into our private image, */
1312 /* if we have one... */
1313 GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1315 /* we should have a pool, configured in setcaps */
1316 if (ximagesink->pool == NULL)
1319 if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1320 goto activate_failed;
1322 /* take a buffer from our pool */
1323 res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, NULL);
1324 if (res != GST_FLOW_OK)
1327 GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, ximagesink,
1328 "slow copy into bufferpool buffer %p", to_put);
1330 if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1331 goto invalid_buffer;
1333 if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1334 gst_video_frame_unmap (&src);
1335 goto invalid_buffer;
1338 gst_video_frame_copy (&dest, &src);
1340 gst_video_frame_unmap (&dest);
1341 gst_video_frame_unmap (&src);
1344 if (!gst_ximagesink_ximage_put (ximagesink, to_put))
1349 gst_buffer_unref (to_put);
1356 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1357 ("Internal error: can't allocate images"),
1358 ("We don't have a bufferpool negotiated"));
1359 return GST_FLOW_ERROR;
1363 /* No image available. That's very bad ! */
1364 GST_WARNING_OBJECT (ximagesink, "could not create image");
1369 /* No Window available to put our image into */
1370 GST_WARNING_OBJECT (ximagesink, "could map image");
1376 /* No Window available to put our image into */
1377 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1378 res = GST_FLOW_ERROR;
1383 GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1384 res = GST_FLOW_ERROR;
1390 gst_ximagesink_event (GstBaseSink * sink, GstEvent * event)
1392 GstXImageSink *ximagesink = GST_XIMAGESINK (sink);
1394 switch (GST_EVENT_TYPE (event)) {
1395 case GST_EVENT_TAG:{
1397 gchar *title = NULL;
1399 gst_event_parse_tag (event, &l);
1400 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1403 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1404 gst_ximagesink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1414 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1418 gst_ximagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1420 GstXImageSink *ximagesink = GST_XIMAGESINK (bsink);
1421 GstBufferPool *pool;
1422 GstStructure *config;
1427 gst_query_parse_allocation (query, &caps, &need_pool);
1432 g_mutex_lock (ximagesink->flow_lock);
1433 if ((pool = ximagesink->pool))
1434 gst_object_ref (pool);
1435 g_mutex_unlock (ximagesink->flow_lock);
1438 const GstCaps *pcaps;
1440 /* we had a pool, check caps */
1441 config = gst_buffer_pool_get_config (pool);
1442 gst_buffer_pool_config_get (config, &pcaps, &size, NULL, NULL, NULL, NULL);
1444 GST_DEBUG_OBJECT (ximagesink,
1445 "we had a pool with caps %" GST_PTR_FORMAT, pcaps);
1446 if (!gst_caps_is_equal (caps, pcaps)) {
1447 /* different caps, we can't use this pool */
1448 GST_DEBUG_OBJECT (ximagesink, "pool has different caps");
1449 gst_object_unref (pool);
1453 if (pool == NULL && need_pool) {
1456 GST_DEBUG_OBJECT (ximagesink, "create new pool");
1457 pool = gst_ximage_buffer_pool_new (ximagesink);
1459 if (!gst_video_info_from_caps (&info, caps))
1462 /* the normal size of a frame */
1465 config = gst_buffer_pool_get_config (pool);
1466 gst_buffer_pool_config_set (config, caps, size, 0, 0, 0, 0);
1467 if (!gst_buffer_pool_set_config (pool, config))
1470 /* we need at least 2 buffer because we hold on to the last one */
1471 gst_query_set_allocation_params (query, size, 2, 0, 0, 0, pool);
1473 /* we also support various metadata */
1474 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE);
1475 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE);
1477 gst_object_unref (pool);
1484 GST_DEBUG_OBJECT (bsink, "no caps specified");
1489 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1494 GST_DEBUG_OBJECT (bsink, "failed setting config");
1499 /* Interfaces stuff */
1501 gst_ximagesink_navigation_send_event (GstNavigation * navigation,
1502 GstStructure * structure)
1504 GstXImageSink *ximagesink = GST_XIMAGESINK (navigation);
1506 gint x_offset, y_offset;
1510 event = gst_event_new_navigation (structure);
1512 /* We are not converting the pointer coordinates as there's no hardware
1513 scaling done here. The only possible scaling is done by videoscale and
1514 videoscale will have to catch those events and tranform the coordinates
1515 to match the applied scaling. So here we just add the offset if the image
1516 is centered in the window. */
1518 /* We take the flow_lock while we look at the window */
1519 g_mutex_lock (ximagesink->flow_lock);
1521 if (!ximagesink->xwindow) {
1522 g_mutex_unlock (ximagesink->flow_lock);
1526 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1527 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1529 g_mutex_unlock (ximagesink->flow_lock);
1531 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1533 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1535 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1537 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1540 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
1542 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
1543 gst_pad_send_event (pad, event);
1545 gst_object_unref (pad);
1550 gst_ximagesink_navigation_init (GstNavigationInterface * iface)
1552 iface->send_event = gst_ximagesink_navigation_send_event;
1556 gst_ximagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1558 XID xwindow_id = id;
1559 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1560 GstXWindow *xwindow = NULL;
1561 XWindowAttributes attr;
1563 /* We acquire the stream lock while setting this window in the element.
1564 We are basically cleaning tons of stuff replacing the old window, putting
1565 images while we do that would surely crash */
1566 g_mutex_lock (ximagesink->flow_lock);
1568 /* If we already use that window return */
1569 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1570 g_mutex_unlock (ximagesink->flow_lock);
1574 /* If the element has not initialized the X11 context try to do so */
1575 if (!ximagesink->xcontext &&
1576 !(ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink))) {
1577 g_mutex_unlock (ximagesink->flow_lock);
1578 /* we have thrown a GST_ELEMENT_ERROR now */
1582 /* If a window is there already we destroy it */
1583 if (ximagesink->xwindow) {
1584 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1585 ximagesink->xwindow = NULL;
1588 /* If the xid is 0 we go back to an internal window */
1589 if (xwindow_id == 0) {
1590 /* If no width/height caps nego did not happen window will be created
1591 during caps nego then */
1592 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1593 xwindow = gst_ximagesink_xwindow_new (ximagesink,
1594 GST_VIDEO_SINK_WIDTH (ximagesink),
1595 GST_VIDEO_SINK_HEIGHT (ximagesink));
1598 xwindow = g_new0 (GstXWindow, 1);
1600 xwindow->win = xwindow_id;
1602 /* We get window geometry, set the event we want to receive,
1604 g_mutex_lock (ximagesink->x_lock);
1605 XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
1606 xwindow->width = attr.width;
1607 xwindow->height = attr.height;
1608 xwindow->internal = FALSE;
1609 if (ximagesink->handle_events) {
1610 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1611 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1615 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1616 g_mutex_unlock (ximagesink->x_lock);
1620 ximagesink->xwindow = xwindow;
1622 g_mutex_unlock (ximagesink->flow_lock);
1626 gst_ximagesink_expose (GstVideoOverlay * overlay)
1628 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1630 gst_ximagesink_xwindow_update_geometry (ximagesink);
1631 gst_ximagesink_ximage_put (ximagesink, NULL);
1635 gst_ximagesink_set_event_handling (GstVideoOverlay * overlay,
1636 gboolean handle_events)
1638 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1640 ximagesink->handle_events = handle_events;
1642 g_mutex_lock (ximagesink->flow_lock);
1644 if (G_UNLIKELY (!ximagesink->xwindow)) {
1645 g_mutex_unlock (ximagesink->flow_lock);
1649 g_mutex_lock (ximagesink->x_lock);
1651 if (handle_events) {
1652 if (ximagesink->xwindow->internal) {
1653 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1654 ExposureMask | StructureNotifyMask | PointerMotionMask |
1655 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1657 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1658 ExposureMask | StructureNotifyMask | PointerMotionMask |
1659 KeyPressMask | KeyReleaseMask);
1662 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1665 g_mutex_unlock (ximagesink->x_lock);
1667 g_mutex_unlock (ximagesink->flow_lock);
1671 gst_ximagesink_video_overlay_init (GstVideoOverlayInterface * iface)
1673 iface->set_window_handle = gst_ximagesink_set_window_handle;
1674 iface->expose = gst_ximagesink_expose;
1675 iface->handle_events = gst_ximagesink_set_event_handling;
1678 /* =========================================== */
1680 /* Init & Class init */
1682 /* =========================================== */
1685 gst_ximagesink_set_property (GObject * object, guint prop_id,
1686 const GValue * value, GParamSpec * pspec)
1688 GstXImageSink *ximagesink;
1690 g_return_if_fail (GST_IS_XIMAGESINK (object));
1692 ximagesink = GST_XIMAGESINK (object);
1696 ximagesink->display_name = g_strdup (g_value_get_string (value));
1698 case PROP_SYNCHRONOUS:
1699 ximagesink->synchronous = g_value_get_boolean (value);
1700 if (ximagesink->xcontext) {
1701 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1702 ximagesink->synchronous ? "TRUE" : "FALSE");
1703 g_mutex_lock (ximagesink->x_lock);
1704 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1705 g_mutex_unlock (ximagesink->x_lock);
1708 case PROP_FORCE_ASPECT_RATIO:
1709 ximagesink->keep_aspect = g_value_get_boolean (value);
1711 case PROP_PIXEL_ASPECT_RATIO:
1715 tmp = g_new0 (GValue, 1);
1716 g_value_init (tmp, GST_TYPE_FRACTION);
1718 if (!g_value_transform (value, tmp)) {
1719 GST_WARNING_OBJECT (ximagesink,
1720 "Could not transform string to aspect ratio");
1723 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1724 gst_value_get_fraction_numerator (tmp),
1725 gst_value_get_fraction_denominator (tmp));
1726 g_free (ximagesink->par);
1727 ximagesink->par = tmp;
1731 case PROP_HANDLE_EVENTS:
1732 gst_ximagesink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1733 g_value_get_boolean (value));
1734 gst_ximagesink_manage_event_thread (ximagesink);
1736 case PROP_HANDLE_EXPOSE:
1737 ximagesink->handle_expose = g_value_get_boolean (value);
1738 gst_ximagesink_manage_event_thread (ximagesink);
1741 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1747 gst_ximagesink_get_property (GObject * object, guint prop_id,
1748 GValue * value, GParamSpec * pspec)
1750 GstXImageSink *ximagesink;
1752 g_return_if_fail (GST_IS_XIMAGESINK (object));
1754 ximagesink = GST_XIMAGESINK (object);
1758 g_value_set_string (value, ximagesink->display_name);
1760 case PROP_SYNCHRONOUS:
1761 g_value_set_boolean (value, ximagesink->synchronous);
1763 case PROP_FORCE_ASPECT_RATIO:
1764 g_value_set_boolean (value, ximagesink->keep_aspect);
1766 case PROP_PIXEL_ASPECT_RATIO:
1767 if (ximagesink->par)
1768 g_value_transform (ximagesink->par, value);
1770 case PROP_HANDLE_EVENTS:
1771 g_value_set_boolean (value, ximagesink->handle_events);
1773 case PROP_HANDLE_EXPOSE:
1774 g_value_set_boolean (value, ximagesink->handle_expose);
1776 case PROP_WINDOW_WIDTH:
1777 if (ximagesink->xwindow)
1778 g_value_set_uint64 (value, ximagesink->xwindow->width);
1780 g_value_set_uint64 (value, 0);
1782 case PROP_WINDOW_HEIGHT:
1783 if (ximagesink->xwindow)
1784 g_value_set_uint64 (value, ximagesink->xwindow->height);
1786 g_value_set_uint64 (value, 0);
1789 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1795 gst_ximagesink_reset (GstXImageSink * ximagesink)
1799 GST_OBJECT_LOCK (ximagesink);
1800 ximagesink->running = FALSE;
1801 /* grab thread and mark it as NULL */
1802 thread = ximagesink->event_thread;
1803 ximagesink->event_thread = NULL;
1804 GST_OBJECT_UNLOCK (ximagesink);
1806 /* Wait for our event thread to finish before we clean up our stuff. */
1808 g_thread_join (thread);
1810 if (ximagesink->cur_image) {
1811 gst_buffer_unref (ximagesink->cur_image);
1812 ximagesink->cur_image = NULL;
1815 g_mutex_lock (ximagesink->flow_lock);
1817 if (ximagesink->pool) {
1818 gst_object_unref (ximagesink->pool);
1819 ximagesink->pool = NULL;
1822 if (ximagesink->xwindow) {
1823 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1824 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1825 ximagesink->xwindow = NULL;
1827 g_mutex_unlock (ximagesink->flow_lock);
1829 gst_ximagesink_xcontext_clear (ximagesink);
1833 gst_ximagesink_finalize (GObject * object)
1835 GstXImageSink *ximagesink;
1837 ximagesink = GST_XIMAGESINK (object);
1839 gst_ximagesink_reset (ximagesink);
1841 if (ximagesink->display_name) {
1842 g_free (ximagesink->display_name);
1843 ximagesink->display_name = NULL;
1845 if (ximagesink->par) {
1846 g_free (ximagesink->par);
1847 ximagesink->par = NULL;
1849 if (ximagesink->x_lock) {
1850 g_mutex_free (ximagesink->x_lock);
1851 ximagesink->x_lock = NULL;
1853 if (ximagesink->flow_lock) {
1854 g_mutex_free (ximagesink->flow_lock);
1855 ximagesink->flow_lock = NULL;
1858 g_free (ximagesink->media_title);
1860 G_OBJECT_CLASS (parent_class)->finalize (object);
1864 gst_ximagesink_init (GstXImageSink * ximagesink)
1866 ximagesink->display_name = NULL;
1867 ximagesink->xcontext = NULL;
1868 ximagesink->xwindow = NULL;
1869 ximagesink->cur_image = NULL;
1871 ximagesink->event_thread = NULL;
1872 ximagesink->running = FALSE;
1874 ximagesink->fps_n = 0;
1875 ximagesink->fps_d = 1;
1877 ximagesink->x_lock = g_mutex_new ();
1878 ximagesink->flow_lock = g_mutex_new ();
1880 ximagesink->par = NULL;
1882 ximagesink->pool = NULL;
1884 ximagesink->synchronous = FALSE;
1885 ximagesink->keep_aspect = FALSE;
1886 ximagesink->handle_events = TRUE;
1887 ximagesink->handle_expose = TRUE;
1891 gst_ximagesink_class_init (GstXImageSinkClass * klass)
1893 GObjectClass *gobject_class;
1894 GstElementClass *gstelement_class;
1895 GstBaseSinkClass *gstbasesink_class;
1896 GstVideoSinkClass *videosink_class;
1898 gobject_class = (GObjectClass *) klass;
1899 gstelement_class = (GstElementClass *) klass;
1900 gstbasesink_class = (GstBaseSinkClass *) klass;
1901 videosink_class = (GstVideoSinkClass *) klass;
1903 gobject_class->finalize = gst_ximagesink_finalize;
1904 gobject_class->set_property = gst_ximagesink_set_property;
1905 gobject_class->get_property = gst_ximagesink_get_property;
1907 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1908 g_param_spec_string ("display", "Display", "X Display name",
1909 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1910 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1911 g_param_spec_boolean ("synchronous", "Synchronous",
1912 "When enabled, runs the X display in synchronous mode. "
1913 "(unrelated to A/V sync, used only for debugging)", FALSE,
1914 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1915 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1916 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1917 "When enabled, reverse caps negotiation (scaling) will respect "
1918 "original aspect ratio", FALSE,
1919 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1920 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1921 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1922 "The pixel aspect ratio of the device", "1/1",
1923 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1924 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1925 g_param_spec_boolean ("handle-events", "Handle XEvents",
1926 "When enabled, XEvents will be selected and handled", TRUE,
1927 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1928 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1929 g_param_spec_boolean ("handle-expose", "Handle expose",
1931 "the current frame will always be drawn in response to X Expose "
1932 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1935 * GstXImageSink:window-width
1937 * Actual width of the video window.
1941 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1942 g_param_spec_uint64 ("window-width", "window-width",
1943 "Width of the window", 0, G_MAXUINT64, 0,
1944 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1947 * GstXImageSink:window-height
1949 * Actual height of the video window.
1953 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1954 g_param_spec_uint64 ("window-height", "window-height",
1955 "Height of the window", 0, G_MAXUINT64, 0,
1956 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1958 gst_element_class_set_details_simple (gstelement_class,
1959 "Video sink", "Sink/Video",
1960 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1962 gst_element_class_add_pad_template (gstelement_class,
1963 gst_static_pad_template_get (&gst_ximagesink_sink_template_factory));
1965 gstelement_class->change_state = gst_ximagesink_change_state;
1967 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_getcaps);
1968 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_setcaps);
1969 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_ximagesink_get_times);
1970 gstbasesink_class->propose_allocation =
1971 GST_DEBUG_FUNCPTR (gst_ximagesink_propose_allocation);
1972 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_ximagesink_event);
1974 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);