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
25 * XImageSink renders video frames to a drawable (XWindow) on a local or remote
26 * display. This element can receive a Window ID from the application through
27 * the XOverlay interface and will then render video frames in this drawable.
28 * If no Window ID was provided by the application, the element will create its
29 * own internal window and render into it.
31 * <title>Scaling</title>
33 * As standard XImage rendering to a drawable is not scaled, XImageSink will use
34 * reverse caps negotiation to try to get scaled video frames for the drawable.
35 * This is accomplished by asking the peer pad if it accepts some different caps
36 * which in most cases implies that there is a scaling element in the pipeline,
37 * or that an element generating the video frames can generate them with a
38 * different geometry. This mechanism is handled during buffer allocations, for
39 * each allocation request the video sink will check the drawable geometry, look
41 * <link linkend="GstXImageSink--force-aspect-ratio">force-aspect-ratio</link>
42 * property, calculate the geometry of desired video frames and then check that
43 * the peer pad accept those new caps. If it does it will then allocate a buffer
44 * in video memory with this new 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.
58 * <title>Pixel aspect ratio</title>
60 * When changing state to GST_STATE_READY, XImageSink will open a connection to
61 * the display specified in the
62 * <link linkend="GstXImageSink--display">display</link> property or the default
63 * display if nothing specified. Once this connection is open it will inspect
64 * the display configuration including the physical display geometry and
65 * then calculate the pixel aspect ratio. When caps negotiation will occur, the
66 * video sink will set the calculated pixel aspect ratio on the caps to make
67 * sure that incoming video frames will have the correct pixel aspect ratio for
68 * this display. Sometimes the calculated pixel aspect ratio can be wrong, it is
69 * then possible to enforce a specific pixel aspect ratio using the
70 * <link linkend="GstXImageSink--pixel-aspect-ratio">pixel-aspect-ratio</link>
73 * <title>Examples</title>
75 * Here is a simple pipeline to test reverse negotiation :
77 * gst-launch -v videotestsrc ! queue ! ximagesink
79 * When the test video signal appears you can resize the window and see that
80 * scaled buffers of the desired size are going to arrive with a short delay.
81 * This illustrates how buffers of desired size are allocated along the way.
82 * If you take away the queue, scaling will happen almost immediately.
85 * Here is a simple pipeline to test navigation events :
87 * gst-launch -v videotestsrc ! navigationtest ! ffmpegcolorspace ! ximagesink
89 * While moving the mouse pointer over the test signal you will see a black box
90 * following the mouse pointer. If you press the mouse button somewhere on the
91 * video and release it somewhere else a green box will appear where you pressed
92 * the button and a red one where you released it. (The navigationtest element
93 * is part of gst-plugins-good.)
96 * Here is a simple pipeline to test pixel aspect ratio :
98 * gst-launch -v videotestsrc ! video/x-raw-rgb, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
100 * This is faking a 4/3 pixel aspect ratio caps on video frames produced by
101 * videotestsrc, in most cases the pixel aspect ratio of the display will be
102 * 1/1. This means that videoscale will have to do the scaling to convert
103 * incoming frames to a size that will match the display pixel aspect ratio
104 * (from 320x240 to 320x180 in this case). Note that you might have to escape
105 * some characters for your shell like '\(fraction\)'.
115 #include <gst/interfaces/navigation.h>
116 #include <gst/interfaces/xoverlay.h>
119 #include "ximagesink.h"
121 /* Debugging category */
122 #include <gst/gstinfo.h>
124 GST_DEBUG_CATEGORY_EXTERN (gst_debug_ximagesink);
125 #define GST_CAT_DEFAULT gst_debug_ximagesink
130 unsigned long functions;
131 unsigned long decorations;
133 unsigned long status;
135 MotifWmHints, MwmHints;
137 #define MWM_HINTS_DECORATIONS (1L << 1)
139 static void gst_ximagesink_ximage_destroy (GstXImageSink * ximagesink,
140 GstXImageBuffer * ximage);
141 static void gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink,
142 GstXWindow * xwindow);
143 static void gst_ximagesink_expose (GstXOverlay * overlay);
145 /* ElementFactory information */
146 static const GstElementDetails gst_ximagesink_details =
147 GST_ELEMENT_DETAILS ("Video sink",
149 "A standard X based videosink",
150 "Julien Moutte <julien@moutte.net>");
152 static GstStaticPadTemplate gst_ximagesink_sink_template_factory =
153 GST_STATIC_PAD_TEMPLATE ("sink",
156 GST_STATIC_CAPS ("video/x-raw-rgb, "
157 "framerate = (fraction) [ 0, MAX ], "
158 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
166 PROP_PIXEL_ASPECT_RATIO,
167 PROP_FORCE_ASPECT_RATIO
171 static GstVideoSinkClass *parent_class = NULL;
173 /* ============================================================= */
175 /* Private Methods */
177 /* ============================================================= */
181 #define GST_TYPE_XIMAGE_BUFFER (gst_ximage_buffer_get_type())
183 #define GST_IS_XIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_XIMAGE_BUFFER))
184 #define GST_XIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_XIMAGE_BUFFER, GstXImageBuffer))
185 #define GST_XIMAGE_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_XIMAGE_BUFFER, GstXImageBufferClass))
187 /* So some words about GstMiniObject, this is pretty messy...
188 GstMiniObject does not use the standard finalizing of GObjects, you are
189 supposed to call gst_buffer_unref that's going to call gst_mini_objec_unref
190 which will handle its own refcount system and call gst_mini_object_free.
191 gst_mini_object_free will call the class finalize method which is not the
192 one from GObject, after calling this finalize method it will free the object
193 instance for you if the refcount is still 0 so you should not chain up */
195 gst_ximage_buffer_finalize (GstXImageBuffer * ximage)
197 GstXImageSink *ximagesink = NULL;
198 gboolean recycled = FALSE;
200 g_return_if_fail (ximage != NULL);
202 ximagesink = ximage->ximagesink;
204 GST_WARNING_OBJECT (ximagesink, "no sink found");
208 /* If our geometry changed we can't reuse that image. */
209 if ((ximage->width != GST_VIDEO_SINK_WIDTH (ximagesink)) ||
210 (ximage->height != GST_VIDEO_SINK_HEIGHT (ximagesink))) {
211 GST_DEBUG_OBJECT (ximagesink,
212 "destroy image %p as its size changed %dx%d vs current %dx%d",
213 ximage, ximage->width, ximage->height,
214 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
215 gst_ximagesink_ximage_destroy (ximagesink, ximage);
217 /* In that case we can reuse the image and add it to our image pool. */
218 GST_LOG_OBJECT (ximagesink, "recycling image %p in pool", ximage);
219 /* need to increment the refcount again to recycle */
220 gst_buffer_ref (GST_BUFFER (ximage));
221 g_mutex_lock (ximagesink->pool_lock);
222 ximagesink->buffer_pool = g_slist_prepend (ximagesink->buffer_pool, ximage);
223 g_mutex_unlock (ximagesink->pool_lock);
232 gst_ximage_buffer_free (GstXImageBuffer * ximage)
234 /* make sure it is not recycled */
237 gst_buffer_unref (GST_BUFFER (ximage));
241 gst_ximage_buffer_init (GstXImageBuffer * ximage_buffer, gpointer g_class)
244 ximage_buffer->SHMInfo.shmaddr = ((void *) -1);
245 ximage_buffer->SHMInfo.shmid = -1;
250 gst_ximage_buffer_class_init (gpointer g_class, gpointer class_data)
252 GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class);
254 mini_object_class->finalize = (GstMiniObjectFinalizeFunction)
255 gst_ximage_buffer_finalize;
259 gst_ximage_buffer_get_type (void)
261 static GType _gst_ximage_buffer_type;
263 if (G_UNLIKELY (_gst_ximage_buffer_type == 0)) {
264 static const GTypeInfo ximage_buffer_info = {
265 sizeof (GstBufferClass),
268 gst_ximage_buffer_class_init,
271 sizeof (GstXImageBuffer),
273 (GInstanceInitFunc) gst_ximage_buffer_init,
276 _gst_ximage_buffer_type = g_type_register_static (GST_TYPE_BUFFER,
277 "GstXImageBuffer", &ximage_buffer_info, 0);
279 return _gst_ximage_buffer_type;
284 #ifdef HAVE_XSHM /* Check that XShm calls actually work */
285 static gboolean error_caught = FALSE;
288 gst_ximagesink_handle_xerror (Display * display, XErrorEvent * xevent)
290 char error_msg[1024];
292 XGetErrorText (display, xevent->error_code, error_msg, 1024);
293 GST_DEBUG ("ximagesink failed to use XShm calls. error: %s", error_msg);
299 gst_ximagesink_check_xshm_calls (GstXImageSink * ximagesink,
300 GstXContext * xcontext)
303 XShmSegmentInfo SHMInfo;
305 int (*handler) (Display *, XErrorEvent *);
306 gboolean result = FALSE;
307 gboolean did_attach = FALSE;
309 g_return_val_if_fail (xcontext != NULL, FALSE);
311 /* Sync to ensure any older errors are already processed */
312 XSync (xcontext->disp, FALSE);
314 /* Set defaults so we don't free these later unnecessarily */
315 SHMInfo.shmaddr = ((void *) -1);
318 /* Setting an error handler to catch failure */
319 error_caught = FALSE;
320 handler = XSetErrorHandler (gst_ximagesink_handle_xerror);
322 /* Trying to create a 1x1 ximage */
323 GST_DEBUG ("XShmCreateImage of 1x1");
325 ximage = XShmCreateImage (xcontext->disp, xcontext->visual,
326 xcontext->depth, ZPixmap, NULL, &SHMInfo, 1, 1);
328 /* Might cause an error, sync to ensure it is noticed */
329 XSync (xcontext->disp, FALSE);
330 if (!ximage || error_caught) {
331 GST_WARNING ("could not XShmCreateImage a 1x1 image");
334 size = ximage->height * ximage->bytes_per_line;
336 SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
337 if (SHMInfo.shmid == -1) {
338 GST_WARNING ("could not get shared memory of %d bytes", size);
342 SHMInfo.shmaddr = shmat (SHMInfo.shmid, 0, 0);
343 if (SHMInfo.shmaddr == ((void *) -1)) {
344 GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
345 /* Clean up shm seg */
346 shmctl (SHMInfo.shmid, IPC_RMID, 0);
350 /* Delete the shared memory segment as soon as we manage to attach.
351 * This way, it will be deleted as soon as we detach later, and not
352 * leaked if we crash. */
353 shmctl (SHMInfo.shmid, IPC_RMID, 0);
355 ximage->data = SHMInfo.shmaddr;
356 SHMInfo.readOnly = FALSE;
358 if (XShmAttach (xcontext->disp, &SHMInfo) == 0) {
359 GST_WARNING ("Failed to XShmAttach");
363 /* Sync to ensure we see any errors we caused */
364 XSync (xcontext->disp, FALSE);
368 /* store whether we succeeded in result */
373 /* Sync to ensure we swallow any errors we caused and reset error_caught */
374 XSync (xcontext->disp, FALSE);
375 error_caught = FALSE;
376 XSetErrorHandler (handler);
379 XShmDetach (xcontext->disp, &SHMInfo);
380 XSync (xcontext->disp, FALSE);
382 if (SHMInfo.shmaddr != ((void *) -1))
383 shmdt (SHMInfo.shmaddr);
385 XDestroyImage (ximage);
388 #endif /* HAVE_XSHM */
390 /* This function handles GstXImageBuffer creation depending on XShm availability */
391 static GstXImageBuffer *
392 gst_ximagesink_ximage_new (GstXImageSink * ximagesink, GstCaps * caps)
394 GstXImageBuffer *ximage = NULL;
395 GstStructure *structure = NULL;
396 gboolean succeeded = FALSE;
398 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
400 ximage = (GstXImageBuffer *) gst_mini_object_new (GST_TYPE_XIMAGE_BUFFER);
402 structure = gst_caps_get_structure (caps, 0);
404 if (!gst_structure_get_int (structure, "width", &ximage->width) ||
405 !gst_structure_get_int (structure, "height", &ximage->height)) {
406 GST_WARNING ("failed getting geometry from caps %" GST_PTR_FORMAT, caps);
409 GST_DEBUG_OBJECT (ximagesink, "creating image %p (%dx%d)", ximage,
410 ximage->width, ximage->height);
412 g_mutex_lock (ximagesink->x_lock);
415 if (ximagesink->xcontext->use_xshm) {
416 ximage->ximage = XShmCreateImage (ximagesink->xcontext->disp,
417 ximagesink->xcontext->visual,
418 ximagesink->xcontext->depth,
419 ZPixmap, NULL, &ximage->SHMInfo, ximage->width, ximage->height);
420 if (!ximage->ximage) {
421 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
422 ("Failed to create output image buffer of %dx%d pixels",
423 ximage->width, ximage->height),
424 ("could not XShmCreateImage a %dx%d image",
425 ximage->width, ximage->height));
429 /* we have to use the returned bytes_per_line for our shm size */
430 ximage->size = ximage->ximage->bytes_per_line * ximage->ximage->height;
431 GST_LOG_OBJECT (ximagesink, "XShm image size is %d, width %d, stride %d",
432 ximage->size, ximage->width, ximage->ximage->bytes_per_line);
434 ximage->SHMInfo.shmid = shmget (IPC_PRIVATE, ximage->size,
436 if (ximage->SHMInfo.shmid == -1) {
437 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
438 ("Failed to create output image buffer of %dx%d pixels",
439 ximage->width, ximage->height),
440 ("could not get shared memory of %d bytes", ximage->size));
444 ximage->SHMInfo.shmaddr = shmat (ximage->SHMInfo.shmid, 0, 0);
445 if (ximage->SHMInfo.shmaddr == ((void *) -1)) {
446 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
447 ("Failed to create output image buffer of %dx%d pixels",
448 ximage->width, ximage->height),
449 ("Failed to shmat: %s", g_strerror (errno)));
450 /* Clean up the shared memory segment */
451 shmctl (ximage->SHMInfo.shmid, IPC_RMID, 0);
455 /* Now that we've attached, we can delete the shared memory segment.
456 * This way, it will be deleted as soon as we detach later, and not
457 * leaked if we crash. */
458 shmctl (ximage->SHMInfo.shmid, IPC_RMID, 0);
460 ximage->ximage->data = ximage->SHMInfo.shmaddr;
461 ximage->SHMInfo.readOnly = FALSE;
463 if (XShmAttach (ximagesink->xcontext->disp, &ximage->SHMInfo) == 0) {
464 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
465 ("Failed to create output image buffer of %dx%d pixels",
466 ximage->width, ximage->height), ("Failed to XShmAttach"));
470 XSync (ximagesink->xcontext->disp, FALSE);
472 #endif /* HAVE_XSHM */
474 ximage->ximage = XCreateImage (ximagesink->xcontext->disp,
475 ximagesink->xcontext->visual,
476 ximagesink->xcontext->depth,
478 ximage->width, ximage->height, ximagesink->xcontext->bpp, 0);
479 if (!ximage->ximage) {
480 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
481 ("Failed to create output image buffer of %dx%d pixels",
482 ximage->width, ximage->height),
483 ("could not XCreateImage a %dx%d image",
484 ximage->width, ximage->height));
488 /* we have to use the returned bytes_per_line for our image size */
489 ximage->size = ximage->ximage->bytes_per_line * ximage->ximage->height;
490 ximage->ximage->data = g_malloc (ximage->size);
492 XSync (ximagesink->xcontext->disp, FALSE);
496 GST_BUFFER_DATA (ximage) = (guchar *) ximage->ximage->data;
497 GST_BUFFER_SIZE (ximage) = ximage->size;
499 /* Keep a ref to our sink */
500 ximage->ximagesink = gst_object_ref (ximagesink);
503 g_mutex_unlock (ximagesink->x_lock);
506 gst_ximage_buffer_free (ximage);
513 /* This function destroys a GstXImageBuffer handling XShm availability */
515 gst_ximagesink_ximage_destroy (GstXImageSink * ximagesink,
516 GstXImageBuffer * ximage)
518 g_return_if_fail (ximage != NULL);
519 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
521 /* If the destroyed image is the current one we destroy our reference too */
522 if (ximagesink->cur_image == ximage) {
523 ximagesink->cur_image = NULL;
526 /* We might have some buffers destroyed after changing state to NULL */
527 if (!ximagesink->xcontext) {
531 g_mutex_lock (ximagesink->x_lock);
534 if (ximagesink->xcontext->use_xshm) {
535 if (ximage->SHMInfo.shmaddr != ((void *) -1)) {
536 XShmDetach (ximagesink->xcontext->disp, &ximage->SHMInfo);
537 XSync (ximagesink->xcontext->disp, 0);
538 shmdt (ximage->SHMInfo.shmaddr);
541 XDestroyImage (ximage->ximage);
544 #endif /* HAVE_XSHM */
546 if (ximage->ximage) {
547 XDestroyImage (ximage->ximage);
551 XSync (ximagesink->xcontext->disp, FALSE);
553 g_mutex_unlock (ximagesink->x_lock);
556 if (ximage->ximagesink) {
557 /* Release the ref to our sink */
558 ximage->ximagesink = NULL;
559 gst_object_unref (ximagesink);
565 /* We are called with the x_lock taken */
567 gst_ximagesink_xwindow_draw_borders (GstXImageSink * ximagesink,
568 GstXWindow * xwindow, GstVideoRectangle rect)
570 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
571 g_return_if_fail (xwindow != NULL);
573 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
574 ximagesink->xcontext->black);
578 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
579 0, 0, rect.x, xwindow->height);
583 if ((rect.x + rect.w) < xwindow->width) {
584 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
585 rect.x + rect.w, 0, xwindow->width, xwindow->height);
590 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
591 0, 0, xwindow->width, rect.y);
595 if ((rect.y + rect.h) < xwindow->height) {
596 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
597 0, rect.y + rect.h, xwindow->width, xwindow->height);
601 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
603 gst_ximagesink_ximage_put (GstXImageSink * ximagesink, GstXImageBuffer * ximage)
605 GstVideoRectangle src, dst, result;
607 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
609 /* We take the flow_lock. If expose is in there we don't want to run
610 concurrently from the data flow thread */
611 g_mutex_lock (ximagesink->flow_lock);
613 /* Store a reference to the last image we put, lose the previous one */
614 if (ximage && ximagesink->cur_image != ximage) {
615 if (ximagesink->cur_image) {
616 GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
617 gst_buffer_unref (ximagesink->cur_image);
619 GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
620 ximagesink->cur_image =
621 GST_XIMAGE_BUFFER (gst_buffer_ref (GST_BUFFER (ximage)));
624 /* Expose sends a NULL image, we take the latest frame */
626 if (ximagesink->cur_image) {
627 ximage = ximagesink->cur_image;
629 g_mutex_unlock (ximagesink->flow_lock);
634 gst_ximagesink_xwindow_update_geometry (ximagesink, ximagesink->xwindow);
636 src.w = ximage->width;
637 src.h = ximage->height;
638 dst.w = ximagesink->xwindow->width;
639 dst.h = ximagesink->xwindow->height;
641 gst_video_sink_center_rect (src, dst, &result, FALSE);
643 g_mutex_lock (ximagesink->x_lock);
645 gst_ximagesink_xwindow_draw_borders (ximagesink, ximagesink->xwindow, result);
648 if (ximagesink->xcontext->use_xshm) {
649 GST_LOG_OBJECT (ximagesink,
650 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
651 ximage, 0, 0, result.x, result.y, result.w, result.h,
652 ximagesink->xwindow->width, ximagesink->xwindow->height);
653 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
654 ximagesink->xwindow->gc, ximage->ximage, 0, 0, result.x, result.y,
655 result.w, result.h, FALSE);
657 #endif /* HAVE_XSHM */
659 GST_LOG_OBJECT (ximagesink,
660 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
661 ximage, 0, 0, result.x, result.y, result.w, result.h,
662 ximagesink->xwindow->width, ximagesink->xwindow->height);
663 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
664 ximagesink->xwindow->gc, ximage->ximage, 0, 0, result.x, result.y,
668 XSync (ximagesink->xcontext->disp, FALSE);
670 g_mutex_unlock (ximagesink->x_lock);
672 g_mutex_unlock (ximagesink->flow_lock);
676 gst_ximagesink_xwindow_decorate (GstXImageSink * ximagesink,
679 Atom hints_atom = None;
682 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), FALSE);
683 g_return_val_if_fail (window != NULL, FALSE);
685 g_mutex_lock (ximagesink->x_lock);
687 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS", 1);
688 if (hints_atom == None) {
689 g_mutex_unlock (ximagesink->x_lock);
693 hints = g_malloc0 (sizeof (MotifWmHints));
695 hints->flags |= MWM_HINTS_DECORATIONS;
696 hints->decorations = 1 << 0;
698 XChangeProperty (ximagesink->xcontext->disp, window->win,
699 hints_atom, hints_atom, 32, PropModeReplace,
700 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
702 XSync (ximagesink->xcontext->disp, FALSE);
704 g_mutex_unlock (ximagesink->x_lock);
711 /* This function handles a GstXWindow creation */
713 gst_ximagesink_xwindow_new (GstXImageSink * ximagesink, gint width, gint height)
715 GstXWindow *xwindow = NULL;
718 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
720 xwindow = g_new0 (GstXWindow, 1);
722 xwindow->width = width;
723 xwindow->height = height;
724 xwindow->internal = TRUE;
726 g_mutex_lock (ximagesink->x_lock);
728 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
729 ximagesink->xcontext->root,
730 0, 0, xwindow->width, xwindow->height, 0, 0, ximagesink->xcontext->black);
732 /* We have to do that to prevent X from redrawing the background on
733 ConfigureNotify. This takes away flickering of video when resizing. */
734 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
736 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
737 StructureNotifyMask | PointerMotionMask | KeyPressMask |
738 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
740 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
743 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
745 XSync (ximagesink->xcontext->disp, FALSE);
747 g_mutex_unlock (ximagesink->x_lock);
749 gst_ximagesink_xwindow_decorate (ximagesink, xwindow);
751 gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (ximagesink), xwindow->win);
756 /* This function destroys a GstXWindow */
758 gst_ximagesink_xwindow_destroy (GstXImageSink * ximagesink,
759 GstXWindow * xwindow)
761 g_return_if_fail (xwindow != NULL);
762 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
764 g_mutex_lock (ximagesink->x_lock);
766 /* If we did not create that window we just free the GC and let it live */
767 if (xwindow->internal)
768 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
770 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
772 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
774 XSync (ximagesink->xcontext->disp, FALSE);
776 g_mutex_unlock (ximagesink->x_lock);
782 gst_ximagesink_xwindow_update_geometry (GstXImageSink * ximagesink,
783 GstXWindow * xwindow)
785 XWindowAttributes attr;
787 g_return_if_fail (xwindow != NULL);
788 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
790 /* Update the window geometry */
791 g_mutex_lock (ximagesink->x_lock);
793 XGetWindowAttributes (ximagesink->xcontext->disp,
794 ximagesink->xwindow->win, &attr);
796 ximagesink->xwindow->width = attr.width;
797 ximagesink->xwindow->height = attr.height;
799 g_mutex_unlock (ximagesink->x_lock);
803 gst_ximagesink_xwindow_clear (GstXImageSink * ximagesink, GstXWindow * xwindow)
805 g_return_if_fail (xwindow != NULL);
806 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
808 g_mutex_lock (ximagesink->x_lock);
810 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
811 ximagesink->xcontext->black);
813 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
814 0, 0, xwindow->width, xwindow->height);
816 XSync (ximagesink->xcontext->disp, FALSE);
818 g_mutex_unlock (ximagesink->x_lock);
821 /* This function handles XEvents that might be in the queue. It generates
822 GstEvent that will be sent upstream in the pipeline to handle interactivity
825 gst_ximagesink_handle_xevents (GstXImageSink * ximagesink)
829 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
832 guint pointer_x = 0, pointer_y = 0;
833 gboolean pointer_moved = FALSE;
835 /* Then we get all pointer motion events, only the last position is
837 g_mutex_lock (ximagesink->x_lock);
838 while (XCheckWindowEvent (ximagesink->xcontext->disp,
839 ximagesink->xwindow->win, PointerMotionMask, &e)) {
840 g_mutex_unlock (ximagesink->x_lock);
844 pointer_x = e.xmotion.x;
845 pointer_y = e.xmotion.y;
846 pointer_moved = TRUE;
852 g_mutex_lock (ximagesink->x_lock);
854 g_mutex_unlock (ximagesink->x_lock);
857 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
858 pointer_x, pointer_y);
859 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
860 "mouse-move", 0, pointer_x, pointer_y);
864 /* We get all remaining events on our window to throw them upstream */
865 g_mutex_lock (ximagesink->x_lock);
866 while (XCheckWindowEvent (ximagesink->xcontext->disp,
867 ximagesink->xwindow->win,
868 KeyPressMask | KeyReleaseMask |
869 ButtonPressMask | ButtonReleaseMask, &e)) {
872 /* We lock only for the X function call */
873 g_mutex_unlock (ximagesink->x_lock);
877 /* Mouse button pressed/released over our window. We send upstream
878 events for interactivity/navigation */
879 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
880 e.xbutton.button, e.xbutton.x, e.xbutton.x);
881 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
882 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
885 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
886 e.xbutton.button, e.xbutton.x, e.xbutton.x);
887 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
888 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
892 /* Key pressed/released over our window. We send upstream
893 events for interactivity/navigation */
894 GST_DEBUG ("ximagesink key %d pressed over window at %d,%d",
895 e.xkey.keycode, e.xkey.x, e.xkey.x);
896 keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
898 if (keysym != NoSymbol) {
899 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
901 "key-press" : "key-release", XKeysymToString (keysym));
903 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
904 e.type == KeyPress ? "key-press" : "key-release", "unknown");
908 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
911 g_mutex_lock (ximagesink->x_lock);
913 g_mutex_unlock (ximagesink->x_lock);
916 gboolean exposed = FALSE;
918 g_mutex_lock (ximagesink->x_lock);
919 while (XCheckWindowEvent (ximagesink->xcontext->disp,
920 ximagesink->xwindow->win, ExposureMask, &e)) {
921 g_mutex_unlock (ximagesink->x_lock);
931 g_mutex_lock (ximagesink->x_lock);
933 g_mutex_unlock (ximagesink->x_lock);
936 gst_ximagesink_expose (GST_X_OVERLAY (ximagesink));
942 gst_ximagesink_event_thread (GstXImageSink * ximagesink)
944 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
946 while (ximagesink->running) {
947 if (ximagesink->xwindow) {
948 gst_ximagesink_handle_xevents (ximagesink);
956 /* This function calculates the pixel aspect ratio based on the properties
957 * in the xcontext structure and stores it there. */
959 gst_ximagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
962 {1, 1}, /* regular screen */
963 {16, 15}, /* PAL TV */
964 {11, 10}, /* 525 line Rec.601 video */
965 {54, 59}, /* 625 line Rec.601 video */
966 {64, 45}, /* 1280x1024 on 16:9 display */
967 {5, 3}, /* 1280x1024 on 4:3 display */
968 {4, 3} /* 800x600 on 16:9 display */
975 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
977 /* first calculate the "real" ratio based on the X values;
978 * which is the "physical" w/h divided by the w/h in pixels of the display */
979 ratio = (gdouble) (xcontext->widthmm * xcontext->height)
980 / (xcontext->heightmm * xcontext->width);
982 /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
984 if (xcontext->width == 720 && xcontext->height == 576) {
985 ratio = 4.0 * 576 / (3.0 * 720);
987 GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
989 /* now find the one from par[][2] with the lowest delta to the real one */
993 for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
994 gdouble this_delta = DELTA (i);
996 if (this_delta < delta) {
1002 GST_DEBUG ("Decided on index %d (%d/%d)", index,
1003 par[index][0], par[index][1]);
1005 g_free (xcontext->par);
1006 xcontext->par = g_new0 (GValue, 1);
1007 g_value_init (xcontext->par, GST_TYPE_FRACTION);
1008 gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
1009 GST_DEBUG ("set xcontext PAR to %d/%d",
1010 gst_value_get_fraction_numerator (xcontext->par),
1011 gst_value_get_fraction_denominator (xcontext->par));
1014 /* This function gets the X Display and global info about it. Everything is
1015 stored in our object and will be cleaned when the object is disposed. Note
1016 here that caps for supported format are generated without any window or
1018 static GstXContext *
1019 gst_ximagesink_xcontext_get (GstXImageSink * ximagesink)
1021 GstXContext *xcontext = NULL;
1022 XPixmapFormatValues *px_formats = NULL;
1023 gint nb_formats = 0, i;
1025 g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
1027 xcontext = g_new0 (GstXContext, 1);
1029 g_mutex_lock (ximagesink->x_lock);
1031 xcontext->disp = XOpenDisplay (ximagesink->display_name);
1033 if (!xcontext->disp) {
1034 g_mutex_unlock (ximagesink->x_lock);
1036 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1037 ("Could not initialise X output"), ("Could not open display"));
1041 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1042 xcontext->screen_num = DefaultScreen (xcontext->disp);
1043 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1044 xcontext->root = DefaultRootWindow (xcontext->disp);
1045 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1046 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1047 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1049 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1050 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1051 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1052 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1054 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
1055 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1057 gst_ximagesink_calculate_pixel_aspect_ratio (xcontext);
1059 /* We get supported pixmap formats at supported depth */
1060 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1063 XCloseDisplay (xcontext->disp);
1064 g_mutex_unlock (ximagesink->x_lock);
1069 /* We get bpp value corresponding to our running depth */
1070 for (i = 0; i < nb_formats; i++) {
1071 if (px_formats[i].depth == xcontext->depth)
1072 xcontext->bpp = px_formats[i].bits_per_pixel;
1077 xcontext->endianness =
1078 (ImageByteOrder (xcontext->disp) ==
1079 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1081 /* Search for XShm extension support */
1083 if (XShmQueryExtension (xcontext->disp) &&
1084 gst_ximagesink_check_xshm_calls (ximagesink, xcontext)) {
1085 xcontext->use_xshm = TRUE;
1086 GST_DEBUG ("ximagesink is using XShm extension");
1090 xcontext->use_xshm = FALSE;
1091 GST_DEBUG ("ximagesink is not using XShm extension");
1094 /* our caps system handles 24/32bpp RGB as big-endian. */
1095 if ((xcontext->bpp == 24 || xcontext->bpp == 32) &&
1096 xcontext->endianness == G_LITTLE_ENDIAN) {
1097 xcontext->endianness = G_BIG_ENDIAN;
1098 xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask);
1099 xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask);
1100 xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask);
1101 if (xcontext->bpp == 24) {
1102 xcontext->visual->red_mask >>= 8;
1103 xcontext->visual->green_mask >>= 8;
1104 xcontext->visual->blue_mask >>= 8;
1108 /* update object's par with calculated one if not set yet */
1109 if (!ximagesink->par) {
1110 ximagesink->par = g_new0 (GValue, 1);
1111 gst_value_init_and_copy (ximagesink->par, xcontext->par);
1112 GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
1114 xcontext->caps = gst_caps_new_simple ("video/x-raw-rgb",
1115 "bpp", G_TYPE_INT, xcontext->bpp,
1116 "depth", G_TYPE_INT, xcontext->depth,
1117 "endianness", G_TYPE_INT, xcontext->endianness,
1118 "red_mask", G_TYPE_INT, xcontext->visual->red_mask,
1119 "green_mask", G_TYPE_INT, xcontext->visual->green_mask,
1120 "blue_mask", G_TYPE_INT, xcontext->visual->blue_mask,
1121 "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1122 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1123 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
1124 if (ximagesink->par) {
1127 nom = gst_value_get_fraction_numerator (ximagesink->par);
1128 den = gst_value_get_fraction_denominator (ximagesink->par);
1129 gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
1130 GST_TYPE_FRACTION, nom, den, NULL);
1133 g_mutex_unlock (ximagesink->x_lock);
1135 /* Setup our event listening thread */
1136 ximagesink->event_thread = g_thread_create (
1137 (GThreadFunc) gst_ximagesink_event_thread, ximagesink, TRUE, NULL);
1142 /* This function cleans the X context. Closing the Display and unrefing the
1143 caps for supported formats. */
1145 gst_ximagesink_xcontext_clear (GstXImageSink * ximagesink)
1147 g_return_if_fail (GST_IS_XIMAGESINK (ximagesink));
1148 g_return_if_fail (ximagesink->xcontext != NULL);
1150 /* Wait for our event thread */
1151 if (ximagesink->event_thread) {
1152 g_thread_join (ximagesink->event_thread);
1153 ximagesink->event_thread = NULL;
1156 gst_caps_unref (ximagesink->xcontext->caps);
1157 g_free (ximagesink->xcontext->par);
1158 g_free (ximagesink->par);
1159 ximagesink->par = NULL;
1161 g_mutex_lock (ximagesink->x_lock);
1163 XCloseDisplay (ximagesink->xcontext->disp);
1165 g_mutex_unlock (ximagesink->x_lock);
1167 g_free (ximagesink->xcontext);
1168 ximagesink->xcontext = NULL;
1172 gst_ximagesink_bufferpool_clear (GstXImageSink * ximagesink)
1175 g_mutex_lock (ximagesink->pool_lock);
1177 while (ximagesink->buffer_pool) {
1178 GstXImageBuffer *ximage = ximagesink->buffer_pool->data;
1180 ximagesink->buffer_pool = g_slist_delete_link (ximagesink->buffer_pool,
1181 ximagesink->buffer_pool);
1182 gst_ximage_buffer_free (ximage);
1185 g_mutex_unlock (ximagesink->pool_lock);
1191 gst_ximagesink_getcaps (GstBaseSink * bsink)
1193 GstXImageSink *ximagesink;
1197 ximagesink = GST_XIMAGESINK (bsink);
1199 if (ximagesink->xcontext)
1200 return gst_caps_ref (ximagesink->xcontext->caps);
1202 /* get a template copy and add the pixel aspect ratio */
1204 gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->
1206 for (i = 0; i < gst_caps_get_size (caps); ++i) {
1207 GstStructure *structure = gst_caps_get_structure (caps, i);
1209 if (ximagesink->par) {
1212 nom = gst_value_get_fraction_numerator (ximagesink->par);
1213 den = gst_value_get_fraction_denominator (ximagesink->par);
1214 gst_structure_set (structure, "pixel-aspect-ratio",
1215 GST_TYPE_FRACTION, nom, den, NULL);
1223 gst_ximagesink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1225 GstXImageSink *ximagesink;
1226 gboolean ret = TRUE;
1227 GstStructure *structure;
1228 GstCaps *intersection;
1230 gint new_width, new_height;
1233 ximagesink = GST_XIMAGESINK (bsink);
1235 if (!ximagesink->xcontext)
1238 GST_DEBUG_OBJECT (ximagesink,
1239 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1240 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1242 /* We intersect those caps with our template to make sure they are correct */
1243 intersection = gst_caps_intersect (ximagesink->xcontext->caps, caps);
1244 GST_DEBUG_OBJECT (ximagesink, "intersection returned %" GST_PTR_FORMAT,
1246 if (gst_caps_is_empty (intersection)) {
1250 gst_caps_unref (intersection);
1252 structure = gst_caps_get_structure (caps, 0);
1254 ret &= gst_structure_get_int (structure, "width", &new_width);
1255 ret &= gst_structure_get_int (structure, "height", &new_height);
1256 fps = gst_structure_get_value (structure, "framerate");
1257 ret &= (fps != NULL);
1261 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1262 * otherwise linking should fail */
1263 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1265 if (ximagesink->par) {
1266 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1269 } else if (ximagesink->xcontext->par) {
1270 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1276 GST_VIDEO_SINK_WIDTH (ximagesink) = new_width;
1277 GST_VIDEO_SINK_HEIGHT (ximagesink) = new_height;
1278 ximagesink->fps_n = gst_value_get_fraction_numerator (fps);
1279 ximagesink->fps_d = gst_value_get_fraction_denominator (fps);
1281 /* Notify application to set xwindow id now */
1282 if (!ximagesink->xwindow) {
1283 gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (ximagesink));
1286 /* Creating our window and our image */
1287 g_assert (GST_VIDEO_SINK_WIDTH (ximagesink) > 0);
1288 g_assert (GST_VIDEO_SINK_HEIGHT (ximagesink) > 0);
1289 if (!ximagesink->xwindow) {
1290 ximagesink->xwindow = gst_ximagesink_xwindow_new (ximagesink,
1291 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1294 /* If our ximage has changed we destroy it, next chain iteration will create
1296 if ((ximagesink->ximage) &&
1297 ((GST_VIDEO_SINK_WIDTH (ximagesink) != ximagesink->ximage->width) ||
1298 (GST_VIDEO_SINK_HEIGHT (ximagesink) != ximagesink->ximage->height))) {
1299 GST_DEBUG_OBJECT (ximagesink, "our image is not usable anymore, unref %p",
1300 ximagesink->ximage);
1301 gst_buffer_unref (GST_BUFFER (ximagesink->ximage));
1302 ximagesink->ximage = NULL;
1310 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1315 static GstStateChangeReturn
1316 gst_ximagesink_change_state (GstElement * element, GstStateChange transition)
1318 GstXImageSink *ximagesink;
1319 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1321 ximagesink = GST_XIMAGESINK (element);
1323 switch (transition) {
1324 case GST_STATE_CHANGE_NULL_TO_READY:
1325 ximagesink->running = TRUE;
1326 /* Initializing the XContext */
1327 if (!ximagesink->xcontext)
1328 ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink);
1329 if (!ximagesink->xcontext) {
1330 ret = GST_STATE_CHANGE_FAILURE;
1333 /* call XSynchronize with the current value of synchronous */
1334 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1335 ximagesink->synchronous ? "TRUE" : "FALSE");
1336 g_mutex_lock (ximagesink->x_lock);
1337 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1338 g_mutex_unlock (ximagesink->x_lock);
1340 case GST_STATE_CHANGE_READY_TO_PAUSED:
1342 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1348 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1349 if (ret == GST_STATE_CHANGE_FAILURE)
1352 switch (transition) {
1353 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1355 case GST_STATE_CHANGE_PAUSED_TO_READY:
1356 if (ximagesink->xwindow)
1357 gst_ximagesink_xwindow_clear (ximagesink, ximagesink->xwindow);
1358 ximagesink->fps_n = 0;
1359 ximagesink->fps_d = 1;
1360 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1361 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1363 case GST_STATE_CHANGE_READY_TO_NULL:
1364 ximagesink->running = FALSE;
1365 if (ximagesink->ximage) {
1366 gst_buffer_unref (ximagesink->ximage);
1367 ximagesink->ximage = NULL;
1369 if (ximagesink->cur_image) {
1370 gst_buffer_unref (ximagesink->cur_image);
1371 ximagesink->cur_image = NULL;
1373 if (ximagesink->buffer_pool)
1374 gst_ximagesink_bufferpool_clear (ximagesink);
1376 if (ximagesink->xwindow) {
1377 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1378 ximagesink->xwindow = NULL;
1381 if (ximagesink->xcontext) {
1382 gst_ximagesink_xcontext_clear (ximagesink);
1383 ximagesink->xcontext = NULL;
1395 gst_ximagesink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1396 GstClockTime * start, GstClockTime * end)
1398 GstXImageSink *ximagesink;
1400 ximagesink = GST_XIMAGESINK (bsink);
1402 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1403 *start = GST_BUFFER_TIMESTAMP (buf);
1404 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1405 *end = *start + GST_BUFFER_DURATION (buf);
1407 if (ximagesink->fps_n > 0) {
1408 *end = *start + (GST_SECOND * ximagesink->fps_d) / ximagesink->fps_n;
1414 static GstFlowReturn
1415 gst_ximagesink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
1417 GstXImageSink *ximagesink;
1419 g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1421 ximagesink = GST_XIMAGESINK (bsink);
1423 /* If this buffer has been allocated using our buffer management we simply
1424 put the ximage which is in the PRIVATE pointer */
1425 if (GST_IS_XIMAGE_BUFFER (buf)) {
1426 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1427 gst_ximagesink_ximage_put (ximagesink, GST_XIMAGE_BUFFER (buf));
1429 /* Else we have to copy the data into our private image, */
1430 /* if we have one... */
1431 GST_LOG_OBJECT (ximagesink, "normal buffer, copying from it");
1432 if (!ximagesink->ximage) {
1433 GST_DEBUG_OBJECT (ximagesink, "creating our ximage");
1434 ximagesink->ximage = gst_ximagesink_ximage_new (ximagesink,
1435 GST_BUFFER_CAPS (buf));
1436 if (!ximagesink->ximage)
1437 /* The create method should have posted an informative error */
1440 if (ximagesink->ximage->size < GST_BUFFER_SIZE (buf)) {
1441 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1442 ("Failed to create output image buffer of %dx%d pixels",
1443 ximagesink->ximage->width, ximagesink->ximage->height),
1444 ("XServer allocated buffer size did not match input buffer"));
1446 gst_ximagesink_ximage_destroy (ximagesink, ximagesink->ximage);
1447 ximagesink->ximage = NULL;
1451 memcpy (GST_BUFFER_DATA (ximagesink->ximage), GST_BUFFER_DATA (buf),
1452 MIN (GST_BUFFER_SIZE (buf), ximagesink->ximage->size));
1453 gst_ximagesink_ximage_put (ximagesink, ximagesink->ximage);
1461 /* No image available. That's very bad ! */
1462 GST_DEBUG ("could not create image");
1463 return GST_FLOW_ERROR;
1467 /* Buffer management */
1469 static GstFlowReturn
1470 gst_ximagesink_buffer_alloc (GstBaseSink * bsink, guint64 offset, guint size,
1471 GstCaps * caps, GstBuffer ** buf)
1473 GstXImageSink *ximagesink;
1474 GstXImageBuffer *ximage = NULL;
1475 GstStructure *structure = NULL;
1476 GstCaps *desired_caps = NULL;
1477 GstFlowReturn ret = GST_FLOW_OK;
1478 gboolean rev_nego = FALSE;
1481 ximagesink = GST_XIMAGESINK (bsink);
1483 GST_LOG_OBJECT (ximagesink,
1484 "a buffer of %d bytes was requested with caps %" GST_PTR_FORMAT
1485 " and offset %" G_GUINT64_FORMAT, size, caps, offset);
1487 desired_caps = gst_caps_copy (caps);
1489 structure = gst_caps_get_structure (desired_caps, 0);
1491 if (gst_structure_get_int (structure, "width", &width) &&
1492 gst_structure_get_int (structure, "height", &height)) {
1493 GstVideoRectangle dst, src, result;
1498 /* We take the flow_lock because the window might go away */
1499 g_mutex_lock (ximagesink->flow_lock);
1501 if (!ximagesink->xwindow) {
1502 g_mutex_unlock (ximagesink->flow_lock);
1506 /* What is our geometry */
1507 gst_ximagesink_xwindow_update_geometry (ximagesink, ximagesink->xwindow);
1508 dst.w = ximagesink->xwindow->width;
1509 dst.h = ximagesink->xwindow->height;
1511 g_mutex_unlock (ximagesink->flow_lock);
1513 if (ximagesink->keep_aspect) {
1514 GST_LOG_OBJECT (ximagesink, "enforcing aspect ratio in reverse caps "
1516 gst_video_sink_center_rect (src, dst, &result, TRUE);
1518 GST_LOG_OBJECT (ximagesink, "trying to resize to window geometry "
1519 "ignoring aspect ratio");
1520 result.x = result.y = 0;
1525 /* We would like another geometry */
1526 if (width != result.w || height != result.h) {
1528 GstPad *peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
1530 if (!GST_IS_PAD (peer)) {
1531 /* Is this situation possible ? */
1535 GST_DEBUG ("we would love to receive a %dx%d video", result.w, result.h);
1536 gst_structure_set (structure, "width", G_TYPE_INT, result.w, NULL);
1537 gst_structure_set (structure, "height", G_TYPE_INT, result.h, NULL);
1539 /* PAR property overrides the X calculated one */
1540 if (ximagesink->par) {
1541 nom = gst_value_get_fraction_numerator (ximagesink->par);
1542 den = gst_value_get_fraction_denominator (ximagesink->par);
1543 gst_structure_set (structure, "pixel-aspect-ratio",
1544 GST_TYPE_FRACTION, nom, den, NULL);
1545 } else if (ximagesink->xcontext->par) {
1546 nom = gst_value_get_fraction_numerator (ximagesink->xcontext->par);
1547 den = gst_value_get_fraction_denominator (ximagesink->xcontext->par);
1548 gst_structure_set (structure, "pixel-aspect-ratio",
1549 GST_TYPE_FRACTION, nom, den, NULL);
1552 if (gst_pad_accept_caps (peer, desired_caps)) {
1555 bpp = size / height / width;
1559 size = bpp * width * height;
1560 GST_DEBUG ("peed pad accepts our desired caps %" GST_PTR_FORMAT
1561 " buffer size is now %d bytes", desired_caps, size);
1563 GST_DEBUG ("peer pad does not accept our desired caps %" GST_PTR_FORMAT,
1566 width = GST_VIDEO_SINK_WIDTH (ximagesink);
1567 height = GST_VIDEO_SINK_HEIGHT (ximagesink);
1569 gst_object_unref (peer);
1574 /* Inspect our buffer pool */
1575 g_mutex_lock (ximagesink->pool_lock);
1576 while (ximagesink->buffer_pool) {
1577 ximage = (GstXImageBuffer *) ximagesink->buffer_pool->data;
1580 /* Removing from the pool */
1581 ximagesink->buffer_pool = g_slist_delete_link (ximagesink->buffer_pool,
1582 ximagesink->buffer_pool);
1584 /* If the ximage is invalid for our need, destroy */
1585 if ((ximage->width != width) || (ximage->height != height)) {
1586 gst_ximage_buffer_free (ximage);
1589 /* We found a suitable ximage */
1594 g_mutex_unlock (ximagesink->pool_lock);
1596 /* We haven't found anything, creating a new one */
1599 ximage = gst_ximagesink_ximage_new (ximagesink, desired_caps);
1601 ximage = gst_ximagesink_ximage_new (ximagesink, caps);
1604 /* Now we should have a ximage, set appropriate caps on it */
1607 gst_buffer_set_caps (GST_BUFFER (ximage), desired_caps);
1609 gst_buffer_set_caps (GST_BUFFER (ximage), caps);
1613 gst_caps_unref (desired_caps);
1615 *buf = GST_BUFFER (ximage);
1620 /* Interfaces stuff */
1623 gst_ximagesink_interface_supported (GstImplementsInterface * iface, GType type)
1625 g_assert (type == GST_TYPE_NAVIGATION || type == GST_TYPE_X_OVERLAY);
1630 gst_ximagesink_interface_init (GstImplementsInterfaceClass * klass)
1632 klass->supported = gst_ximagesink_interface_supported;
1636 gst_ximagesink_navigation_send_event (GstNavigation * navigation,
1637 GstStructure * structure)
1639 GstXImageSink *ximagesink = GST_XIMAGESINK (navigation);
1641 gint x_offset, y_offset;
1645 event = gst_event_new_navigation (structure);
1647 /* We are not converting the pointer coordinates as there's no hardware
1648 scaling done here. The only possible scaling is done by videoscale and
1649 videoscale will have to catch those events and tranform the coordinates
1650 to match the applied scaling. So here we just add the offset if the image
1651 is centered in the window. */
1653 /* We take the flow_lock while we look at the window */
1654 g_mutex_lock (ximagesink->flow_lock);
1656 if (!ximagesink->xwindow) {
1657 g_mutex_unlock (ximagesink->flow_lock);
1661 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1662 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1664 g_mutex_unlock (ximagesink->flow_lock);
1666 if (gst_structure_get_double (structure, "pointer_x", &x)) {
1668 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1670 if (gst_structure_get_double (structure, "pointer_y", &y)) {
1672 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1675 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (ximagesink));
1677 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
1678 gst_pad_send_event (pad, event);
1680 gst_object_unref (pad);
1685 gst_ximagesink_navigation_init (GstNavigationInterface * iface)
1687 iface->send_event = gst_ximagesink_navigation_send_event;
1691 gst_ximagesink_set_xwindow_id (GstXOverlay * overlay, XID xwindow_id)
1693 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1694 GstXWindow *xwindow = NULL;
1695 XWindowAttributes attr;
1697 /* If we already use that window return */
1698 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win))
1701 /* If the element has not initialized the X11 context try to do so */
1702 if (!ximagesink->xcontext)
1703 ximagesink->xcontext = gst_ximagesink_xcontext_get (ximagesink);
1705 if (!ximagesink->xcontext) {
1706 GST_WARNING_OBJECT (ximagesink,
1707 "ximagesink was unable to obtain the X11 context.");
1711 /* We acquire the stream lock while setting this window in the element.
1712 We are basically cleaning tons of stuff replacing the old window, putting
1713 images while we do that would surely crash */
1714 g_mutex_lock (ximagesink->flow_lock);
1716 /* If a window is there already we destroy it */
1717 if (ximagesink->xwindow) {
1718 gst_ximagesink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1719 ximagesink->xwindow = NULL;
1722 /* If the xid is 0 we go back to an internal window */
1723 if (xwindow_id == 0) {
1724 /* If no width/height caps nego did not happen window will be created
1725 during caps nego then */
1726 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1727 xwindow = gst_ximagesink_xwindow_new (ximagesink,
1728 GST_VIDEO_SINK_WIDTH (ximagesink),
1729 GST_VIDEO_SINK_HEIGHT (ximagesink));
1732 xwindow = g_new0 (GstXWindow, 1);
1734 xwindow->win = xwindow_id;
1736 /* We get window geometry, set the event we want to receive,
1738 g_mutex_lock (ximagesink->x_lock);
1739 XGetWindowAttributes (ximagesink->xcontext->disp, xwindow->win, &attr);
1740 xwindow->width = attr.width;
1741 xwindow->height = attr.height;
1742 xwindow->internal = FALSE;
1743 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1744 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1747 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1748 g_mutex_unlock (ximagesink->x_lock);
1752 ximagesink->xwindow = xwindow;
1754 g_mutex_unlock (ximagesink->flow_lock);
1758 gst_ximagesink_expose (GstXOverlay * overlay)
1760 GstXImageSink *ximagesink = GST_XIMAGESINK (overlay);
1762 if (!ximagesink->xwindow)
1765 gst_ximagesink_ximage_put (ximagesink, NULL);
1769 gst_ximagesink_xoverlay_init (GstXOverlayClass * iface)
1771 iface->set_xwindow_id = gst_ximagesink_set_xwindow_id;
1772 iface->expose = gst_ximagesink_expose;
1775 /* =========================================== */
1777 /* Init & Class init */
1779 /* =========================================== */
1782 gst_ximagesink_set_property (GObject * object, guint prop_id,
1783 const GValue * value, GParamSpec * pspec)
1785 GstXImageSink *ximagesink;
1787 g_return_if_fail (GST_IS_XIMAGESINK (object));
1789 ximagesink = GST_XIMAGESINK (object);
1793 ximagesink->display_name = g_strdup (g_value_get_string (value));
1795 case PROP_SYNCHRONOUS:
1796 ximagesink->synchronous = g_value_get_boolean (value);
1797 if (ximagesink->xcontext) {
1798 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1799 ximagesink->synchronous ? "TRUE" : "FALSE");
1800 g_mutex_lock (ximagesink->x_lock);
1801 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1802 g_mutex_unlock (ximagesink->x_lock);
1805 case PROP_FORCE_ASPECT_RATIO:
1806 ximagesink->keep_aspect = g_value_get_boolean (value);
1808 case PROP_PIXEL_ASPECT_RATIO:
1812 tmp = g_new0 (GValue, 1);
1813 g_value_init (tmp, GST_TYPE_FRACTION);
1815 if (!g_value_transform (value, tmp)) {
1816 GST_WARNING_OBJECT (ximagesink,
1817 "Could not transform string to aspect ratio");
1820 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1821 gst_value_get_fraction_numerator (tmp),
1822 gst_value_get_fraction_denominator (tmp));
1823 g_free (ximagesink->par);
1824 ximagesink->par = tmp;
1829 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1835 gst_ximagesink_get_property (GObject * object, guint prop_id,
1836 GValue * value, GParamSpec * pspec)
1838 GstXImageSink *ximagesink;
1840 g_return_if_fail (GST_IS_XIMAGESINK (object));
1842 ximagesink = GST_XIMAGESINK (object);
1846 g_value_set_string (value, g_strdup (ximagesink->display_name));
1848 case PROP_SYNCHRONOUS:
1849 g_value_set_boolean (value, ximagesink->synchronous);
1851 case PROP_FORCE_ASPECT_RATIO:
1852 g_value_set_boolean (value, ximagesink->keep_aspect);
1854 case PROP_PIXEL_ASPECT_RATIO:
1855 if (ximagesink->par)
1856 g_value_transform (ximagesink->par, value);
1859 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1865 gst_ximagesink_finalize (GObject * object)
1867 GstXImageSink *ximagesink;
1869 ximagesink = GST_XIMAGESINK (object);
1871 if (ximagesink->display_name) {
1872 g_free (ximagesink->display_name);
1873 ximagesink->display_name = NULL;
1875 if (ximagesink->par) {
1876 g_free (ximagesink->par);
1877 ximagesink->par = NULL;
1879 if (ximagesink->x_lock) {
1880 g_mutex_free (ximagesink->x_lock);
1881 ximagesink->x_lock = NULL;
1883 if (ximagesink->flow_lock) {
1884 g_mutex_free (ximagesink->flow_lock);
1885 ximagesink->flow_lock = NULL;
1887 if (ximagesink->pool_lock) {
1888 g_mutex_free (ximagesink->pool_lock);
1889 ximagesink->pool_lock = NULL;
1892 G_OBJECT_CLASS (parent_class)->finalize (object);
1896 gst_ximagesink_init (GstXImageSink * ximagesink)
1898 ximagesink->display_name = NULL;
1899 ximagesink->xcontext = NULL;
1900 ximagesink->xwindow = NULL;
1901 ximagesink->ximage = NULL;
1902 ximagesink->cur_image = NULL;
1904 ximagesink->event_thread = NULL;
1905 ximagesink->running = FALSE;
1907 ximagesink->fps_n = 0;
1908 ximagesink->fps_d = 1;
1910 ximagesink->x_lock = g_mutex_new ();
1911 ximagesink->flow_lock = g_mutex_new ();
1913 ximagesink->par = NULL;
1915 ximagesink->pool_lock = g_mutex_new ();
1916 ximagesink->buffer_pool = NULL;
1918 ximagesink->synchronous = FALSE;
1919 ximagesink->keep_aspect = FALSE;
1923 gst_ximagesink_base_init (gpointer g_class)
1925 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
1927 gst_element_class_set_details (element_class, &gst_ximagesink_details);
1929 gst_element_class_add_pad_template (element_class,
1930 gst_static_pad_template_get (&gst_ximagesink_sink_template_factory));
1934 gst_ximagesink_class_init (GstXImageSinkClass * klass)
1936 GObjectClass *gobject_class;
1937 GstElementClass *gstelement_class;
1938 GstBaseSinkClass *gstbasesink_class;
1940 gobject_class = (GObjectClass *) klass;
1941 gstelement_class = (GstElementClass *) klass;
1942 gstbasesink_class = (GstBaseSinkClass *) klass;
1944 parent_class = g_type_class_peek_parent (klass);
1946 gobject_class->finalize = gst_ximagesink_finalize;
1947 gobject_class->set_property = gst_ximagesink_set_property;
1948 gobject_class->get_property = gst_ximagesink_get_property;
1950 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1951 g_param_spec_string ("display", "Display", "X Display name",
1952 NULL, G_PARAM_READWRITE));
1953 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1954 g_param_spec_boolean ("synchronous", "Synchronous", "When enabled, runs "
1955 "the X display in synchronous mode. (used only for debugging)", FALSE,
1956 G_PARAM_READWRITE));
1957 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1958 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1959 "When enabled, reverse caps negotiation (scaling) will respect "
1960 "original aspect ratio", FALSE, G_PARAM_READWRITE));
1961 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1962 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1963 "The pixel aspect ratio of the device", "1/1", G_PARAM_READWRITE));
1965 gstelement_class->change_state = gst_ximagesink_change_state;
1967 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_getcaps);
1968 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_ximagesink_setcaps);
1969 gstbasesink_class->buffer_alloc =
1970 GST_DEBUG_FUNCPTR (gst_ximagesink_buffer_alloc);
1971 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_ximagesink_get_times);
1972 gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);
1973 gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_ximagesink_show_frame);
1976 /* ============================================================= */
1978 /* Public Methods */
1980 /* ============================================================= */
1982 /* =========================================== */
1984 /* Object typing & Creation */
1986 /* =========================================== */
1989 gst_ximagesink_get_type (void)
1991 static GType ximagesink_type = 0;
1993 if (!ximagesink_type) {
1994 static const GTypeInfo ximagesink_info = {
1995 sizeof (GstXImageSinkClass),
1996 gst_ximagesink_base_init,
1998 (GClassInitFunc) gst_ximagesink_class_init,
2001 sizeof (GstXImageSink),
2003 (GInstanceInitFunc) gst_ximagesink_init,
2005 static const GInterfaceInfo iface_info = {
2006 (GInterfaceInitFunc) gst_ximagesink_interface_init,
2010 static const GInterfaceInfo navigation_info = {
2011 (GInterfaceInitFunc) gst_ximagesink_navigation_init,
2015 static const GInterfaceInfo overlay_info = {
2016 (GInterfaceInitFunc) gst_ximagesink_xoverlay_init,
2021 ximagesink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
2022 "GstXImageSink", &ximagesink_info, 0);
2024 g_type_add_interface_static (ximagesink_type, GST_TYPE_IMPLEMENTS_INTERFACE,
2026 g_type_add_interface_static (ximagesink_type, GST_TYPE_NAVIGATION,
2028 g_type_add_interface_static (ximagesink_type, GST_TYPE_X_OVERLAY,
2031 /* register type in a more safe place instead of at runtime since the
2032 * type registration is not threadsafe. */
2033 gst_ximage_buffer_get_type ();
2036 return ximagesink_type;