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;
283 src.h = meta->height;
285 dst.w = ximagesink->xwindow->width;
286 dst.h = ximagesink->xwindow->height;
288 gst_video_sink_center_rect (src, dst, &result, FALSE);
290 g_mutex_lock (ximagesink->x_lock);
293 gst_ximagesink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
295 ximagesink->draw_border = FALSE;
298 if (ximagesink->xcontext->use_xshm) {
299 GST_LOG_OBJECT (ximagesink,
300 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
301 ximage, 0, 0, result.x, result.y, result.w, result.h,
302 ximagesink->xwindow->width, ximagesink->xwindow->height);
303 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
304 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
305 result.w, result.h, FALSE);
307 #endif /* HAVE_XSHM */
309 GST_LOG_OBJECT (ximagesink,
310 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
311 ximage, 0, 0, result.x, result.y, result.w, result.h,
312 ximagesink->xwindow->width, ximagesink->xwindow->height);
313 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
314 ximagesink->xwindow->gc, meta->ximage, src.x, src.y, result.x, result.y,
318 XSync (ximagesink->xcontext->disp, FALSE);
320 g_mutex_unlock (ximagesink->x_lock);
322 g_mutex_unlock (ximagesink->flow_lock);
328 gst_ximagesink_xwindow_decorate (GstXImageSink * ximagesink,
331 Atom hints_atom = None;
334 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
335 g_return_val_if_fail (window != NULL, FALSE);
337 g_mutex_lock (ximagesink->x_lock);
339 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
341 if (hints_atom == None) {
342 g_mutex_unlock (ximagesink->x_lock);
346 hints = g_malloc0 (sizeof (MotifWmHints));
348 hints->flags |= MWM_HINTS_DECORATIONS;
349 hints->decorations = 1 << 0;
351 XChangeProperty (ximagesink->xcontext->disp, window->win,
352 hints_atom, hints_atom, 32, PropModeReplace,
353 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
355 XSync (ximagesink->xcontext->disp, FALSE);
357 g_mutex_unlock (ximagesink->x_lock);
365 gst_ximagesink_xwindow_set_title (GstXImageSink * ximagesink,
366 GstXWindow * xwindow, const gchar * media_title)
369 g_free (ximagesink->media_title);
370 ximagesink->media_title = g_strdup (media_title);
373 /* we have a window */
374 if (xwindow->internal) {
375 XTextProperty xproperty;
376 const gchar *app_name;
377 const gchar *title = NULL;
378 gchar *title_mem = NULL;
380 /* set application name as a title */
381 app_name = g_get_application_name ();
383 if (app_name && ximagesink->media_title) {
384 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
386 } else if (app_name) {
388 } else if (ximagesink->media_title) {
389 title = ximagesink->media_title;
393 if ((XStringListToTextProperty (((char **) &title), 1,
395 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
396 XFree (xproperty.value);
405 /* This function handles a GstXWindow creation */
407 gst_ximagesink_xwindow_new (GstXImageSink * ximagesink, gint width, gint height)
409 GstXWindow *xwindow = NULL;
412 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
414 xwindow = g_new0 (GstXWindow, 1);
416 xwindow->width = width;
417 xwindow->height = height;
418 xwindow->internal = TRUE;
420 g_mutex_lock (ximagesink->x_lock);
422 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
423 ximagesink->xcontext->root,
424 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
426 /* We have to do that to prevent X from redrawing the background on
427 ConfigureNotify. This takes away flickering of video when resizing. */
428 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
430 /* set application name as a title */
431 gst_ximagesink_xwindow_set_title (ximagesink, xwindow, NULL);
433 if (ximagesink->handle_events) {
436 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
437 StructureNotifyMask | PointerMotionMask | KeyPressMask |
438 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
440 /* Tell the window manager we'd like delete client messages instead of
442 wm_delete = XInternAtom (ximagesink->xcontext->disp,
443 "WM_DELETE_WINDOW", False);
444 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
448 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
451 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
453 XSync (ximagesink->xcontext->disp, FALSE);
455 g_mutex_unlock (ximagesink->x_lock);
457 gst_ximagesink_xwindow_decorate (ximagesink, xwindow);
459 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
465 /* This function destroys a GstXWindow */
467 gst_ximagesink_xwindow_destroy (GstXImageSink * ximagesink,
468 GstXWindow * xwindow)
470 g_return_if_fail (xwindow != NULL);
471 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
473 g_mutex_lock (ximagesink->x_lock);
475 /* If we did not create that window we just free the GC and let it live */
476 if (xwindow->internal)
477 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
479 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
481 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
483 XSync (ximagesink->xcontext->disp, FALSE);
485 g_mutex_unlock (ximagesink->x_lock);
491 gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink)
493 XWindowAttributes attr;
494 gboolean reconfigure;
496 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
498 /* Update the window geometry */
499 g_mutex_lock (ximagesink->x_lock);
500 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
501 g_mutex_unlock (ximagesink->x_lock);
505 XGetWindowAttributes (ximagesink->xcontext->disp,
506 ximagesink->xwindow->win, &attr);
508 /* Check if we would suggest a different width/height now */
509 reconfigure = (ximagesink->xwindow->width != attr.width)
510 || (ximagesink->xwindow->height != attr.height);
511 ximagesink->xwindow->width = attr.width;
512 ximagesink->xwindow->height = attr.height;
514 g_mutex_unlock (ximagesink->x_lock);
517 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
518 gst_event_new_reconfigure ());
522 gst_ximagesink_xwindow_clear (GstXImageSink * ximagesink, GstXWindow * xwindow)
524 g_return_if_fail (xwindow != NULL);
525 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
527 g_mutex_lock (ximagesink->x_lock);
529 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
530 ximagesink->xcontext->black);
532 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
533 0, 0, xwindow->width, xwindow->height);
535 XSync (ximagesink->xcontext->disp, FALSE);
537 g_mutex_unlock (ximagesink->x_lock);
540 /* This function handles XEvents that might be in the queue. It generates
541 GstEvent that will be sent upstream in the pipeline to handle interactivity
544 gst_ximagesink_handle_xevents (GstXImageSink * ximagesink)
547 guint pointer_x = 0, pointer_y = 0;
548 gboolean pointer_moved = FALSE;
549 gboolean exposed = FALSE, configured = FALSE;
551 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
553 /* Then we get all pointer motion events, only the last position is
555 g_mutex_lock (ximagesink->flow_lock);
556 g_mutex_lock (ximagesink->x_lock);
557 while (XCheckWindowEvent (ximagesink->xcontext->disp,
558 ximagesink->xwindow->win, PointerMotionMask, &e)) {
559 g_mutex_unlock (ximagesink->x_lock);
560 g_mutex_unlock (ximagesink->flow_lock);
564 pointer_x = e.xmotion.x;
565 pointer_y = e.xmotion.y;
566 pointer_moved = TRUE;
571 g_mutex_lock (ximagesink->flow_lock);
572 g_mutex_lock (ximagesink->x_lock);
576 g_mutex_unlock (ximagesink->x_lock);
577 g_mutex_unlock (ximagesink->flow_lock);
579 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
580 pointer_x, pointer_y);
581 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
582 "mouse-move", 0, pointer_x, pointer_y);
584 g_mutex_lock (ximagesink->flow_lock);
585 g_mutex_lock (ximagesink->x_lock);
588 /* We get all remaining events on our window to throw them upstream */
589 while (XCheckWindowEvent (ximagesink->xcontext->disp,
590 ximagesink->xwindow->win,
591 KeyPressMask | KeyReleaseMask |
592 ButtonPressMask | ButtonReleaseMask, &e)) {
595 /* We lock only for the X function call */
596 g_mutex_unlock (ximagesink->x_lock);
597 g_mutex_unlock (ximagesink->flow_lock);
601 /* Mouse button pressed/released over our window. We send upstream
602 events for interactivity/navigation */
603 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
604 e.xbutton.button, e.xbutton.x, e.xbutton.x);
605 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
606 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
609 GST_DEBUG ("ximagesink button %d release 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-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
616 /* Key pressed/released over our window. We send upstream
617 events for interactivity/navigation */
618 GST_DEBUG ("ximagesink key %d pressed over window at %d,%d",
619 e.xkey.keycode, e.xkey.x, e.xkey.x);
620 g_mutex_lock (ximagesink->x_lock);
621 keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
623 g_mutex_unlock (ximagesink->x_lock);
624 if (keysym != NoSymbol) {
625 char *key_str = NULL;
627 g_mutex_lock (ximagesink->x_lock);
628 key_str = XKeysymToString (keysym);
629 g_mutex_unlock (ximagesink->x_lock);
630 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
631 e.type == KeyPress ? "key-press" : "key-release", key_str);
633 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
634 e.type == KeyPress ? "key-press" : "key-release", "unknown");
638 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
641 g_mutex_lock (ximagesink->flow_lock);
642 g_mutex_lock (ximagesink->x_lock);
646 while (XCheckWindowEvent (ximagesink->xcontext->disp,
647 ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
652 case ConfigureNotify:
653 g_mutex_unlock (ximagesink->x_lock);
654 gst_ximagesink_xwindow_update_geometry (ximagesink);
655 g_mutex_lock (ximagesink->x_lock);
663 if (ximagesink->handle_expose && (exposed || configured)) {
664 g_mutex_unlock (ximagesink->x_lock);
665 g_mutex_unlock (ximagesink->flow_lock);
667 gst_ximagesink_expose (GST_VIDEO_OVERLAY (ximagesink));
669 g_mutex_lock (ximagesink->flow_lock);
670 g_mutex_lock (ximagesink->x_lock);
673 /* Handle Display events */
674 while (XPending (ximagesink->xcontext->disp)) {
675 XNextEvent (ximagesink->xcontext->disp, &e);
681 wm_delete = XInternAtom (ximagesink->xcontext->disp,
682 "WM_DELETE_WINDOW", False);
683 if (wm_delete == (Atom) e.xclient.data.l[0]) {
684 /* Handle window deletion by posting an error on the bus */
685 GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
686 ("Output window was closed"), (NULL));
688 g_mutex_unlock (ximagesink->x_lock);
689 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
690 ximagesink->xwindow = NULL;
691 g_mutex_lock (ximagesink->x_lock);
700 g_mutex_unlock (ximagesink->x_lock);
701 g_mutex_unlock (ximagesink->flow_lock);
705 gst_ximagesink_event_thread (GstXImageSink * ximagesink)
707 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
709 GST_OBJECT_LOCK (ximagesink);
710 while (ximagesink->running) {
711 GST_OBJECT_UNLOCK (ximagesink);
713 if (ximagesink->xwindow) {
714 gst_ximagesink_handle_xevents (ximagesink);
716 /* FIXME: do we want to align this with the framerate or anything else? */
717 g_usleep (G_USEC_PER_SEC / 20);
719 GST_OBJECT_LOCK (ximagesink);
721 GST_OBJECT_UNLOCK (ximagesink);
727 gst_ximagesink_manage_event_thread (GstXImageSink * ximagesink)
729 GThread *thread = NULL;
731 /* don't start the thread too early */
732 if (ximagesink->xcontext == NULL) {
736 GST_OBJECT_LOCK (ximagesink);
737 if (ximagesink->handle_expose || ximagesink->handle_events) {
738 if (!ximagesink->event_thread) {
739 /* Setup our event listening thread */
740 GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
741 ximagesink->handle_expose, ximagesink->handle_events);
742 ximagesink->running = TRUE;
743 ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
744 (GThreadFunc) gst_ximagesink_event_thread, ximagesink, NULL);
747 if (ximagesink->event_thread) {
748 GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
749 ximagesink->handle_expose, ximagesink->handle_events);
750 ximagesink->running = FALSE;
751 /* grab thread and mark it as NULL */
752 thread = ximagesink->event_thread;
753 ximagesink->event_thread = NULL;
756 GST_OBJECT_UNLOCK (ximagesink);
758 /* Wait for our event thread to finish */
760 g_thread_join (thread);
765 /* This function calculates the pixel aspect ratio based on the properties
766 * in the xcontext structure and stores it there. */
768 gst_ximagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
770 static const gint par[][2] = {
771 {1, 1}, /* regular screen */
772 {16, 15}, /* PAL TV */
773 {11, 10}, /* 525 line Rec.601 video */
774 {54, 59}, /* 625 line Rec.601 video */
775 {64, 45}, /* 1280x1024 on 16:9 display */
776 {5, 3}, /* 1280x1024 on 4:3 display */
777 {4, 3} /* 800x600 on 16:9 display */
784 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
786 /* first calculate the "real" ratio based on the X values;
787 * which is the "physical" w/h divided by the w/h in pixels of the display */
788 ratio = (gdouble) (xcontext->widthmm * xcontext->height)
789 / (xcontext->heightmm * xcontext->width);
791 /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
793 if (xcontext->width == 720 && xcontext->height == 576) {
794 ratio = 4.0 * 576 / (3.0 * 720);
796 GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
798 /* now find the one from par[][2] with the lowest delta to the real one */
802 for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
803 gdouble this_delta = DELTA (i);
805 if (this_delta < delta) {
811 GST_DEBUG ("Decided on index %d (%d/%d)", index,
812 par[index][0], par[index][1]);
814 g_free (xcontext->par);
815 xcontext->par = g_new0 (GValue, 1);
816 g_value_init (xcontext->par, GST_TYPE_FRACTION);
817 gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
818 GST_DEBUG ("set xcontext PAR to %d/%d",
819 gst_value_get_fraction_numerator (xcontext->par),
820 gst_value_get_fraction_denominator (xcontext->par));
823 /* This function gets the X Display and global info about it. Everything is
824 stored in our object and will be cleaned when the object is disposed. Note
825 here that caps for supported format are generated without any window or
828 gst_ximagesink_xcontext_get (GstXImageSink * ximagesink)
830 GstXContext *xcontext = NULL;
831 XPixmapFormatValues *px_formats = NULL;
832 gint nb_formats = 0, i;
834 GstVideoFormat vformat;
836 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
838 xcontext = g_new0 (GstXContext, 1);
840 g_mutex_lock (ximagesink->x_lock);
842 xcontext->disp = XOpenDisplay (ximagesink->display_name);
844 if (!xcontext->disp) {
845 g_mutex_unlock (ximagesink->x_lock);
847 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
848 ("Could not initialise X output"), ("Could not open display"));
852 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
853 xcontext->screen_num = DefaultScreen (xcontext->disp);
854 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
855 xcontext->root = DefaultRootWindow (xcontext->disp);
856 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
857 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
858 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
860 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
861 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
862 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
863 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
865 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
866 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
868 gst_ximagesink_calculate_pixel_aspect_ratio (xcontext);
870 /* We get supported pixmap formats at supported depth */
871 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
874 XCloseDisplay (xcontext->disp);
875 g_mutex_unlock (ximagesink->x_lock);
876 g_free (xcontext->par);
878 GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
879 ("Could not get supported pixmap formats"), (NULL));
883 /* We get bpp value corresponding to our running depth */
884 for (i = 0; i < nb_formats; i++) {
885 if (px_formats[i].depth == xcontext->depth)
886 xcontext->bpp = px_formats[i].bits_per_pixel;
891 endianness = (ImageByteOrder (xcontext->disp) ==
892 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
894 /* Search for XShm extension support */
896 if (XShmQueryExtension (xcontext->disp) &&
897 gst_ximagesink_check_xshm_calls (ximagesink, xcontext)) {
898 xcontext->use_xshm = TRUE;
899 GST_DEBUG ("ximagesink is using XShm extension");
901 #endif /* HAVE_XSHM */
903 xcontext->use_xshm = FALSE;
904 GST_DEBUG ("ximagesink is not using XShm extension");
907 vformat = gst_video_format_from_masks (xcontext->depth, xcontext->bpp,
908 endianness, xcontext->visual->red_mask, xcontext->visual->green_mask,
909 xcontext->visual->blue_mask, 0);
911 if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
914 /* update object's par with calculated one if not set yet */
915 if (!ximagesink->par) {
916 ximagesink->par = g_new0 (GValue, 1);
917 gst_value_init_and_copy (ximagesink->par, xcontext->par);
918 GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
920 xcontext->caps = gst_caps_new_simple ("video/x-raw",
921 "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
922 "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
923 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
924 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
925 if (ximagesink->par) {
928 nom = gst_value_get_fraction_numerator (ximagesink->par);
929 den = gst_value_get_fraction_denominator (ximagesink->par);
930 gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
931 GST_TYPE_FRACTION, nom, den, NULL);
934 g_mutex_unlock (ximagesink->x_lock);
941 GST_ERROR_OBJECT (ximagesink, "unknown format");
946 /* This function cleans the X context. Closing the Display and unrefing the
947 caps for supported formats. */
949 gst_ximagesink_xcontext_clear (GstXImageSink * ximagesink)
951 GstXContext *xcontext;
953 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
955 GST_OBJECT_LOCK (ximagesink);
956 if (ximagesink->xcontext == NULL) {
957 GST_OBJECT_UNLOCK (ximagesink);
961 /* Take the xcontext reference and NULL it while we
962 * clean it up, so that any buffer-alloced buffers
963 * arriving after this will be freed correctly */
964 xcontext = ximagesink->xcontext;
965 ximagesink->xcontext = NULL;
967 GST_OBJECT_UNLOCK (ximagesink);
969 gst_caps_unref (xcontext->caps);
970 g_free (xcontext->par);
971 g_free (ximagesink->par);
972 ximagesink->par = NULL;
974 if (xcontext->last_caps)
975 gst_caps_replace (&xcontext->last_caps, NULL);
977 g_mutex_lock (ximagesink->x_lock);
979 XCloseDisplay (xcontext->disp);
981 g_mutex_unlock (ximagesink->x_lock);
989 gst_ximagesink_getcaps (GstBaseSink * bsink, GstCaps * filter)
991 GstXImageSink *ximagesink;
995 ximagesink = GST_XIMAGESINK (bsink);
997 g_mutex_lock (ximagesink->x_lock);
998 if (ximagesink->xcontext) {
1001 caps = gst_caps_ref (ximagesink->xcontext->caps);
1004 GstCaps *intersection;
1007 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1008 gst_caps_unref (caps);
1009 caps = intersection;
1012 if (ximagesink->xwindow && ximagesink->xwindow->width) {
1013 GstStructure *s0, *s1;
1015 caps = gst_caps_make_writable (caps);
1017 /* There can only be a single structure because the xcontext
1018 * caps only have a single structure */
1019 s0 = gst_caps_get_structure (caps, 0);
1020 s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1022 gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1023 "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1024 gst_caps_append_structure (caps, s1);
1026 /* This will not change the order but will remove the
1027 * fixed width/height caps again if not possible
1030 GstCaps *intersection;
1033 gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1034 gst_caps_unref (caps);
1035 caps = intersection;
1039 g_mutex_unlock (ximagesink->x_lock);
1042 g_mutex_unlock (ximagesink->x_lock);
1044 /* get a template copy and add the pixel aspect ratio */
1045 caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1046 if (ximagesink->par) {
1047 caps = gst_caps_make_writable (caps);
1048 for (i = 0; i < gst_caps_get_size (caps); ++i) {
1049 GstStructure *structure = gst_caps_get_structure (caps, i);
1052 nom = gst_value_get_fraction_numerator (ximagesink->par);
1053 den = gst_value_get_fraction_denominator (ximagesink->par);
1054 gst_structure_set (structure, "pixel-aspect-ratio",
1055 GST_TYPE_FRACTION, nom, den, NULL);
1060 GstCaps *intersection;
1063 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1064 gst_caps_unref (caps);
1065 caps = intersection;
1072 gst_ximagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1074 GstXImageSink *ximagesink;
1075 GstStructure *structure;
1077 GstBufferPool *newpool, *oldpool;
1081 ximagesink = GST_XIMAGESINK (bsink);
1083 if (!ximagesink->xcontext)
1086 GST_DEBUG_OBJECT (ximagesink,
1087 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1088 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1090 /* We intersect those caps with our template to make sure they are correct */
1091 if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1092 goto incompatible_caps;
1094 if (!gst_video_info_from_caps (&info, caps))
1095 goto invalid_format;
1099 structure = gst_caps_get_structure (caps, 0);
1100 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1101 * otherwise linking should fail */
1102 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1104 if (ximagesink->par) {
1105 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1108 } else if (ximagesink->xcontext->par) {
1109 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1115 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1116 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1117 ximagesink->fps_n = info.fps_n;
1118 ximagesink->fps_d = info.fps_d;
1120 /* Notify application to set xwindow id now */
1121 g_mutex_lock (ximagesink->flow_lock);
1122 if (!ximagesink->xwindow) {
1123 g_mutex_unlock (ximagesink->flow_lock);
1124 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1126 g_mutex_unlock (ximagesink->flow_lock);
1129 /* Creating our window and our image */
1130 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1131 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1134 g_mutex_lock (ximagesink->flow_lock);
1135 if (!ximagesink->xwindow) {
1136 ximagesink->xwindow = gst_ximagesink_xwindow_new (ximagesink,
1137 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1140 ximagesink->info = info;
1142 /* Remember to draw borders for next frame */
1143 ximagesink->draw_border = TRUE;
1145 /* create a new pool for the new configuration */
1146 newpool = gst_ximage_buffer_pool_new (ximagesink);
1148 structure = gst_buffer_pool_get_config (newpool);
1149 gst_buffer_pool_config_set (structure, caps, size, 2, 0, 0, 15);
1150 if (!gst_buffer_pool_set_config (newpool, structure))
1153 oldpool = ximagesink->pool;
1154 ximagesink->pool = newpool;
1156 /* unref the old sink */
1158 /* we don't deactivate, some elements might still be using it, it will be
1159 * deactivated when the last ref is gone */
1160 gst_object_unref (oldpool);
1162 g_mutex_unlock (ximagesink->flow_lock);
1169 GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1174 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1179 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1184 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1185 ("Invalid image size."));
1190 GST_ERROR_OBJECT (ximagesink, "failed to set config.");
1191 g_mutex_unlock (ximagesink->flow_lock);
1196 static GstStateChangeReturn
1197 gst_ximagesink_change_state (GstElement * element, GstStateChange transition)
1199 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1200 GstXImageSink *ximagesink;
1201 GstXContext *xcontext = NULL;
1203 ximagesink = GST_XIMAGESINK (element);
1205 switch (transition) {
1206 case GST_STATE_CHANGE_NULL_TO_READY:
1207 /* Initializing the XContext */
1208 if (ximagesink->xcontext == NULL) {
1209 xcontext = gst_ximagesink_xcontext_get (ximagesink);
1210 if (xcontext == NULL) {
1211 ret = GST_STATE_CHANGE_FAILURE;
1214 GST_OBJECT_LOCK (ximagesink);
1216 ximagesink->xcontext = xcontext;
1217 GST_OBJECT_UNLOCK (ximagesink);
1220 /* call XSynchronize with the current value of synchronous */
1221 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1222 ximagesink->synchronous ? "TRUE" : "FALSE");
1223 g_mutex_lock (ximagesink->x_lock);
1224 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1225 g_mutex_unlock (ximagesink->x_lock);
1226 gst_ximagesink_manage_event_thread (ximagesink);
1228 case GST_STATE_CHANGE_READY_TO_PAUSED:
1229 g_mutex_lock (ximagesink->flow_lock);
1230 if (ximagesink->xwindow)
1231 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1232 g_mutex_unlock (ximagesink->flow_lock);
1234 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1240 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1242 switch (transition) {
1243 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1245 case GST_STATE_CHANGE_PAUSED_TO_READY:
1246 ximagesink->fps_n = 0;
1247 ximagesink->fps_d = 1;
1248 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1249 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1250 g_mutex_lock (ximagesink->flow_lock);
1251 if (ximagesink->pool)
1252 gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1253 g_mutex_unlock (ximagesink->flow_lock);
1255 case GST_STATE_CHANGE_READY_TO_NULL:
1256 gst_ximagesink_reset (ximagesink);
1267 gst_ximagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1268 GstClockTime * start, GstClockTime * end)
1270 GstXImageSink *ximagesink;
1272 ximagesink = GST_XIMAGESINK (bsink);
1274 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1275 *start = GST_BUFFER_TIMESTAMP (buf);
1276 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1277 *end = *start + GST_BUFFER_DURATION (buf);
1279 if (ximagesink->fps_n > 0) {
1281 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1288 static GstFlowReturn
1289 gst_ximagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1292 GstXImageSink *ximagesink;
1293 GstXImageMeta *meta;
1294 GstBuffer *to_put = NULL;
1296 ximagesink = GST_XIMAGESINK (vsink);
1298 meta = gst_buffer_get_ximage_meta (buf);
1300 if (meta && meta->sink == ximagesink) {
1301 /* If this buffer has been allocated using our buffer management we simply
1302 put the ximage which is in the PRIVATE pointer */
1303 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1307 GstVideoFrame src, dest;
1309 /* Else we have to copy the data into our private image, */
1310 /* if we have one... */
1311 GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1313 /* we should have a pool, configured in setcaps */
1314 if (ximagesink->pool == NULL)
1317 if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1318 goto activate_failed;
1320 /* take a buffer form our pool */
1321 res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, NULL);
1322 if (res != GST_FLOW_OK)
1325 GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, ximagesink,
1326 "slow copy into bufferpool buffer %p", to_put);
1328 if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1329 goto invalid_buffer;
1331 if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1332 gst_video_frame_unmap (&src);
1333 goto invalid_buffer;
1336 gst_video_frame_copy (&dest, &src);
1338 gst_video_frame_unmap (&dest);
1339 gst_video_frame_unmap (&src);
1342 if (!gst_ximagesink_ximage_put (ximagesink, to_put))
1347 gst_buffer_unref (to_put);
1354 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1355 ("Internal error: can't allocate images"),
1356 ("We don't have a bufferpool negotiated"));
1357 return GST_FLOW_ERROR;
1361 /* No image available. That's very bad ! */
1362 GST_WARNING_OBJECT (ximagesink, "could not create image");
1367 /* No Window available to put our image into */
1368 GST_WARNING_OBJECT (ximagesink, "could map image");
1374 /* No Window available to put our image into */
1375 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1376 res = GST_FLOW_ERROR;
1381 GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1382 res = GST_FLOW_ERROR;
1388 gst_ximagesink_event (GstBaseSink * sink, GstEvent * event)
1390 GstXImageSink *ximagesink = GST_XIMAGESINK (sink);
1392 switch (GST_EVENT_TYPE (event)) {
1393 case GST_EVENT_TAG:{
1395 gchar *title = NULL;
1397 gst_event_parse_tag (event, &l);
1398 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1401 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1402 gst_ximagesink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1412 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1416 gst_ximagesink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1418 GstXImageSink *ximagesink = GST_XIMAGESINK (bsink);
1419 GstBufferPool *pool;
1420 GstStructure *config;
1425 gst_query_parse_allocation (query, &caps, &need_pool);
1430 g_mutex_lock (ximagesink->flow_lock);
1431 if ((pool = ximagesink->pool))
1432 gst_object_ref (pool);
1433 g_mutex_unlock (ximagesink->flow_lock);
1436 const GstCaps *pcaps;
1438 /* we had a pool, check caps */
1439 config = gst_buffer_pool_get_config (pool);
1440 gst_buffer_pool_config_get (config, &pcaps, &size, NULL, NULL, NULL, NULL);
1442 GST_DEBUG_OBJECT (ximagesink,
1443 "we had a pool with caps %" GST_PTR_FORMAT, pcaps);
1444 if (!gst_caps_is_equal (caps, pcaps)) {
1445 /* different caps, we can't use this pool */
1446 GST_DEBUG_OBJECT (ximagesink, "pool has different caps");
1447 gst_object_unref (pool);
1451 if (pool == NULL && need_pool) {
1454 GST_DEBUG_OBJECT (ximagesink, "create new pool");
1455 pool = gst_ximage_buffer_pool_new (ximagesink);
1457 if (!gst_video_info_from_caps (&info, caps))
1460 /* the normal size of a frame */
1463 config = gst_buffer_pool_get_config (pool);
1464 gst_buffer_pool_config_set (config, caps, size, 0, 0, 0, 0);
1465 if (!gst_buffer_pool_set_config (pool, config))
1468 /* we need at least 2 buffer because we hold on to the last one */
1469 gst_query_set_allocation_params (query, size, 2, 0, 0, 0, pool);
1471 /* we also support various metadata */
1472 gst_query_add_allocation_meta (query, GST_VIDEO_META_API);
1473 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API);
1475 gst_object_unref (pool);
1482 GST_DEBUG_OBJECT (bsink, "no caps specified");
1487 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1492 GST_DEBUG_OBJECT (bsink, "failed setting config");
1497 /* Interfaces stuff */
1499 gst_ximagesink_navigation_send_event (GstNavigation * navigation,
1500 GstStructure * structure)
1502 GstXImageSink *ximagesink = GST_XIMAGESINK (navigation);
1504 gint x_offset, y_offset;
1508 event = gst_event_new_navigation (structure);
1510 /* We are not converting the pointer coordinates as there's no hardware
1511 scaling done here. The only possible scaling is done by videoscale and
1512 videoscale will have to catch those events and tranform the coordinates
1513 to match the applied scaling. So here we just add the offset if the image
1514 is centered in the window. */
1516 /* We take the flow_lock while we look at the window */
1517 g_mutex_lock (ximagesink->flow_lock);
1519 if (!ximagesink->xwindow) {
1520 g_mutex_unlock (ximagesink->flow_lock);
1524 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1525 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1527 g_mutex_unlock (ximagesink->flow_lock);
1529 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1531 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1533 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1535 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1538 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
1540 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
1541 gst_pad_send_event (pad, event);
1543 gst_object_unref (pad);
1548 gst_ximagesink_navigation_init (GstNavigationInterface * iface)
1550 iface->send_event = gst_ximagesink_navigation_send_event;
1554 gst_ximagesink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1556 XID xwindow_id = id;
1557 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1558 GstXWindow *xwindow = NULL;
1559 XWindowAttributes attr;
1561 /* We acquire the stream lock while setting this window in the element.
1562 We are basically cleaning tons of stuff replacing the old window, putting
1563 images while we do that would surely crash */
1564 g_mutex_lock (ximagesink->flow_lock);
1566 /* If we already use that window return */
1567 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1568 g_mutex_unlock (ximagesink->flow_lock);
1572 /* If the element has not initialized the X11 context try to do so */
1573 if (!ximagesink->xcontext &&
1574 !(ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink))) {
1575 g_mutex_unlock (ximagesink->flow_lock);
1576 /* we have thrown a GST_ELEMENT_ERROR now */
1580 /* If a window is there already we destroy it */
1581 if (ximagesink->xwindow) {
1582 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1583 ximagesink->xwindow = NULL;
1586 /* If the xid is 0 we go back to an internal window */
1587 if (xwindow_id == 0) {
1588 /* If no width/height caps nego did not happen window will be created
1589 during caps nego then */
1590 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1591 xwindow = gst_ximagesink_xwindow_new (ximagesink,
1592 GST_VIDEO_SINK_WIDTH (ximagesink),
1593 GST_VIDEO_SINK_HEIGHT (ximagesink));
1596 xwindow = g_new0 (GstXWindow, 1);
1598 xwindow->win = xwindow_id;
1600 /* We get window geometry, set the event we want to receive,
1602 g_mutex_lock (ximagesink->x_lock);
1603 XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
1604 xwindow->width = attr.width;
1605 xwindow->height = attr.height;
1606 xwindow->internal = FALSE;
1607 if (ximagesink->handle_events) {
1608 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1609 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1613 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1614 g_mutex_unlock (ximagesink->x_lock);
1618 ximagesink->xwindow = xwindow;
1620 g_mutex_unlock (ximagesink->flow_lock);
1624 gst_ximagesink_expose (GstVideoOverlay * overlay)
1626 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1628 gst_ximagesink_xwindow_update_geometry (ximagesink);
1629 gst_ximagesink_ximage_put (ximagesink, NULL);
1633 gst_ximagesink_set_event_handling (GstVideoOverlay * overlay,
1634 gboolean handle_events)
1636 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1638 ximagesink->handle_events = handle_events;
1640 g_mutex_lock (ximagesink->flow_lock);
1642 if (G_UNLIKELY (!ximagesink->xwindow)) {
1643 g_mutex_unlock (ximagesink->flow_lock);
1647 g_mutex_lock (ximagesink->x_lock);
1649 if (handle_events) {
1650 if (ximagesink->xwindow->internal) {
1651 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1652 ExposureMask | StructureNotifyMask | PointerMotionMask |
1653 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1655 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1656 ExposureMask | StructureNotifyMask | PointerMotionMask |
1657 KeyPressMask | KeyReleaseMask);
1660 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1663 g_mutex_unlock (ximagesink->x_lock);
1665 g_mutex_unlock (ximagesink->flow_lock);
1669 gst_ximagesink_video_overlay_init (GstVideoOverlayInterface * iface)
1671 iface->set_window_handle = gst_ximagesink_set_window_handle;
1672 iface->expose = gst_ximagesink_expose;
1673 iface->handle_events = gst_ximagesink_set_event_handling;
1676 /* =========================================== */
1678 /* Init & Class init */
1680 /* =========================================== */
1683 gst_ximagesink_set_property (GObject * object, guint prop_id,
1684 const GValue * value, GParamSpec * pspec)
1686 GstXImageSink *ximagesink;
1688 g_return_if_fail (GST_IS_XIMAGESINK (object));
1690 ximagesink = GST_XIMAGESINK (object);
1694 ximagesink->display_name = g_strdup (g_value_get_string (value));
1696 case PROP_SYNCHRONOUS:
1697 ximagesink->synchronous = g_value_get_boolean (value);
1698 if (ximagesink->xcontext) {
1699 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1700 ximagesink->synchronous ? "TRUE" : "FALSE");
1701 g_mutex_lock (ximagesink->x_lock);
1702 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1703 g_mutex_unlock (ximagesink->x_lock);
1706 case PROP_FORCE_ASPECT_RATIO:
1707 ximagesink->keep_aspect = g_value_get_boolean (value);
1709 case PROP_PIXEL_ASPECT_RATIO:
1713 tmp = g_new0 (GValue, 1);
1714 g_value_init (tmp, GST_TYPE_FRACTION);
1716 if (!g_value_transform (value, tmp)) {
1717 GST_WARNING_OBJECT (ximagesink,
1718 "Could not transform string to aspect ratio");
1721 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1722 gst_value_get_fraction_numerator (tmp),
1723 gst_value_get_fraction_denominator (tmp));
1724 g_free (ximagesink->par);
1725 ximagesink->par = tmp;
1729 case PROP_HANDLE_EVENTS:
1730 gst_ximagesink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1731 g_value_get_boolean (value));
1732 gst_ximagesink_manage_event_thread (ximagesink);
1734 case PROP_HANDLE_EXPOSE:
1735 ximagesink->handle_expose = g_value_get_boolean (value);
1736 gst_ximagesink_manage_event_thread (ximagesink);
1739 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1745 gst_ximagesink_get_property (GObject * object, guint prop_id,
1746 GValue * value, GParamSpec * pspec)
1748 GstXImageSink *ximagesink;
1750 g_return_if_fail (GST_IS_XIMAGESINK (object));
1752 ximagesink = GST_XIMAGESINK (object);
1756 g_value_set_string (value, ximagesink->display_name);
1758 case PROP_SYNCHRONOUS:
1759 g_value_set_boolean (value, ximagesink->synchronous);
1761 case PROP_FORCE_ASPECT_RATIO:
1762 g_value_set_boolean (value, ximagesink->keep_aspect);
1764 case PROP_PIXEL_ASPECT_RATIO:
1765 if (ximagesink->par)
1766 g_value_transform (ximagesink->par, value);
1768 case PROP_HANDLE_EVENTS:
1769 g_value_set_boolean (value, ximagesink->handle_events);
1771 case PROP_HANDLE_EXPOSE:
1772 g_value_set_boolean (value, ximagesink->handle_expose);
1774 case PROP_WINDOW_WIDTH:
1775 if (ximagesink->xwindow)
1776 g_value_set_uint64 (value, ximagesink->xwindow->width);
1778 g_value_set_uint64 (value, 0);
1780 case PROP_WINDOW_HEIGHT:
1781 if (ximagesink->xwindow)
1782 g_value_set_uint64 (value, ximagesink->xwindow->height);
1784 g_value_set_uint64 (value, 0);
1787 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1793 gst_ximagesink_reset (GstXImageSink * ximagesink)
1797 GST_OBJECT_LOCK (ximagesink);
1798 ximagesink->running = FALSE;
1799 /* grab thread and mark it as NULL */
1800 thread = ximagesink->event_thread;
1801 ximagesink->event_thread = NULL;
1802 GST_OBJECT_UNLOCK (ximagesink);
1804 /* Wait for our event thread to finish before we clean up our stuff. */
1806 g_thread_join (thread);
1808 if (ximagesink->cur_image) {
1809 gst_buffer_unref (ximagesink->cur_image);
1810 ximagesink->cur_image = NULL;
1813 g_mutex_lock (ximagesink->flow_lock);
1815 if (ximagesink->pool) {
1816 gst_object_unref (ximagesink->pool);
1817 ximagesink->pool = NULL;
1820 if (ximagesink->xwindow) {
1821 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1822 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1823 ximagesink->xwindow = NULL;
1825 g_mutex_unlock (ximagesink->flow_lock);
1827 gst_ximagesink_xcontext_clear (ximagesink);
1831 gst_ximagesink_finalize (GObject * object)
1833 GstXImageSink *ximagesink;
1835 ximagesink = GST_XIMAGESINK (object);
1837 gst_ximagesink_reset (ximagesink);
1839 if (ximagesink->display_name) {
1840 g_free (ximagesink->display_name);
1841 ximagesink->display_name = NULL;
1843 if (ximagesink->par) {
1844 g_free (ximagesink->par);
1845 ximagesink->par = NULL;
1847 if (ximagesink->x_lock) {
1848 g_mutex_free (ximagesink->x_lock);
1849 ximagesink->x_lock = NULL;
1851 if (ximagesink->flow_lock) {
1852 g_mutex_free (ximagesink->flow_lock);
1853 ximagesink->flow_lock = NULL;
1856 g_free (ximagesink->media_title);
1858 G_OBJECT_CLASS (parent_class)->finalize (object);
1862 gst_ximagesink_init (GstXImageSink * ximagesink)
1864 ximagesink->display_name = NULL;
1865 ximagesink->xcontext = NULL;
1866 ximagesink->xwindow = NULL;
1867 ximagesink->cur_image = NULL;
1869 ximagesink->event_thread = NULL;
1870 ximagesink->running = FALSE;
1872 ximagesink->fps_n = 0;
1873 ximagesink->fps_d = 1;
1875 ximagesink->x_lock = g_mutex_new ();
1876 ximagesink->flow_lock = g_mutex_new ();
1878 ximagesink->par = NULL;
1880 ximagesink->pool = NULL;
1882 ximagesink->synchronous = FALSE;
1883 ximagesink->keep_aspect = FALSE;
1884 ximagesink->handle_events = TRUE;
1885 ximagesink->handle_expose = TRUE;
1889 gst_ximagesink_class_init (GstXImageSinkClass * klass)
1891 GObjectClass *gobject_class;
1892 GstElementClass *gstelement_class;
1893 GstBaseSinkClass *gstbasesink_class;
1894 GstVideoSinkClass *videosink_class;
1896 gobject_class = (GObjectClass *) klass;
1897 gstelement_class = (GstElementClass *) klass;
1898 gstbasesink_class = (GstBaseSinkClass *) klass;
1899 videosink_class = (GstVideoSinkClass *) klass;
1901 gobject_class->finalize = gst_ximagesink_finalize;
1902 gobject_class->set_property = gst_ximagesink_set_property;
1903 gobject_class->get_property = gst_ximagesink_get_property;
1905 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1906 g_param_spec_string ("display", "Display", "X Display name",
1907 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1908 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1909 g_param_spec_boolean ("synchronous", "Synchronous",
1910 "When enabled, runs the X display in synchronous mode. "
1911 "(unrelated to A/V sync, used only for debugging)", FALSE,
1912 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1913 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1914 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1915 "When enabled, reverse caps negotiation (scaling) will respect "
1916 "original aspect ratio", FALSE,
1917 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1918 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1919 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1920 "The pixel aspect ratio of the device", "1/1",
1921 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1922 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1923 g_param_spec_boolean ("handle-events", "Handle XEvents",
1924 "When enabled, XEvents will be selected and handled", TRUE,
1925 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1926 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1927 g_param_spec_boolean ("handle-expose", "Handle expose",
1929 "the current frame will always be drawn in response to X Expose "
1930 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1933 * GstXImageSink:window-width
1935 * Actual width of the video window.
1939 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1940 g_param_spec_uint64 ("window-width", "window-width",
1941 "Width of the window", 0, G_MAXUINT64, 0,
1942 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1945 * GstXImageSink:window-height
1947 * Actual height of the video window.
1951 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1952 g_param_spec_uint64 ("window-height", "window-height",
1953 "Height of the window", 0, G_MAXUINT64, 0,
1954 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1956 gst_element_class_set_details_simple (gstelement_class,
1957 "Video sink", "Sink/Video",
1958 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1960 gst_element_class_add_pad_template (gstelement_class,
1961 gst_static_pad_template_get (&gst_ximagesink_sink_template_factory));
1963 gstelement_class->change_state = gst_ximagesink_change_state;
1965 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_getcaps);
1966 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_setcaps);
1967 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_ximagesink_get_times);
1968 gstbasesink_class->propose_allocation =
1969 GST_DEBUG_FUNCPTR (gst_ximagesink_propose_allocation);
1970 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_ximagesink_event);
1972 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);