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., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, 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-1.0 -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-1.0 -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-1.0 -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
93 * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
94 * videotestsrc, in most cases the pixel aspect ratio of the display will be
95 * 1/1. This means that videoscale will have to do the scaling to convert
96 * incoming frames to a size that will match the display pixel aspect ratio
97 * (from 320x240 to 320x180 in this case). Note that you might have to escape
98 * some characters for your shell like '\(fraction\)'.
107 #include <gst/video/navigation.h>
108 #include <gst/video/videooverlay.h>
110 #include <gst/video/gstvideometa.h>
113 #include "ximagesink.h"
115 /* Debugging category */
116 #include <gst/gstinfo.h>
118 /* for XkbKeycodeToKeysym */
119 #include <X11/XKBlib.h>
121 GST_DEBUG_CATEGORY_EXTERN (gst_debug_x_image_sink);
122 GST_DEBUG_CATEGORY_EXTERN (GST_CAT_PERFORMANCE);
123 #define GST_CAT_DEFAULT gst_debug_x_image_sink
128 unsigned long functions;
129 unsigned long decorations;
131 unsigned long status;
133 MotifWmHints, MwmHints;
135 #define MWM_HINTS_DECORATIONS (1L << 1)
137 static void gst_x_image_sink_reset (GstXImageSink * ximagesink);
138 static void gst_x_image_sink_xwindow_update_geometry (GstXImageSink *
140 static void gst_x_image_sink_expose (GstVideoOverlay * overlay);
142 static GstStaticPadTemplate gst_x_image_sink_sink_template_factory =
143 GST_STATIC_PAD_TEMPLATE ("sink",
146 GST_STATIC_CAPS ("video/x-raw, "
147 "framerate = (fraction) [ 0, MAX ], "
148 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
156 PROP_PIXEL_ASPECT_RATIO,
157 PROP_FORCE_ASPECT_RATIO,
164 /* ============================================================= */
168 /* ============================================================= */
170 /* =========================================== */
172 /* Object typing & Creation */
174 /* =========================================== */
175 static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface);
176 static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface *
178 #define gst_x_image_sink_parent_class parent_class
179 G_DEFINE_TYPE_WITH_CODE (GstXImageSink, gst_x_image_sink, GST_TYPE_VIDEO_SINK,
180 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
181 gst_x_image_sink_navigation_init);
182 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
183 gst_x_image_sink_video_overlay_init));
185 /* ============================================================= */
187 /* Private Methods */
189 /* ============================================================= */
193 /* We are called with the x_lock taken */
195 gst_x_image_sink_xwindow_draw_borders (GstXImageSink * ximagesink,
196 GstXWindow * xwindow, GstVideoRectangle rect)
198 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
199 g_return_if_fail (xwindow != NULL);
201 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
202 ximagesink->xcontext->black);
206 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
207 0, 0, rect.x, xwindow->height);
211 if ((rect.x + rect.w) < xwindow->width) {
212 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
213 rect.x + rect.w, 0, xwindow->width, xwindow->height);
218 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
219 0, 0, xwindow->width, rect.y);
223 if ((rect.y + rect.h) < xwindow->height) {
224 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
225 0, rect.y + rect.h, xwindow->width, xwindow->height);
229 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
231 gst_x_image_sink_ximage_put (GstXImageSink * ximagesink, GstBuffer * ximage)
233 GstXImageMemory *mem;
234 GstVideoCropMeta *crop;
235 GstVideoRectangle src = { 0, };
236 GstVideoRectangle dst = { 0, };
237 GstVideoRectangle result;
238 gboolean draw_border = FALSE;
240 /* We take the flow_lock. If expose is in there we don't want to run
241 concurrently from the data flow thread */
242 g_mutex_lock (&ximagesink->flow_lock);
244 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
245 g_mutex_unlock (&ximagesink->flow_lock);
249 /* Draw borders when displaying the first frame. After this
250 draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
251 if (!ximagesink->cur_image || ximagesink->draw_border) {
255 /* Store a reference to the last image we put, lose the previous one */
256 if (ximage && ximagesink->cur_image != ximage) {
257 if (ximagesink->cur_image) {
258 GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
259 gst_buffer_unref (ximagesink->cur_image);
261 GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
262 ximagesink->cur_image = gst_buffer_ref (ximage);
265 /* Expose sends a NULL image, we take the latest frame */
268 if (ximagesink->cur_image) {
269 ximage = ximagesink->cur_image;
271 g_mutex_unlock (&ximagesink->flow_lock);
276 mem = (GstXImageMemory *) gst_buffer_peek_memory (ximage, 0);
277 crop = gst_buffer_get_video_crop_meta (ximage);
280 src.x = crop->x + mem->x;
281 src.y = crop->y + mem->y;
283 src.h = crop->height;
284 GST_LOG_OBJECT (ximagesink,
285 "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
292 dst.w = ximagesink->xwindow->width;
293 dst.h = ximagesink->xwindow->height;
295 gst_video_sink_center_rect (src, dst, &result, FALSE);
297 g_mutex_lock (&ximagesink->x_lock);
300 gst_x_image_sink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
302 ximagesink->draw_border = FALSE;
305 if (ximagesink->xcontext->use_xshm) {
306 GST_LOG_OBJECT (ximagesink,
307 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
308 ximage, 0, 0, result.x, result.y, result.w, result.h,
309 ximagesink->xwindow->width, ximagesink->xwindow->height);
310 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
311 ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
312 result.w, result.h, FALSE);
314 #endif /* HAVE_XSHM */
316 GST_LOG_OBJECT (ximagesink,
317 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
318 ximage, 0, 0, result.x, result.y, result.w, result.h,
319 ximagesink->xwindow->width, ximagesink->xwindow->height);
320 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
321 ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
325 XSync (ximagesink->xcontext->disp, FALSE);
327 g_mutex_unlock (&ximagesink->x_lock);
329 g_mutex_unlock (&ximagesink->flow_lock);
335 gst_x_image_sink_xwindow_decorate (GstXImageSink * ximagesink,
338 Atom hints_atom = None;
341 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), FALSE);
342 g_return_val_if_fail (window != NULL, FALSE);
344 g_mutex_lock (&ximagesink->x_lock);
346 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
348 if (hints_atom == None) {
349 g_mutex_unlock (&ximagesink->x_lock);
353 hints = g_malloc0 (sizeof (MotifWmHints));
355 hints->flags |= MWM_HINTS_DECORATIONS;
356 hints->decorations = 1 << 0;
358 XChangeProperty (ximagesink->xcontext->disp, window->win,
359 hints_atom, hints_atom, 32, PropModeReplace,
360 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
362 XSync (ximagesink->xcontext->disp, FALSE);
364 g_mutex_unlock (&ximagesink->x_lock);
372 gst_x_image_sink_xwindow_set_title (GstXImageSink * ximagesink,
373 GstXWindow * xwindow, const gchar * media_title)
376 g_free (ximagesink->media_title);
377 ximagesink->media_title = g_strdup (media_title);
380 /* we have a window */
381 if (xwindow->internal) {
382 XTextProperty xproperty;
383 XClassHint *hint = XAllocClassHint ();
384 const gchar *app_name;
385 const gchar *title = NULL;
386 gchar *title_mem = NULL;
388 /* set application name as a title */
389 app_name = g_get_application_name ();
391 if (app_name && ximagesink->media_title) {
392 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
394 } else if (app_name) {
396 } else if (ximagesink->media_title) {
397 title = ximagesink->media_title;
401 if ((XStringListToTextProperty (((char **) &title), 1,
403 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
404 XFree (xproperty.value);
411 hint->res_name = (char *) app_name;
412 hint->res_class = (char *) "GStreamer";
413 XSetClassHint (ximagesink->xcontext->disp, xwindow->win, hint);
420 /* This function handles a GstXWindow creation */
422 gst_x_image_sink_xwindow_new (GstXImageSink * ximagesink, gint width,
425 GstXWindow *xwindow = NULL;
428 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
430 xwindow = g_new0 (GstXWindow, 1);
432 xwindow->width = width;
433 xwindow->height = height;
434 xwindow->internal = TRUE;
436 g_mutex_lock (&ximagesink->x_lock);
438 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
439 ximagesink->xcontext->root,
440 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
442 /* We have to do that to prevent X from redrawing the background on
443 ConfigureNotify. This takes away flickering of video when resizing. */
444 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
446 /* set application name as a title */
447 gst_x_image_sink_xwindow_set_title (ximagesink, xwindow, NULL);
449 if (ximagesink->handle_events) {
452 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
453 StructureNotifyMask | PointerMotionMask | KeyPressMask |
454 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
456 /* Tell the window manager we'd like delete client messages instead of
458 wm_delete = XInternAtom (ximagesink->xcontext->disp,
459 "WM_DELETE_WINDOW", False);
460 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
464 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
467 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
469 XSync (ximagesink->xcontext->disp, FALSE);
471 g_mutex_unlock (&ximagesink->x_lock);
473 gst_x_image_sink_xwindow_decorate (ximagesink, xwindow);
475 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
481 /* This function destroys a GstXWindow */
483 gst_x_image_sink_xwindow_destroy (GstXImageSink * ximagesink,
484 GstXWindow * xwindow)
486 g_return_if_fail (xwindow != NULL);
487 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
489 g_mutex_lock (&ximagesink->x_lock);
491 /* If we did not create that window we just free the GC and let it live */
492 if (xwindow->internal)
493 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
495 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
497 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
499 XSync (ximagesink->xcontext->disp, FALSE);
501 g_mutex_unlock (&ximagesink->x_lock);
507 gst_x_image_sink_xwindow_update_geometry (GstXImageSink * ximagesink)
509 XWindowAttributes attr;
510 gboolean reconfigure;
512 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
514 /* Update the window geometry */
515 g_mutex_lock (&ximagesink->x_lock);
516 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
517 g_mutex_unlock (&ximagesink->x_lock);
521 XGetWindowAttributes (ximagesink->xcontext->disp,
522 ximagesink->xwindow->win, &attr);
524 /* Check if we would suggest a different width/height now */
525 reconfigure = (ximagesink->xwindow->width != attr.width)
526 || (ximagesink->xwindow->height != attr.height);
527 ximagesink->xwindow->width = attr.width;
528 ximagesink->xwindow->height = attr.height;
530 g_mutex_unlock (&ximagesink->x_lock);
533 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
534 gst_event_new_reconfigure ());
538 gst_x_image_sink_xwindow_clear (GstXImageSink * ximagesink,
539 GstXWindow * xwindow)
541 g_return_if_fail (xwindow != NULL);
542 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
544 g_mutex_lock (&ximagesink->x_lock);
546 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
547 ximagesink->xcontext->black);
549 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
550 0, 0, xwindow->width, xwindow->height);
552 XSync (ximagesink->xcontext->disp, FALSE);
554 g_mutex_unlock (&ximagesink->x_lock);
557 /* This function handles XEvents that might be in the queue. It generates
558 GstEvent that will be sent upstream in the pipeline to handle interactivity
561 gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
564 guint pointer_x = 0, pointer_y = 0;
565 gboolean pointer_moved = FALSE;
566 gboolean exposed = FALSE, configured = FALSE;
568 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
570 /* Then we get all pointer motion events, only the last position is
572 g_mutex_lock (&ximagesink->flow_lock);
573 g_mutex_lock (&ximagesink->x_lock);
574 while (XCheckWindowEvent (ximagesink->xcontext->disp,
575 ximagesink->xwindow->win, PointerMotionMask, &e)) {
576 g_mutex_unlock (&ximagesink->x_lock);
577 g_mutex_unlock (&ximagesink->flow_lock);
581 pointer_x = e.xmotion.x;
582 pointer_y = e.xmotion.y;
583 pointer_moved = TRUE;
588 g_mutex_lock (&ximagesink->flow_lock);
589 g_mutex_lock (&ximagesink->x_lock);
593 g_mutex_unlock (&ximagesink->x_lock);
594 g_mutex_unlock (&ximagesink->flow_lock);
596 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
597 pointer_x, pointer_y);
598 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
599 "mouse-move", 0, pointer_x, pointer_y);
601 g_mutex_lock (&ximagesink->flow_lock);
602 g_mutex_lock (&ximagesink->x_lock);
605 /* We get all remaining events on our window to throw them upstream */
606 while (XCheckWindowEvent (ximagesink->xcontext->disp,
607 ximagesink->xwindow->win,
608 KeyPressMask | KeyReleaseMask |
609 ButtonPressMask | ButtonReleaseMask, &e)) {
611 const char *key_str = NULL;
613 /* We lock only for the X function call */
614 g_mutex_unlock (&ximagesink->x_lock);
615 g_mutex_unlock (&ximagesink->flow_lock);
619 /* Mouse button pressed/released over our window. We send upstream
620 events for interactivity/navigation */
621 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
622 e.xbutton.button, e.xbutton.x, e.xbutton.x);
623 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
624 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
627 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
628 e.xbutton.button, e.xbutton.x, e.xbutton.x);
629 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
630 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
634 /* Key pressed/released over our window. We send upstream
635 events for interactivity/navigation */
636 g_mutex_lock (&ximagesink->x_lock);
637 keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
638 e.xkey.keycode, 0, 0);
639 if (keysym != NoSymbol) {
640 key_str = XKeysymToString (keysym);
644 g_mutex_unlock (&ximagesink->x_lock);
645 GST_DEBUG_OBJECT (ximagesink,
646 "key %d pressed over window at %d,%d (%s)",
647 e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
648 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
649 e.type == KeyPress ? "key-press" : "key-release", key_str);
652 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
655 g_mutex_lock (&ximagesink->flow_lock);
656 g_mutex_lock (&ximagesink->x_lock);
660 while (XCheckWindowEvent (ximagesink->xcontext->disp,
661 ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
666 case ConfigureNotify:
667 g_mutex_unlock (&ximagesink->x_lock);
668 gst_x_image_sink_xwindow_update_geometry (ximagesink);
669 g_mutex_lock (&ximagesink->x_lock);
677 if (ximagesink->handle_expose && (exposed || configured)) {
678 g_mutex_unlock (&ximagesink->x_lock);
679 g_mutex_unlock (&ximagesink->flow_lock);
681 gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
683 g_mutex_lock (&ximagesink->flow_lock);
684 g_mutex_lock (&ximagesink->x_lock);
687 /* Handle Display events */
688 while (XPending (ximagesink->xcontext->disp)) {
689 XNextEvent (ximagesink->xcontext->disp, &e);
695 wm_delete = XInternAtom (ximagesink->xcontext->disp,
696 "WM_DELETE_WINDOW", False);
697 if (wm_delete == (Atom) e.xclient.data.l[0]) {
698 /* Handle window deletion by posting an error on the bus */
699 GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
700 ("Output window was closed"), (NULL));
702 g_mutex_unlock (&ximagesink->x_lock);
703 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
704 ximagesink->xwindow = NULL;
705 g_mutex_lock (&ximagesink->x_lock);
714 g_mutex_unlock (&ximagesink->x_lock);
715 g_mutex_unlock (&ximagesink->flow_lock);
719 gst_x_image_sink_event_thread (GstXImageSink * ximagesink)
721 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
723 GST_OBJECT_LOCK (ximagesink);
724 while (ximagesink->running) {
725 GST_OBJECT_UNLOCK (ximagesink);
727 if (ximagesink->xwindow) {
728 gst_x_image_sink_handle_xevents (ximagesink);
730 /* FIXME: do we want to align this with the framerate or anything else? */
731 g_usleep (G_USEC_PER_SEC / 20);
733 GST_OBJECT_LOCK (ximagesink);
735 GST_OBJECT_UNLOCK (ximagesink);
741 gst_x_image_sink_manage_event_thread (GstXImageSink * ximagesink)
743 GThread *thread = NULL;
745 /* don't start the thread too early */
746 if (ximagesink->xcontext == NULL) {
750 GST_OBJECT_LOCK (ximagesink);
751 if (ximagesink->handle_expose || ximagesink->handle_events) {
752 if (!ximagesink->event_thread) {
753 /* Setup our event listening thread */
754 GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
755 ximagesink->handle_expose, ximagesink->handle_events);
756 ximagesink->running = TRUE;
757 ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
758 (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL);
761 if (ximagesink->event_thread) {
762 GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
763 ximagesink->handle_expose, ximagesink->handle_events);
764 ximagesink->running = FALSE;
765 /* grab thread and mark it as NULL */
766 thread = ximagesink->event_thread;
767 ximagesink->event_thread = NULL;
770 GST_OBJECT_UNLOCK (ximagesink);
772 /* Wait for our event thread to finish */
774 g_thread_join (thread);
779 /* This function calculates the pixel aspect ratio based on the properties
780 * in the xcontext structure and stores it there. */
782 gst_x_image_sink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
784 static const gint par[][2] = {
785 {1, 1}, /* regular screen */
786 {16, 15}, /* PAL TV */
787 {11, 10}, /* 525 line Rec.601 video */
788 {54, 59}, /* 625 line Rec.601 video */
789 {64, 45}, /* 1280x1024 on 16:9 display */
790 {5, 3}, /* 1280x1024 on 4:3 display */
791 {4, 3} /* 800x600 on 16:9 display */
798 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
800 /* first calculate the "real" ratio based on the X values;
801 * which is the "physical" w/h divided by the w/h in pixels of the display */
802 ratio = (gdouble) (xcontext->widthmm * xcontext->height)
803 / (xcontext->heightmm * xcontext->width);
805 /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
807 if (xcontext->width == 720 && xcontext->height == 576) {
808 ratio = 4.0 * 576 / (3.0 * 720);
810 GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
812 /* now find the one from par[][2] with the lowest delta to the real one */
816 for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
817 gdouble this_delta = DELTA (i);
819 if (this_delta < delta) {
825 GST_DEBUG ("Decided on index %d (%d/%d)", index,
826 par[index][0], par[index][1]);
828 g_free (xcontext->par);
829 xcontext->par = g_new0 (GValue, 1);
830 g_value_init (xcontext->par, GST_TYPE_FRACTION);
831 gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
832 GST_DEBUG ("set xcontext PAR to %d/%d",
833 gst_value_get_fraction_numerator (xcontext->par),
834 gst_value_get_fraction_denominator (xcontext->par));
837 /* This function gets the X Display and global info about it. Everything is
838 stored in our object and will be cleaned when the object is disposed. Note
839 here that caps for supported format are generated without any window or
842 gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
844 GstXContext *xcontext = NULL;
845 XPixmapFormatValues *px_formats = NULL;
846 gint nb_formats = 0, i;
848 GstVideoFormat vformat;
851 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
853 xcontext = g_new0 (GstXContext, 1);
855 g_mutex_lock (&ximagesink->x_lock);
857 xcontext->disp = XOpenDisplay (ximagesink->display_name);
859 if (!xcontext->disp) {
860 g_mutex_unlock (&ximagesink->x_lock);
862 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
863 ("Could not initialise X output"), ("Could not open display"));
867 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
868 xcontext->screen_num = DefaultScreen (xcontext->disp);
869 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
870 xcontext->root = DefaultRootWindow (xcontext->disp);
871 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
872 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
873 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
875 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
876 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
877 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
878 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
880 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
881 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
883 gst_x_image_sink_calculate_pixel_aspect_ratio (xcontext);
885 /* We get supported pixmap formats at supported depth */
886 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
889 XCloseDisplay (xcontext->disp);
890 g_mutex_unlock (&ximagesink->x_lock);
891 g_free (xcontext->par);
893 GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
894 ("Could not get supported pixmap formats"), (NULL));
898 /* We get bpp value corresponding to our running depth */
899 for (i = 0; i < nb_formats; i++) {
900 if (px_formats[i].depth == xcontext->depth)
901 xcontext->bpp = px_formats[i].bits_per_pixel;
906 endianness = (ImageByteOrder (xcontext->disp) ==
907 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
909 /* Search for XShm extension support */
911 if (XShmQueryExtension (xcontext->disp) &&
912 gst_x_image_sink_check_xshm_calls (ximagesink, xcontext)) {
913 xcontext->use_xshm = TRUE;
914 GST_DEBUG ("ximagesink is using XShm extension");
916 #endif /* HAVE_XSHM */
918 xcontext->use_xshm = FALSE;
919 GST_DEBUG ("ximagesink is not using XShm extension");
922 /* extrapolate alpha mask */
923 if (xcontext->depth == 32) {
924 alpha_mask = ~(xcontext->visual->red_mask
925 | xcontext->visual->green_mask | xcontext->visual->blue_mask);
931 gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness,
932 xcontext->visual->red_mask, xcontext->visual->green_mask,
933 xcontext->visual->blue_mask, alpha_mask);
935 if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
938 /* update object's par with calculated one if not set yet */
939 if (!ximagesink->par) {
940 ximagesink->par = g_new0 (GValue, 1);
941 gst_value_init_and_copy (ximagesink->par, xcontext->par);
942 GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
944 xcontext->caps = gst_caps_new_simple ("video/x-raw",
945 "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
946 "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
947 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
948 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
949 if (ximagesink->par) {
952 nom = gst_value_get_fraction_numerator (ximagesink->par);
953 den = gst_value_get_fraction_denominator (ximagesink->par);
954 gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
955 GST_TYPE_FRACTION, nom, den, NULL);
958 g_mutex_unlock (&ximagesink->x_lock);
965 GST_ERROR_OBJECT (ximagesink, "unknown format");
970 /* This function cleans the X context. Closing the Display and unrefing the
971 caps for supported formats. */
973 gst_x_image_sink_xcontext_clear (GstXImageSink * ximagesink)
975 GstXContext *xcontext;
977 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
979 GST_OBJECT_LOCK (ximagesink);
980 if (ximagesink->xcontext == NULL) {
981 GST_OBJECT_UNLOCK (ximagesink);
985 /* Take the xcontext reference and NULL it while we
986 * clean it up, so that any buffer-alloced buffers
987 * arriving after this will be freed correctly */
988 xcontext = ximagesink->xcontext;
989 ximagesink->xcontext = NULL;
991 GST_OBJECT_UNLOCK (ximagesink);
993 gst_caps_unref (xcontext->caps);
994 g_free (xcontext->par);
995 g_free (ximagesink->par);
996 ximagesink->par = NULL;
998 if (xcontext->last_caps)
999 gst_caps_replace (&xcontext->last_caps, NULL);
1001 g_mutex_lock (&ximagesink->x_lock);
1003 XCloseDisplay (xcontext->disp);
1005 g_mutex_unlock (&ximagesink->x_lock);
1013 gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1015 GstXImageSink *ximagesink;
1019 ximagesink = GST_X_IMAGE_SINK (bsink);
1021 g_mutex_lock (&ximagesink->x_lock);
1022 if (ximagesink->xcontext) {
1025 caps = gst_caps_ref (ximagesink->xcontext->caps);
1028 GstCaps *intersection;
1031 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1032 gst_caps_unref (caps);
1033 caps = intersection;
1036 if (gst_caps_is_empty (caps)) {
1037 g_mutex_unlock (&ximagesink->x_lock);
1041 if (ximagesink->xwindow && ximagesink->xwindow->width) {
1042 GstStructure *s0, *s1;
1044 caps = gst_caps_make_writable (caps);
1046 /* There can only be a single structure because the xcontext
1047 * caps only have a single structure */
1048 s0 = gst_caps_get_structure (caps, 0);
1049 s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1051 gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1052 "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1053 gst_caps_append_structure (caps, s1);
1055 /* This will not change the order but will remove the
1056 * fixed width/height caps again if not possible
1059 GstCaps *intersection;
1062 gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1063 gst_caps_unref (caps);
1064 caps = intersection;
1068 g_mutex_unlock (&ximagesink->x_lock);
1071 g_mutex_unlock (&ximagesink->x_lock);
1073 /* get a template copy and add the pixel aspect ratio */
1074 caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1075 if (ximagesink->par) {
1076 caps = gst_caps_make_writable (caps);
1077 for (i = 0; i < gst_caps_get_size (caps); ++i) {
1078 GstStructure *structure = gst_caps_get_structure (caps, i);
1081 nom = gst_value_get_fraction_numerator (ximagesink->par);
1082 den = gst_value_get_fraction_denominator (ximagesink->par);
1083 gst_structure_set (structure, "pixel-aspect-ratio",
1084 GST_TYPE_FRACTION, nom, den, NULL);
1089 GstCaps *intersection;
1092 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1093 gst_caps_unref (caps);
1094 caps = intersection;
1100 static GstBufferPool *
1101 gst_x_image_sink_create_pool (GstXImageSink * ximagesink, GstCaps * caps,
1102 gsize size, gint min)
1104 static GstAllocationParams params = { 0, 15, 0, 0, };
1105 GstBufferPool *pool;
1106 GstStructure *config;
1108 /* create a new pool for the new configuration */
1109 pool = gst_ximage_buffer_pool_new (ximagesink);
1111 config = gst_buffer_pool_get_config (pool);
1112 gst_buffer_pool_config_set_params (config, caps, size, min, 0);
1113 gst_buffer_pool_config_set_allocator (config, NULL, ¶ms);
1115 if (!gst_buffer_pool_set_config (pool, config))
1122 GST_WARNING_OBJECT (ximagesink, "failed setting config");
1123 gst_object_unref (pool);
1129 gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1131 GstXImageSink *ximagesink;
1132 GstStructure *structure;
1134 GstBufferPool *newpool, *oldpool;
1137 ximagesink = GST_X_IMAGE_SINK (bsink);
1139 if (!ximagesink->xcontext)
1142 GST_DEBUG_OBJECT (ximagesink,
1143 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1144 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1146 /* We intersect those caps with our template to make sure they are correct */
1147 if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1148 goto incompatible_caps;
1150 if (!gst_video_info_from_caps (&info, caps))
1151 goto invalid_format;
1153 structure = gst_caps_get_structure (caps, 0);
1154 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1155 * otherwise linking should fail */
1156 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1158 if (ximagesink->par) {
1159 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1162 } else if (ximagesink->xcontext->par) {
1163 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1169 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1170 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1171 ximagesink->fps_n = info.fps_n;
1172 ximagesink->fps_d = info.fps_d;
1174 /* Notify application to set xwindow id now */
1175 g_mutex_lock (&ximagesink->flow_lock);
1176 if (!ximagesink->xwindow) {
1177 g_mutex_unlock (&ximagesink->flow_lock);
1178 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1180 g_mutex_unlock (&ximagesink->flow_lock);
1183 /* Creating our window and our image */
1184 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1185 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1188 g_mutex_lock (&ximagesink->flow_lock);
1189 if (!ximagesink->xwindow) {
1190 ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1191 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1194 ximagesink->info = info;
1196 /* Remember to draw borders for next frame */
1197 ximagesink->draw_border = TRUE;
1199 /* create a new internal pool for the new configuration */
1200 newpool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 2);
1202 /* we don't activate the internal pool yet as it may not be needed */
1203 oldpool = ximagesink->pool;
1204 ximagesink->pool = newpool;
1205 g_mutex_unlock (&ximagesink->flow_lock);
1207 /* deactivate and unref the old internal pool */
1209 gst_buffer_pool_set_active (oldpool, FALSE);
1210 gst_object_unref (oldpool);
1218 GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1223 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1228 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1233 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1234 ("Invalid image size."));
1239 static GstStateChangeReturn
1240 gst_x_image_sink_change_state (GstElement * element, GstStateChange transition)
1242 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1243 GstXImageSink *ximagesink;
1244 GstXContext *xcontext = NULL;
1246 ximagesink = GST_X_IMAGE_SINK (element);
1248 switch (transition) {
1249 case GST_STATE_CHANGE_NULL_TO_READY:
1250 /* Initializing the XContext */
1251 if (ximagesink->xcontext == NULL) {
1252 xcontext = gst_x_image_sink_xcontext_get (ximagesink);
1253 if (xcontext == NULL) {
1254 ret = GST_STATE_CHANGE_FAILURE;
1257 GST_OBJECT_LOCK (ximagesink);
1259 ximagesink->xcontext = xcontext;
1260 GST_OBJECT_UNLOCK (ximagesink);
1263 /* call XSynchronize with the current value of synchronous */
1264 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1265 ximagesink->synchronous ? "TRUE" : "FALSE");
1266 g_mutex_lock (&ximagesink->x_lock);
1267 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1268 g_mutex_unlock (&ximagesink->x_lock);
1269 gst_x_image_sink_manage_event_thread (ximagesink);
1271 case GST_STATE_CHANGE_READY_TO_PAUSED:
1272 g_mutex_lock (&ximagesink->flow_lock);
1273 if (ximagesink->xwindow)
1274 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1275 g_mutex_unlock (&ximagesink->flow_lock);
1277 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1283 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1285 switch (transition) {
1286 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1288 case GST_STATE_CHANGE_PAUSED_TO_READY:
1289 ximagesink->fps_n = 0;
1290 ximagesink->fps_d = 1;
1291 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1292 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1293 g_mutex_lock (&ximagesink->flow_lock);
1294 if (ximagesink->pool)
1295 gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1296 g_mutex_unlock (&ximagesink->flow_lock);
1298 case GST_STATE_CHANGE_READY_TO_NULL:
1299 gst_x_image_sink_reset (ximagesink);
1310 gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1311 GstClockTime * start, GstClockTime * end)
1313 GstXImageSink *ximagesink;
1315 ximagesink = GST_X_IMAGE_SINK (bsink);
1317 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1318 *start = GST_BUFFER_TIMESTAMP (buf);
1319 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1320 *end = *start + GST_BUFFER_DURATION (buf);
1322 if (ximagesink->fps_n > 0) {
1324 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1331 static GstFlowReturn
1332 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1335 GstXImageSink *ximagesink;
1336 GstXImageMemory *mem;
1337 GstBuffer *to_put = NULL;
1339 ximagesink = GST_X_IMAGE_SINK (vsink);
1341 if (gst_buffer_n_memory (buf) == 1
1342 && (mem = (GstXImageMemory *) gst_buffer_peek_memory (buf, 0))
1343 && g_strcmp0 (mem->parent.allocator->mem_type, "ximage") == 0
1344 && mem->sink == ximagesink) {
1345 /* If this buffer has been allocated using our buffer management we simply
1346 put the ximage which is in the PRIVATE pointer */
1347 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1351 GstVideoFrame src, dest;
1352 GstBufferPoolAcquireParams params = { 0, };
1354 /* Else we have to copy the data into our private image, */
1355 /* if we have one... */
1356 GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1358 /* an internal pool should have been created in setcaps */
1359 if (G_UNLIKELY (ximagesink->pool == NULL))
1362 if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1363 goto activate_failed;
1365 /* take a buffer from our pool, if there is no buffer in the pool something
1366 * is seriously wrong, waiting for the pool here might deadlock when we try
1367 * to go to PAUSED because we never flush the pool. */
1368 params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
1369 res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, ¶ms);
1370 if (res != GST_FLOW_OK)
1373 GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, ximagesink,
1374 "slow copy into bufferpool buffer %p", to_put);
1376 if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1377 goto invalid_buffer;
1379 if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1380 gst_video_frame_unmap (&src);
1381 goto invalid_buffer;
1384 gst_video_frame_copy (&dest, &src);
1386 gst_video_frame_unmap (&dest);
1387 gst_video_frame_unmap (&src);
1390 if (!gst_x_image_sink_ximage_put (ximagesink, to_put))
1395 gst_buffer_unref (to_put);
1402 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1403 ("Internal error: can't allocate images"),
1404 ("We don't have a bufferpool negotiated"));
1405 return GST_FLOW_ERROR;
1409 /* No image available. That's very bad ! */
1410 GST_WARNING_OBJECT (ximagesink, "could not create image");
1415 /* No Window available to put our image into */
1416 GST_WARNING_OBJECT (ximagesink, "could not map image");
1422 /* No Window available to put our image into */
1423 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1424 res = GST_FLOW_ERROR;
1429 GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1430 res = GST_FLOW_ERROR;
1436 gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event)
1438 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (sink);
1440 switch (GST_EVENT_TYPE (event)) {
1441 case GST_EVENT_TAG:{
1443 gchar *title = NULL;
1445 gst_event_parse_tag (event, &l);
1446 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1449 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1450 gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1460 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1464 gst_x_image_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1466 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (bsink);
1467 GstBufferPool *pool = NULL;
1472 gst_query_parse_allocation (query, &caps, &need_pool);
1480 if (!gst_video_info_from_caps (&info, caps))
1483 pool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 0);
1485 /* the normal size of a frame */
1493 /* we need at least 2 buffer because we hold on to the last one */
1494 gst_query_add_allocation_pool (query, pool, size, 2, 0);
1495 gst_object_unref (pool);
1498 /* we also support various metadata */
1499 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
1500 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
1507 GST_DEBUG_OBJECT (bsink, "no caps specified");
1512 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1517 /* Already warned in create_pool */
1522 /* Interfaces stuff */
1524 gst_x_image_sink_navigation_send_event (GstNavigation * navigation,
1525 GstStructure * structure)
1527 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation);
1528 GstEvent *event = NULL;
1529 gint x_offset, y_offset;
1531 gboolean handled = FALSE;
1533 /* We are not converting the pointer coordinates as there's no hardware
1534 scaling done here. The only possible scaling is done by videoscale and
1535 videoscale will have to catch those events and tranform the coordinates
1536 to match the applied scaling. So here we just add the offset if the image
1537 is centered in the window. */
1539 /* We take the flow_lock while we look at the window */
1540 g_mutex_lock (&ximagesink->flow_lock);
1542 if (!ximagesink->xwindow) {
1543 g_mutex_unlock (&ximagesink->flow_lock);
1547 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1548 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1550 g_mutex_unlock (&ximagesink->flow_lock);
1552 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1554 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1556 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1558 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1561 event = gst_event_new_navigation (structure);
1563 gst_event_ref (event);
1564 handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event);
1567 gst_element_post_message (GST_ELEMENT_CAST (ximagesink),
1568 gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink),
1571 gst_event_unref (event);
1576 gst_x_image_sink_navigation_init (GstNavigationInterface * iface)
1578 iface->send_event = gst_x_image_sink_navigation_send_event;
1582 gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1584 XID xwindow_id = id;
1585 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1586 GstXWindow *xwindow = NULL;
1587 XWindowAttributes attr;
1589 /* We acquire the stream lock while setting this window in the element.
1590 We are basically cleaning tons of stuff replacing the old window, putting
1591 images while we do that would surely crash */
1592 g_mutex_lock (&ximagesink->flow_lock);
1594 /* If we already use that window return */
1595 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1596 g_mutex_unlock (&ximagesink->flow_lock);
1600 /* If the element has not initialized the X11 context try to do so */
1601 if (!ximagesink->xcontext &&
1602 !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) {
1603 g_mutex_unlock (&ximagesink->flow_lock);
1604 /* we have thrown a GST_ELEMENT_ERROR now */
1608 /* If a window is there already we destroy it */
1609 if (ximagesink->xwindow) {
1610 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1611 ximagesink->xwindow = NULL;
1614 /* If the xid is 0 we go back to an internal window */
1615 if (xwindow_id == 0) {
1616 /* If no width/height caps nego did not happen window will be created
1617 during caps nego then */
1618 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1619 xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1620 GST_VIDEO_SINK_WIDTH (ximagesink),
1621 GST_VIDEO_SINK_HEIGHT (ximagesink));
1624 xwindow = g_new0 (GstXWindow, 1);
1626 xwindow->win = xwindow_id;
1628 /* We get window geometry, set the event we want to receive,
1630 g_mutex_lock (&ximagesink->x_lock);
1631 XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
1632 xwindow->width = attr.width;
1633 xwindow->height = attr.height;
1634 xwindow->internal = FALSE;
1635 if (ximagesink->handle_events) {
1636 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1637 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1641 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1642 g_mutex_unlock (&ximagesink->x_lock);
1646 ximagesink->xwindow = xwindow;
1648 g_mutex_unlock (&ximagesink->flow_lock);
1652 gst_x_image_sink_expose (GstVideoOverlay * overlay)
1654 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1656 gst_x_image_sink_xwindow_update_geometry (ximagesink);
1657 gst_x_image_sink_ximage_put (ximagesink, NULL);
1661 gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay,
1662 gboolean handle_events)
1664 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1666 ximagesink->handle_events = handle_events;
1668 g_mutex_lock (&ximagesink->flow_lock);
1670 if (G_UNLIKELY (!ximagesink->xwindow)) {
1671 g_mutex_unlock (&ximagesink->flow_lock);
1675 g_mutex_lock (&ximagesink->x_lock);
1677 if (handle_events) {
1678 if (ximagesink->xwindow->internal) {
1679 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1680 ExposureMask | StructureNotifyMask | PointerMotionMask |
1681 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1683 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1684 ExposureMask | StructureNotifyMask | PointerMotionMask |
1685 KeyPressMask | KeyReleaseMask);
1688 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1691 g_mutex_unlock (&ximagesink->x_lock);
1693 g_mutex_unlock (&ximagesink->flow_lock);
1697 gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface)
1699 iface->set_window_handle = gst_x_image_sink_set_window_handle;
1700 iface->expose = gst_x_image_sink_expose;
1701 iface->handle_events = gst_x_image_sink_set_event_handling;
1704 /* =========================================== */
1706 /* Init & Class init */
1708 /* =========================================== */
1711 gst_x_image_sink_set_property (GObject * object, guint prop_id,
1712 const GValue * value, GParamSpec * pspec)
1714 GstXImageSink *ximagesink;
1716 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1718 ximagesink = GST_X_IMAGE_SINK (object);
1722 ximagesink->display_name = g_strdup (g_value_get_string (value));
1724 case PROP_SYNCHRONOUS:
1725 ximagesink->synchronous = g_value_get_boolean (value);
1726 if (ximagesink->xcontext) {
1727 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1728 ximagesink->synchronous ? "TRUE" : "FALSE");
1729 g_mutex_lock (&ximagesink->x_lock);
1730 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1731 g_mutex_unlock (&ximagesink->x_lock);
1734 case PROP_FORCE_ASPECT_RATIO:
1735 ximagesink->keep_aspect = g_value_get_boolean (value);
1737 case PROP_PIXEL_ASPECT_RATIO:
1741 tmp = g_new0 (GValue, 1);
1742 g_value_init (tmp, GST_TYPE_FRACTION);
1744 if (!g_value_transform (value, tmp)) {
1745 GST_WARNING_OBJECT (ximagesink,
1746 "Could not transform string to aspect ratio");
1749 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1750 gst_value_get_fraction_numerator (tmp),
1751 gst_value_get_fraction_denominator (tmp));
1752 g_free (ximagesink->par);
1753 ximagesink->par = tmp;
1757 case PROP_HANDLE_EVENTS:
1758 gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1759 g_value_get_boolean (value));
1760 gst_x_image_sink_manage_event_thread (ximagesink);
1762 case PROP_HANDLE_EXPOSE:
1763 ximagesink->handle_expose = g_value_get_boolean (value);
1764 gst_x_image_sink_manage_event_thread (ximagesink);
1767 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1773 gst_x_image_sink_get_property (GObject * object, guint prop_id,
1774 GValue * value, GParamSpec * pspec)
1776 GstXImageSink *ximagesink;
1778 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1780 ximagesink = GST_X_IMAGE_SINK (object);
1784 g_value_set_string (value, ximagesink->display_name);
1786 case PROP_SYNCHRONOUS:
1787 g_value_set_boolean (value, ximagesink->synchronous);
1789 case PROP_FORCE_ASPECT_RATIO:
1790 g_value_set_boolean (value, ximagesink->keep_aspect);
1792 case PROP_PIXEL_ASPECT_RATIO:
1793 if (ximagesink->par)
1794 g_value_transform (ximagesink->par, value);
1796 case PROP_HANDLE_EVENTS:
1797 g_value_set_boolean (value, ximagesink->handle_events);
1799 case PROP_HANDLE_EXPOSE:
1800 g_value_set_boolean (value, ximagesink->handle_expose);
1802 case PROP_WINDOW_WIDTH:
1803 if (ximagesink->xwindow)
1804 g_value_set_uint64 (value, ximagesink->xwindow->width);
1806 g_value_set_uint64 (value, 0);
1808 case PROP_WINDOW_HEIGHT:
1809 if (ximagesink->xwindow)
1810 g_value_set_uint64 (value, ximagesink->xwindow->height);
1812 g_value_set_uint64 (value, 0);
1815 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1821 gst_x_image_sink_reset (GstXImageSink * ximagesink)
1825 GST_OBJECT_LOCK (ximagesink);
1826 ximagesink->running = FALSE;
1827 /* grab thread and mark it as NULL */
1828 thread = ximagesink->event_thread;
1829 ximagesink->event_thread = NULL;
1830 GST_OBJECT_UNLOCK (ximagesink);
1832 /* Wait for our event thread to finish before we clean up our stuff. */
1834 g_thread_join (thread);
1836 if (ximagesink->cur_image) {
1837 gst_buffer_unref (ximagesink->cur_image);
1838 ximagesink->cur_image = NULL;
1841 g_mutex_lock (&ximagesink->flow_lock);
1843 if (ximagesink->pool) {
1844 gst_object_unref (ximagesink->pool);
1845 ximagesink->pool = NULL;
1848 if (ximagesink->xwindow) {
1849 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1850 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1851 ximagesink->xwindow = NULL;
1853 g_mutex_unlock (&ximagesink->flow_lock);
1855 gst_x_image_sink_xcontext_clear (ximagesink);
1859 gst_x_image_sink_finalize (GObject * object)
1861 GstXImageSink *ximagesink;
1863 ximagesink = GST_X_IMAGE_SINK (object);
1865 gst_x_image_sink_reset (ximagesink);
1867 if (ximagesink->display_name) {
1868 g_free (ximagesink->display_name);
1869 ximagesink->display_name = NULL;
1871 if (ximagesink->par) {
1872 g_free (ximagesink->par);
1873 ximagesink->par = NULL;
1875 g_mutex_clear (&ximagesink->x_lock);
1876 g_mutex_clear (&ximagesink->flow_lock);
1878 g_free (ximagesink->media_title);
1880 G_OBJECT_CLASS (parent_class)->finalize (object);
1884 gst_x_image_sink_init (GstXImageSink * ximagesink)
1886 ximagesink->display_name = NULL;
1887 ximagesink->xcontext = NULL;
1888 ximagesink->xwindow = NULL;
1889 ximagesink->cur_image = NULL;
1891 ximagesink->event_thread = NULL;
1892 ximagesink->running = FALSE;
1894 ximagesink->fps_n = 0;
1895 ximagesink->fps_d = 1;
1897 g_mutex_init (&ximagesink->x_lock);
1898 g_mutex_init (&ximagesink->flow_lock);
1900 ximagesink->par = NULL;
1902 ximagesink->pool = NULL;
1904 ximagesink->synchronous = FALSE;
1905 ximagesink->keep_aspect = TRUE;
1906 ximagesink->handle_events = TRUE;
1907 ximagesink->handle_expose = TRUE;
1911 gst_x_image_sink_class_init (GstXImageSinkClass * klass)
1913 GObjectClass *gobject_class;
1914 GstElementClass *gstelement_class;
1915 GstBaseSinkClass *gstbasesink_class;
1916 GstVideoSinkClass *videosink_class;
1918 gobject_class = (GObjectClass *) klass;
1919 gstelement_class = (GstElementClass *) klass;
1920 gstbasesink_class = (GstBaseSinkClass *) klass;
1921 videosink_class = (GstVideoSinkClass *) klass;
1923 gobject_class->finalize = gst_x_image_sink_finalize;
1924 gobject_class->set_property = gst_x_image_sink_set_property;
1925 gobject_class->get_property = gst_x_image_sink_get_property;
1927 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1928 g_param_spec_string ("display", "Display", "X Display name",
1929 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1930 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1931 g_param_spec_boolean ("synchronous", "Synchronous",
1932 "When enabled, runs the X display in synchronous mode. "
1933 "(unrelated to A/V sync, used only for debugging)", FALSE,
1934 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1935 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1936 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1937 "When enabled, reverse caps negotiation (scaling) will respect "
1938 "original aspect ratio", TRUE,
1939 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1940 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1941 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1942 "The pixel aspect ratio of the device", "1/1",
1943 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1944 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1945 g_param_spec_boolean ("handle-events", "Handle XEvents",
1946 "When enabled, XEvents will be selected and handled", TRUE,
1947 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1948 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1949 g_param_spec_boolean ("handle-expose", "Handle expose",
1951 "the current frame will always be drawn in response to X Expose "
1952 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1955 * GstXImageSink:window-width
1957 * Actual width of the video window.
1959 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1960 g_param_spec_uint64 ("window-width", "window-width",
1961 "Width of the window", 0, G_MAXUINT64, 0,
1962 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1965 * GstXImageSink:window-height
1967 * Actual height of the video window.
1969 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1970 g_param_spec_uint64 ("window-height", "window-height",
1971 "Height of the window", 0, G_MAXUINT64, 0,
1972 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1974 gst_element_class_set_static_metadata (gstelement_class,
1975 "Video sink", "Sink/Video",
1976 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
1978 gst_element_class_add_pad_template (gstelement_class,
1979 gst_static_pad_template_get (&gst_x_image_sink_sink_template_factory));
1981 gstelement_class->change_state = gst_x_image_sink_change_state;
1983 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps);
1984 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps);
1985 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times);
1986 gstbasesink_class->propose_allocation =
1987 GST_DEBUG_FUNCPTR (gst_x_image_sink_propose_allocation);
1988 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event);
1990 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame);